kobako 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +71 -0
- data/Cargo.lock +117 -140
- data/README.md +100 -125
- data/ROADMAP.md +23 -22
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +31 -0
- data/crates/kobako-runtime/Cargo.toml +2 -2
- data/crates/kobako-runtime/README.md +4 -3
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/lib.rs +1 -1
- data/crates/kobako-runtime/src/profile.rs +1 -1
- data/crates/kobako-runtime/src/runtime.rs +31 -17
- data/crates/kobako-runtime/src/snapshot.rs +13 -10
- data/crates/kobako-runtime/src/yielder.rs +9 -6
- data/crates/kobako-transport/CHANGELOG.md +28 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +21 -12
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +77 -53
- data/crates/kobako-transport/src/envelope/request.rs +295 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +44 -0
- data/crates/kobako-wasmtime/Cargo.toml +7 -5
- data/crates/kobako-wasmtime/README.md +2 -2
- data/crates/kobako-wasmtime/src/abi.rs +5 -8
- data/crates/kobako-wasmtime/src/ambient.rs +2 -2
- data/crates/kobako-wasmtime/src/cache.rs +25 -59
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +11 -14
- data/crates/kobako-wasmtime/src/dispatch.rs +70 -80
- data/crates/kobako-wasmtime/src/driver.rs +133 -86
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +145 -138
- data/crates/kobako-wasmtime/src/instance_pre.rs +11 -22
- data/crates/kobako-wasmtime/src/invocation.rs +55 -372
- data/crates/kobako-wasmtime/src/io.rs +286 -0
- data/crates/kobako-wasmtime/src/lib.rs +3 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/trap.rs +32 -45
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +69 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +94 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +34 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +50 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +2 -2
- data/ext/kobako/src/runtime/bridge.rs +38 -47
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/gvl.rs +4 -12
- data/ext/kobako/src/runtime.rs +169 -212
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +41 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/services.rb +7 -61
- data/lib/kobako/catalog/snippets.rb +42 -66
- data/lib/kobako/catalog.rb +2 -10
- data/lib/kobako/codec/decoder.rb +9 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +4 -10
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec.rb +3 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +51 -112
- data/lib/kobako/errors.rb +51 -127
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +23 -53
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +96 -133
- data/lib/kobako/sandbox_options.rb +38 -84
- data/lib/kobako/snippet/binary.rb +1 -16
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +1 -9
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +112 -195
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +5 -10
- data/lib/kobako/transport/reflection.rb +39 -50
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +21 -73
- data/lib/kobako/transport.rb +3 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +17 -0
- data/sig/kobako/catalog/services.rbs +0 -6
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec.rbs +2 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +2 -3
- data/sig/kobako/errors.rbs +6 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/runtime.rbs +12 -8
- data/sig/kobako/sandbox.rbs +4 -0
- data/sig/kobako/sandbox_options.rbs +2 -6
- data/sig/kobako/transport/dispatcher.rbs +14 -18
- data/sig/kobako/transport/reflection.rbs +5 -3
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +3 -3
- data/sig/kobako/transport.rbs +7 -6
- metadata +16 -4
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/rust-toolchain.toml +0 -15
|
@@ -1,130 +1,153 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! Guest linear-memory I/O for every host↔guest buffer handoff, whether
|
|
2
|
+
//! the invocation body drives it through its `Store` or a host import
|
|
3
|
+
//! drives it through its `Caller`.
|
|
2
4
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
5
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
//! bounds-check + `memory.write` dance with only the diagnostic strings
|
|
8
|
-
//! differing. The Store-based write path (`frames::write_envelope`) is a
|
|
9
|
-
//! separate beast — it holds the per-invocation `Store`, not a `Caller` —
|
|
10
|
-
//! and stays in `frames`.
|
|
5
|
+
//! The two shared steps answer an error kind, and each caller words it,
|
|
6
|
+
//! because depth decides what the same problem means: before the export
|
|
7
|
+
//! call it is a `Trap` or an intact runtime, inside a callback it is a
|
|
8
|
+
//! wire failure the guest receives.
|
|
11
9
|
|
|
12
|
-
use wasmtime::{
|
|
10
|
+
use wasmtime::{AsContext, AsContextMut, Caller, Memory};
|
|
13
11
|
|
|
12
|
+
use crate::exports::{Exports, RUNTIME_INCOMPATIBLE};
|
|
14
13
|
use crate::invocation::Invocation;
|
|
15
|
-
use kobako_runtime::error::Trap;
|
|
16
|
-
use kobako_runtime::yielder::Yielder;
|
|
17
14
|
use kobako_transport::abi::{unpack_ptr_len, MAX_DISPATCH_PAYLOAD};
|
|
15
|
+
use kobako_transport::envelope::Downcall;
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/// handed to the dispatch handler, so nested dispatch frames each
|
|
23
|
-
/// carry their own and stack on the Rust call stack with no shared slot.
|
|
24
|
-
pub(crate) struct CallerYielder<'a, 'c> {
|
|
25
|
-
caller: &'a mut Caller<'c, Invocation>,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
impl<'a, 'c> CallerYielder<'a, 'c> {
|
|
29
|
-
pub(crate) fn new(caller: &'a mut Caller<'c, Invocation>) -> Self {
|
|
30
|
-
Self { caller }
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
impl Yielder for CallerYielder<'_, '_> {
|
|
35
|
-
fn yield_to_block(&mut self, args: &[u8]) -> Result<Vec<u8>, Trap> {
|
|
36
|
-
drive_yield(self.caller, args).map_err(|msg| Trap::Other(msg.to_string()))
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// User-facing reason when a required guest export (the allocation or
|
|
41
|
-
/// block-yield hook) is absent or has the wrong signature — the loaded
|
|
42
|
-
/// `data/kobako.wasm` does not match the installed gem. Phrased in caller
|
|
43
|
-
/// vocabulary: the underlying hook symbol names are not actionable, and
|
|
44
|
-
/// the actionable fix is to rebuild the runtime.
|
|
45
|
-
const RUNTIME_INCOMPATIBLE: &str =
|
|
46
|
-
"the Sandbox runtime is incompatible; rebuild data/kobako.wasm against the installed version";
|
|
17
|
+
// The size-limit messages here and in `io` name the cap as 16 MiB, so
|
|
18
|
+
// moving the cap fails the build until they are reworded with it.
|
|
19
|
+
const _: () = assert!(MAX_DISPATCH_PAYLOAD == 16 << 20);
|
|
47
20
|
|
|
48
21
|
/// User-facing message for the "the loaded Wasm module is not a
|
|
49
|
-
/// Kobako-shaped runtime at all" failure mode
|
|
50
|
-
///
|
|
51
|
-
/// `frames`. One constant so the two detection sites cannot drift.
|
|
22
|
+
/// Kobako-shaped runtime at all" failure mode, such as an instance with no
|
|
23
|
+
/// `memory` export.
|
|
52
24
|
pub(crate) const SANDBOX_RUNTIME_NOT_KOBAKO: &str =
|
|
53
25
|
"the loaded Wasm module is not a Kobako-compatible runtime";
|
|
54
26
|
|
|
55
|
-
///
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
///
|
|
59
|
-
///
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
27
|
+
/// Why a host write into guest memory failed.
|
|
28
|
+
pub(crate) enum WriteError {
|
|
29
|
+
TooLarge,
|
|
30
|
+
/// The allocator or the memory is missing or mistyped; carries the
|
|
31
|
+
/// message for it.
|
|
32
|
+
Incompatible(&'static str),
|
|
33
|
+
Trapped(wasmtime::Error),
|
|
34
|
+
/// The allocator ran and answered 0.
|
|
35
|
+
OutOfMemory,
|
|
36
|
+
OutOfBounds,
|
|
65
37
|
}
|
|
66
38
|
|
|
67
|
-
///
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
39
|
+
/// Why a host read of a guest-named buffer failed.
|
|
40
|
+
pub(crate) enum ReadError {
|
|
41
|
+
TooLarge,
|
|
42
|
+
OutOfBounds,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Allocate a guest buffer through `__kobako_alloc` and copy `bytes` into
|
|
46
|
+
/// it, answering the buffer's address.
|
|
71
47
|
pub(crate) fn alloc_and_write(
|
|
72
|
-
|
|
48
|
+
mut ctx: impl AsContextMut,
|
|
49
|
+
exports: &Exports,
|
|
73
50
|
bytes: &[u8],
|
|
74
|
-
) -> Result<u32,
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
51
|
+
) -> Result<u32, WriteError> {
|
|
52
|
+
let len = checked_payload_len(bytes.len()).map_err(|_| WriteError::TooLarge)?;
|
|
53
|
+
let alloc = exports
|
|
54
|
+
.alloc
|
|
55
|
+
.as_ref()
|
|
56
|
+
.ok_or(WriteError::Incompatible(RUNTIME_INCOMPATIBLE))?;
|
|
57
|
+
let memory = exports
|
|
58
|
+
.memory
|
|
59
|
+
.ok_or(WriteError::Incompatible(SANDBOX_RUNTIME_NOT_KOBAKO))?;
|
|
82
60
|
let ptr = alloc
|
|
83
|
-
.call(&mut
|
|
84
|
-
.map_err(
|
|
61
|
+
.call(&mut ctx, len as u32)
|
|
62
|
+
.map_err(WriteError::Trapped)?;
|
|
85
63
|
if ptr == 0 {
|
|
86
|
-
return Err(
|
|
64
|
+
return Err(WriteError::OutOfMemory);
|
|
87
65
|
}
|
|
66
|
+
memory
|
|
67
|
+
.write(&mut ctx, ptr as usize, bytes)
|
|
68
|
+
.map_err(|_| WriteError::OutOfBounds)?;
|
|
69
|
+
Ok(ptr)
|
|
70
|
+
}
|
|
88
71
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
72
|
+
/// Copy out the buffer the guest names; a length past the 16 MiB cap is
|
|
73
|
+
/// refused before memory is touched.
|
|
74
|
+
pub(crate) fn read_buffer(
|
|
75
|
+
ctx: impl AsContext,
|
|
76
|
+
memory: Memory,
|
|
77
|
+
ptr: usize,
|
|
78
|
+
len: usize,
|
|
79
|
+
) -> Result<Vec<u8>, ReadError> {
|
|
80
|
+
if len > MAX_DISPATCH_PAYLOAD {
|
|
81
|
+
return Err(ReadError::TooLarge);
|
|
82
|
+
}
|
|
83
|
+
let data = memory.data(&ctx);
|
|
84
|
+
let range = guest_buffer_range(ptr, len, data.len()).map_err(|_| ReadError::OutOfBounds)?;
|
|
85
|
+
Ok(data[range].to_vec())
|
|
93
86
|
}
|
|
94
87
|
|
|
95
|
-
///
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
///
|
|
101
|
-
|
|
88
|
+
/// The handles of the instance a host import runs inside.
|
|
89
|
+
fn callback_exports(caller: &Caller<'_, Invocation>) -> Result<Exports, &'static str> {
|
|
90
|
+
caller.data().exports().ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Keep a trap the guest raised during a callback into it for the dispatch
|
|
94
|
+
/// import to end the invocation with, and answer the callback's own reason.
|
|
95
|
+
fn trapped(
|
|
102
96
|
caller: &mut Caller<'_, Invocation>,
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
trap: wasmtime::Error,
|
|
98
|
+
reason: &'static str,
|
|
99
|
+
) -> &'static str {
|
|
100
|
+
caller.data_mut().record_reentry_trap(trap);
|
|
101
|
+
reason
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// The request the guest hands the upcall import. Its `(ptr, len)` are
|
|
105
|
+
/// guest addresses, so they are read unsigned.
|
|
106
|
+
pub(crate) fn read_request(
|
|
107
|
+
caller: &mut Caller<'_, Invocation>,
|
|
108
|
+
req_ptr: i32,
|
|
109
|
+
req_len: i32,
|
|
105
110
|
) -> Result<Vec<u8>, &'static str> {
|
|
106
|
-
let
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
let memory = callback_exports(caller)?
|
|
112
|
+
.memory
|
|
113
|
+
.ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)?;
|
|
114
|
+
read_buffer(
|
|
115
|
+
&*caller,
|
|
116
|
+
memory,
|
|
117
|
+
req_ptr as u32 as usize,
|
|
118
|
+
req_len as u32 as usize,
|
|
119
|
+
)
|
|
120
|
+
.map_err(|err| match err {
|
|
121
|
+
ReadError::TooLarge => "request payload exceeds the 16 MiB limit",
|
|
122
|
+
ReadError::OutOfBounds => "the Sandbox produced an out-of-bounds request",
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Write `bytes` into the guest from inside a callback. A guest that has
|
|
127
|
+
/// already trapped is not called again.
|
|
128
|
+
pub(crate) fn write_from_callback(
|
|
129
|
+
caller: &mut Caller<'_, Invocation>,
|
|
130
|
+
bytes: &[u8],
|
|
131
|
+
) -> Result<u32, &'static str> {
|
|
132
|
+
if caller.data().reentry_trapped() {
|
|
133
|
+
return Err("the Sandbox already trapped during this invocation");
|
|
109
134
|
}
|
|
110
|
-
let
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
135
|
+
let exports = callback_exports(caller)?;
|
|
136
|
+
alloc_and_write(&mut *caller, &exports, bytes).map_err(|err| match err {
|
|
137
|
+
WriteError::TooLarge => "payload exceeds the 16 MiB limit",
|
|
138
|
+
WriteError::Incompatible(message) => message,
|
|
139
|
+
WriteError::Trapped(trap) => trapped(
|
|
140
|
+
caller,
|
|
141
|
+
trap,
|
|
142
|
+
"the Sandbox trapped while allocating memory for the request",
|
|
143
|
+
),
|
|
144
|
+
WriteError::OutOfMemory => "the Sandbox ran out of memory while preparing the request",
|
|
145
|
+
WriteError::OutOfBounds => "could not write the request into the Sandbox's memory",
|
|
146
|
+
})
|
|
120
147
|
}
|
|
121
148
|
|
|
122
|
-
///
|
|
123
|
-
///
|
|
124
|
-
/// Every host *write* boundary (`alloc_and_write`, `drive_yield`,
|
|
125
|
-
/// `frames::write_envelope`) routes its length through here so the
|
|
126
|
-
/// wire-violation reason is uniform; the *read* boundaries compare
|
|
127
|
-
/// against `MAX_DISPATCH_PAYLOAD` directly.
|
|
149
|
+
/// Every host write boundary routes its length through here, so the
|
|
150
|
+
/// wire-violation reason is uniform.
|
|
128
151
|
pub(crate) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
|
|
129
152
|
if len > MAX_DISPATCH_PAYLOAD {
|
|
130
153
|
return Err("payload exceeds the 16 MiB limit");
|
|
@@ -133,10 +156,6 @@ pub(crate) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
|
|
|
133
156
|
i32::try_from(len).map_err(|_| "payload exceeds the 16 MiB limit")
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
/// Compute the half-open range `[ptr, ptr + len)` for a guest linear-memory
|
|
137
|
-
/// copy, validating that the arithmetic does not overflow and the range
|
|
138
|
-
/// fits inside `mem_size`. Shared by `frames::write_envelope` (write side)
|
|
139
|
-
/// and `frames::fetch_outcome_bytes` (read side).
|
|
140
159
|
pub(crate) fn guest_buffer_range(
|
|
141
160
|
ptr: usize,
|
|
142
161
|
len: usize,
|
|
@@ -149,53 +168,41 @@ pub(crate) fn guest_buffer_range(
|
|
|
149
168
|
Ok(ptr..end)
|
|
150
169
|
}
|
|
151
170
|
|
|
152
|
-
/// The `(ptr, len)`
|
|
153
|
-
///
|
|
154
|
-
pub(crate) fn
|
|
171
|
+
/// The `(ptr, len)` the guest packs for any answer it hands back, the
|
|
172
|
+
/// outcome and a block's result alike.
|
|
173
|
+
pub(crate) fn unpack_guest_buffer(packed: u64) -> (usize, usize) {
|
|
155
174
|
let (ptr, len) = unpack_ptr_len(packed);
|
|
156
175
|
(ptr as usize, len as usize)
|
|
157
176
|
}
|
|
158
177
|
|
|
159
|
-
/// Allocate `args.len()` bytes in guest memory, copy the args payload in,
|
|
160
|
-
/// call `__kobako_yield_to_block(ptr, len)`, then read the response slice
|
|
161
|
-
/// the guest produced and return it. Mirrors `dispatch::write_response`'s
|
|
162
|
-
/// allocator dance but in the opposite direction — the host is the
|
|
163
|
-
/// *initiator* of this round-trip, not the responder.
|
|
164
178
|
pub(crate) fn drive_yield(
|
|
165
179
|
caller: &mut Caller<'_, Invocation>,
|
|
166
180
|
args: &[u8],
|
|
167
181
|
) -> Result<Vec<u8>, &'static str> {
|
|
168
|
-
let
|
|
169
|
-
let
|
|
182
|
+
let request = Downcall::Yield(args.to_vec()).encode();
|
|
183
|
+
let len_i32 = checked_payload_len(request.len())?;
|
|
184
|
+
let req_ptr = write_from_callback(caller, &request)? as i32;
|
|
170
185
|
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.map_err(|_| RUNTIME_INCOMPATIBLE)?,
|
|
175
|
-
_ => return Err(RUNTIME_INCOMPATIBLE),
|
|
176
|
-
};
|
|
186
|
+
let exports = callback_exports(caller)?;
|
|
187
|
+
let yield_fn = exports.downcall.ok_or(RUNTIME_INCOMPATIBLE)?;
|
|
188
|
+
let memory = exports.memory.ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)?;
|
|
177
189
|
let packed = yield_fn
|
|
178
190
|
.call(&mut *caller, (req_ptr, len_i32))
|
|
179
|
-
.map_err(|
|
|
180
|
-
let (
|
|
181
|
-
if
|
|
191
|
+
.map_err(|trap| trapped(caller, trap, "the Sandbox trapped while invoking a block"))?;
|
|
192
|
+
let (reply_ptr, reply_len) = unpack_guest_buffer(packed);
|
|
193
|
+
if reply_len == 0 {
|
|
182
194
|
return Err("the Sandbox returned an empty block result");
|
|
183
195
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
let mem = memory_export(caller)?;
|
|
189
|
-
let data = mem.data(&caller);
|
|
190
|
-
let range = guest_buffer_range(resp_ptr, resp_len, data.len())
|
|
191
|
-
.map_err(|_| "the Sandbox returned an out-of-bounds block result")?;
|
|
192
|
-
Ok(data[range].to_vec())
|
|
196
|
+
read_buffer(&*caller, memory, reply_ptr, reply_len).map_err(|err| match err {
|
|
197
|
+
ReadError::TooLarge => "block result payload exceeds the 16 MiB limit",
|
|
198
|
+
ReadError::OutOfBounds => "the Sandbox returned an out-of-bounds block result",
|
|
199
|
+
})
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
#[cfg(test)]
|
|
196
203
|
mod tests {
|
|
197
204
|
use super::{
|
|
198
|
-
checked_payload_len, guest_buffer_range,
|
|
205
|
+
checked_payload_len, guest_buffer_range, unpack_guest_buffer, MAX_DISPATCH_PAYLOAD,
|
|
199
206
|
};
|
|
200
207
|
|
|
201
208
|
// @behavior WE-059
|
|
@@ -243,16 +250,16 @@ mod tests {
|
|
|
243
250
|
|
|
244
251
|
// @behavior WE-001
|
|
245
252
|
#[test]
|
|
246
|
-
fn
|
|
253
|
+
fn unpack_guest_buffer_extracts_high_ptr_low_len() {
|
|
247
254
|
assert_eq!(
|
|
248
|
-
|
|
255
|
+
unpack_guest_buffer(0xAABB_CCDD_1122_3344),
|
|
249
256
|
(0xAABB_CCDD, 0x1122_3344)
|
|
250
257
|
);
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
// @behavior WE-065
|
|
254
261
|
#[test]
|
|
255
|
-
fn
|
|
256
|
-
assert_eq!(
|
|
262
|
+
fn unpack_guest_buffer_zero_decodes_to_zero_pair() {
|
|
263
|
+
assert_eq!(unpack_guest_buffer(0), (0, 0));
|
|
257
264
|
}
|
|
258
265
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! Per-path cache of pre-instantiated wasmtime artifacts.
|
|
2
2
|
//!
|
|
3
3
|
//! The `Linker` wiring (the WASI preview1 import set plus the
|
|
4
|
-
//! `
|
|
4
|
+
//! `__kobako_upcall` host import) and its type-check against the
|
|
5
5
|
//! compiled Module are identical for every `Kobako::Runtime` on the
|
|
6
6
|
//! same Guest Binary — both host closures read all their state from
|
|
7
7
|
//! the `Invocation` inside the calling Store, never from the Runtime.
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
//! version check it guards (`crate::abi`) — takes both off the
|
|
10
10
|
//! `Driver::new` hot path.
|
|
11
11
|
//!
|
|
12
|
-
//! Concurrency:
|
|
13
|
-
//!
|
|
12
|
+
//! Concurrency: the same as `crate::cache` — the Mutex covers lookup and
|
|
13
|
+
//! insert only, and a concurrent miss builds an equivalent `InstancePre`.
|
|
14
14
|
|
|
15
15
|
use std::collections::HashMap;
|
|
16
16
|
use std::path::{Path, PathBuf};
|
|
@@ -27,11 +27,6 @@ use kobako_runtime::error::SetupError;
|
|
|
27
27
|
static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocation>>>> =
|
|
28
28
|
OnceLock::new();
|
|
29
29
|
|
|
30
|
-
/// Look up `path` in the per-path `InstancePre` cache, wiring the
|
|
31
|
-
/// Linker, resolving the Module's imports, and verifying the artifact's
|
|
32
|
-
/// ABI version on a miss. Compilation faults surface through
|
|
33
|
-
/// `cached_module`; import-resolution and ABI faults return a
|
|
34
|
-
/// runtime-dead `SetupError` (boundary → `Kobako::SetupError`).
|
|
35
30
|
pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>, SetupError> {
|
|
36
31
|
let cache = INSTANCE_PRE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
|
37
32
|
|
|
@@ -59,8 +54,6 @@ pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>
|
|
|
59
54
|
Ok(pre)
|
|
60
55
|
}
|
|
61
56
|
|
|
62
|
-
/// Build the host-import `Linker` every Guest Binary instantiates
|
|
63
|
-
/// against.
|
|
64
57
|
fn build_linker() -> Result<Linker<Invocation>, SetupError> {
|
|
65
58
|
let mut linker: Linker<Invocation> = Linker::new(shared_engine()?);
|
|
66
59
|
|
|
@@ -72,21 +65,17 @@ fn build_linker() -> Result<Linker<Invocation>, SetupError> {
|
|
|
72
65
|
p1::add_to_linker_sync(&mut linker, |state: &mut Invocation| state.wasi_mut())
|
|
73
66
|
.map_err(|e| SetupError::Dead(format!("failed to set up the WASI runtime: {e}")))?;
|
|
74
67
|
|
|
75
|
-
// `
|
|
76
|
-
// § ABI Signatures
|
|
77
|
-
//
|
|
78
|
-
// Reads the Call bytes from guest memory and hands them —
|
|
79
|
-
// undecoded — to the bound `DispatchHandler` (the frontend's
|
|
80
|
-
// dispatch bridge, e.g. a Ruby Proc), then allocates a guest
|
|
81
|
-
// buffer through `__kobako_alloc`, writes the handler's Reply
|
|
82
|
-
// bytes there, and returns the packed `(ptr<<32)|len`. The
|
|
83
|
-
// dispatcher returns 0 on any wire-layer fault (including no
|
|
84
|
-
// handler bound); see `dispatch::handle`.
|
|
68
|
+
// `__kobako_upcall`, `(ptr: i32, len: i32) -> i64` per
|
|
69
|
+
// docs/wire/abi.md § ABI Signatures; `dispatch::handle` owns what it
|
|
70
|
+
// answers.
|
|
85
71
|
linker
|
|
86
72
|
.func_wrap(
|
|
87
73
|
"env",
|
|
88
|
-
"
|
|
89
|
-
|mut caller: Caller<'_, Invocation>,
|
|
74
|
+
"__kobako_upcall",
|
|
75
|
+
|mut caller: Caller<'_, Invocation>,
|
|
76
|
+
req_ptr: i32,
|
|
77
|
+
req_len: i32|
|
|
78
|
+
-> wasmtime::Result<i64> {
|
|
90
79
|
dispatch::handle(&mut caller, req_ptr, req_len)
|
|
91
80
|
},
|
|
92
81
|
)
|