kobako 0.28.0 → 0.30.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 +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -1,265 +0,0 @@
|
|
|
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`.
|
|
4
|
-
//!
|
|
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.
|
|
9
|
-
|
|
10
|
-
use wasmtime::{AsContext, AsContextMut, Caller, Memory};
|
|
11
|
-
|
|
12
|
-
use crate::exports::{Exports, RUNTIME_INCOMPATIBLE};
|
|
13
|
-
use crate::invocation::Invocation;
|
|
14
|
-
use kobako_transport::abi::{unpack_ptr_len, MAX_DISPATCH_PAYLOAD};
|
|
15
|
-
use kobako_transport::envelope::Downcall;
|
|
16
|
-
|
|
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);
|
|
20
|
-
|
|
21
|
-
/// User-facing message for the "the loaded Wasm module is not a
|
|
22
|
-
/// Kobako-shaped runtime at all" failure mode, such as an instance with no
|
|
23
|
-
/// `memory` export.
|
|
24
|
-
pub(crate) const SANDBOX_RUNTIME_NOT_KOBAKO: &str =
|
|
25
|
-
"the loaded Wasm module is not a Kobako-compatible runtime";
|
|
26
|
-
|
|
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,
|
|
37
|
-
}
|
|
38
|
-
|
|
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.
|
|
47
|
-
pub(crate) fn alloc_and_write(
|
|
48
|
-
mut ctx: impl AsContextMut,
|
|
49
|
-
exports: &Exports,
|
|
50
|
-
bytes: &[u8],
|
|
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))?;
|
|
60
|
-
let ptr = alloc
|
|
61
|
-
.call(&mut ctx, len as u32)
|
|
62
|
-
.map_err(WriteError::Trapped)?;
|
|
63
|
-
if ptr == 0 {
|
|
64
|
-
return Err(WriteError::OutOfMemory);
|
|
65
|
-
}
|
|
66
|
-
memory
|
|
67
|
-
.write(&mut ctx, ptr as usize, bytes)
|
|
68
|
-
.map_err(|_| WriteError::OutOfBounds)?;
|
|
69
|
-
Ok(ptr)
|
|
70
|
-
}
|
|
71
|
-
|
|
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())
|
|
86
|
-
}
|
|
87
|
-
|
|
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(
|
|
96
|
-
caller: &mut Caller<'_, Invocation>,
|
|
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,
|
|
110
|
-
) -> Result<Vec<u8>, &'static str> {
|
|
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");
|
|
134
|
-
}
|
|
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
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/// Every host write boundary routes its length through here, so the
|
|
150
|
-
/// wire-violation reason is uniform.
|
|
151
|
-
pub(crate) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
|
|
152
|
-
if len > MAX_DISPATCH_PAYLOAD {
|
|
153
|
-
return Err("payload exceeds the 16 MiB limit");
|
|
154
|
-
}
|
|
155
|
-
// The cap above sits below `i32::MAX`, so this conversion cannot wrap.
|
|
156
|
-
i32::try_from(len).map_err(|_| "payload exceeds the 16 MiB limit")
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
pub(crate) fn guest_buffer_range(
|
|
160
|
-
ptr: usize,
|
|
161
|
-
len: usize,
|
|
162
|
-
mem_size: usize,
|
|
163
|
-
) -> Result<core::ops::Range<usize>, &'static str> {
|
|
164
|
-
let end = ptr.checked_add(len).ok_or("ptr + len overflow")?;
|
|
165
|
-
if end > mem_size {
|
|
166
|
-
return Err("range exceeds Sandbox memory size");
|
|
167
|
-
}
|
|
168
|
-
Ok(ptr..end)
|
|
169
|
-
}
|
|
170
|
-
|
|
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) {
|
|
174
|
-
let (ptr, len) = unpack_ptr_len(packed);
|
|
175
|
-
(ptr as usize, len as usize)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
pub(crate) fn drive_yield(
|
|
179
|
-
caller: &mut Caller<'_, Invocation>,
|
|
180
|
-
args: &[u8],
|
|
181
|
-
) -> Result<Vec<u8>, &'static str> {
|
|
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;
|
|
185
|
-
|
|
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)?;
|
|
189
|
-
let packed = yield_fn
|
|
190
|
-
.call(&mut *caller, (req_ptr, len_i32))
|
|
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 {
|
|
194
|
-
return Err("the Sandbox returned an empty block result");
|
|
195
|
-
}
|
|
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
|
-
})
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#[cfg(test)]
|
|
203
|
-
mod tests {
|
|
204
|
-
use super::{
|
|
205
|
-
checked_payload_len, guest_buffer_range, unpack_guest_buffer, MAX_DISPATCH_PAYLOAD,
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// @behavior WE-059
|
|
209
|
-
#[test]
|
|
210
|
-
fn checked_payload_len_accepts_zero_and_the_cap() {
|
|
211
|
-
assert_eq!(checked_payload_len(0), Ok(0));
|
|
212
|
-
assert_eq!(
|
|
213
|
-
checked_payload_len(MAX_DISPATCH_PAYLOAD),
|
|
214
|
-
Ok(MAX_DISPATCH_PAYLOAD as i32)
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// @behavior WE-060
|
|
219
|
-
#[test]
|
|
220
|
-
fn checked_payload_len_rejects_past_the_cap() {
|
|
221
|
-
assert!(checked_payload_len(MAX_DISPATCH_PAYLOAD + 1).is_err());
|
|
222
|
-
assert!(checked_payload_len(usize::MAX).is_err());
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// @behavior WE-061
|
|
226
|
-
#[test]
|
|
227
|
-
fn guest_buffer_range_returns_half_open_range() {
|
|
228
|
-
assert_eq!(guest_buffer_range(10, 5, 100), Ok(10..15));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// @behavior WE-062
|
|
232
|
-
#[test]
|
|
233
|
-
fn guest_buffer_range_accepts_zero_length_at_any_in_bounds_ptr() {
|
|
234
|
-
assert_eq!(guest_buffer_range(0, 0, 0), Ok(0..0));
|
|
235
|
-
assert_eq!(guest_buffer_range(42, 0, 100), Ok(42..42));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// @behavior WE-063
|
|
239
|
-
#[test]
|
|
240
|
-
fn guest_buffer_range_rejects_ptr_plus_len_overflow() {
|
|
241
|
-
assert!(guest_buffer_range(usize::MAX, 1, usize::MAX).is_err());
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// @behavior WE-064
|
|
245
|
-
#[test]
|
|
246
|
-
fn guest_buffer_range_rejects_end_past_memory() {
|
|
247
|
-
assert!(guest_buffer_range(10, 100, 50).is_err());
|
|
248
|
-
assert_eq!(guest_buffer_range(0, 50, 50), Ok(0..50));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// @behavior WE-001
|
|
252
|
-
#[test]
|
|
253
|
-
fn unpack_guest_buffer_extracts_high_ptr_low_len() {
|
|
254
|
-
assert_eq!(
|
|
255
|
-
unpack_guest_buffer(0xAABB_CCDD_1122_3344),
|
|
256
|
-
(0xAABB_CCDD, 0x1122_3344)
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// @behavior WE-065
|
|
261
|
-
#[test]
|
|
262
|
-
fn unpack_guest_buffer_zero_decodes_to_zero_pair() {
|
|
263
|
-
assert_eq!(unpack_guest_buffer(0), (0, 0));
|
|
264
|
-
}
|
|
265
|
-
}
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
//! Per-invocation I/O between the host and the guest: it writes the
|
|
2
|
-
//! entry's downcall into a freshly allocated guest buffer, puts the
|
|
3
|
-
//! Preamble on stdin beside stdout / stderr capture pipes for the WASI
|
|
4
|
-
//! context, and reads the Outcome the downcall answered with.
|
|
5
|
-
|
|
6
|
-
use wasmtime::Store as WtStore;
|
|
7
|
-
use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
|
|
8
|
-
use wasmtime_wasi::WasiCtxBuilder;
|
|
9
|
-
|
|
10
|
-
use crate::config::Config;
|
|
11
|
-
use crate::exports::Exports;
|
|
12
|
-
use crate::guest_mem::{self, ReadError, WriteError};
|
|
13
|
-
use crate::invocation::Invocation;
|
|
14
|
-
use crate::{ambient, capture};
|
|
15
|
-
use kobako_runtime::error::{InvokeError, SetupError, Trap};
|
|
16
|
-
use kobako_runtime::profile::Profile;
|
|
17
|
-
use kobako_transport::abi::FRAME_LEN_SIZE;
|
|
18
|
-
use kobako_transport::envelope::{CompileReply, Outcome};
|
|
19
|
-
|
|
20
|
-
/// A missing or trapping allocator is an engine fault, a `Trap`; one that
|
|
21
|
-
/// runs but returns 0 leaves the runtime intact, so it is a `SetupError`.
|
|
22
|
-
pub(crate) fn write_request(
|
|
23
|
-
store: &mut WtStore<Invocation>,
|
|
24
|
-
exports: &Exports,
|
|
25
|
-
envelope: &[u8],
|
|
26
|
-
) -> Result<(i32, i32), InvokeError> {
|
|
27
|
-
let trap = |message: String| -> InvokeError { Trap::Other(message).into() };
|
|
28
|
-
let ptr =
|
|
29
|
-
guest_mem::alloc_and_write(&mut *store, exports, envelope).map_err(|err| match err {
|
|
30
|
-
WriteError::OutOfMemory => {
|
|
31
|
-
SetupError::Intact("could not allocate input buffer (out of memory)".to_string())
|
|
32
|
-
.into()
|
|
33
|
-
}
|
|
34
|
-
WriteError::TooLarge => trap("payload exceeds the 16 MiB limit".to_string()),
|
|
35
|
-
WriteError::Incompatible(message) => trap(message.to_string()),
|
|
36
|
-
WriteError::Trapped(e) => trap(format!("failed to allocate input buffer: {e}")),
|
|
37
|
-
WriteError::OutOfBounds => {
|
|
38
|
-
trap("the input buffer lies outside the Sandbox's memory".to_string())
|
|
39
|
-
}
|
|
40
|
-
})?;
|
|
41
|
-
// The write has already held the length to the payload cap.
|
|
42
|
-
Ok((ptr as i32, envelope.len() as i32))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/// An uncapped output channel relies on `memory_limit` for its real
|
|
46
|
-
/// ceiling. The 16 MiB cap keeps the Preamble's `u32` length prefix from
|
|
47
|
-
/// wrapping.
|
|
48
|
-
pub(crate) fn install_wasi(
|
|
49
|
-
store: &mut WtStore<Invocation>,
|
|
50
|
-
config: &Config,
|
|
51
|
-
preamble: &[u8],
|
|
52
|
-
) -> Result<(), Trap> {
|
|
53
|
-
// The Preamble carries the same 16 MiB cap as a downcall request
|
|
54
|
-
// (`write_request`): past it, the length prefix would silently wrap.
|
|
55
|
-
guest_mem::checked_payload_len(preamble.len()).map_err(|msg| Trap::Other(msg.to_string()))?;
|
|
56
|
-
|
|
57
|
-
let mut stdin_content: Vec<u8> = Vec::with_capacity(FRAME_LEN_SIZE + preamble.len());
|
|
58
|
-
stdin_content.extend_from_slice(&(preamble.len() as u32).to_be_bytes());
|
|
59
|
-
stdin_content.extend_from_slice(preamble);
|
|
60
|
-
|
|
61
|
-
let stdin_pipe = MemoryInputPipe::new(stdin_content);
|
|
62
|
-
let stdout_pipe = MemoryOutputPipe::new(capture::pipe_capacity(config.stdout_limit));
|
|
63
|
-
let stderr_pipe = MemoryOutputPipe::new(capture::pipe_capacity(config.stderr_limit));
|
|
64
|
-
|
|
65
|
-
let mut builder = WasiCtxBuilder::new();
|
|
66
|
-
builder.stdin(stdin_pipe);
|
|
67
|
-
builder.stdout(stdout_pipe.clone());
|
|
68
|
-
builder.stderr(stderr_pipe.clone());
|
|
69
|
-
// The requested profile decides the ambient-authority grant: the
|
|
70
|
-
// hermetic rung denies the preview1 time and entropy imports (see
|
|
71
|
-
// `ambient`), the permissive rung leaves the live WASI sources.
|
|
72
|
-
// Filesystem, environment, and network stay absent on both rungs —
|
|
73
|
-
// the builder grants none unless asked. The exhaustive match makes
|
|
74
|
-
// a future ladder rung a compile error here, not a silent grant.
|
|
75
|
-
match config.profile {
|
|
76
|
-
Profile::Hermetic => {
|
|
77
|
-
builder.wall_clock(ambient::FrozenWallClock);
|
|
78
|
-
builder.monotonic_clock(ambient::FrozenMonotonicClock);
|
|
79
|
-
builder.secure_random(ambient::deterministic_rng());
|
|
80
|
-
}
|
|
81
|
-
Profile::Permissive => {}
|
|
82
|
-
}
|
|
83
|
-
let wasi = builder.build_p1();
|
|
84
|
-
|
|
85
|
-
store
|
|
86
|
-
.data_mut()
|
|
87
|
-
.install_wasi(wasi, stdout_pipe, stderr_pipe);
|
|
88
|
-
Ok(())
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/// A guest that answered without an Outcome the envelope can frame — an
|
|
92
|
-
/// empty answer included — has nothing left to attribute, so it is a
|
|
93
|
-
/// `Trap` like every other way of failing to read the result.
|
|
94
|
-
pub(crate) fn read_outcome(
|
|
95
|
-
store: &WtStore<Invocation>,
|
|
96
|
-
exports: &Exports,
|
|
97
|
-
packed: u64,
|
|
98
|
-
) -> Result<Outcome, Trap> {
|
|
99
|
-
let bytes = read_answer(store, exports, packed)?;
|
|
100
|
-
Outcome::decode(&bytes).map_err(|_| {
|
|
101
|
-
Trap::Other(
|
|
102
|
-
if bytes.is_empty() {
|
|
103
|
-
"Sandbox exited without producing a result"
|
|
104
|
-
} else {
|
|
105
|
-
"Sandbox produced an unrecognised result"
|
|
106
|
-
}
|
|
107
|
-
.to_string(),
|
|
108
|
-
)
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/// Held to the same 16 MiB bound as an Outcome; an answer that does not
|
|
113
|
-
/// frame is a `Trap` the caller treats like any other failed compile.
|
|
114
|
-
pub(crate) fn read_compile_reply(
|
|
115
|
-
store: &WtStore<Invocation>,
|
|
116
|
-
exports: &Exports,
|
|
117
|
-
packed: u64,
|
|
118
|
-
) -> Result<CompileReply, Trap> {
|
|
119
|
-
let bytes = read_answer(store, exports, packed)?;
|
|
120
|
-
CompileReply::decode(&bytes)
|
|
121
|
-
.map_err(|_| Trap::Other("Sandbox produced an unrecognised compile answer".to_string()))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
fn read_answer(
|
|
125
|
-
store: &WtStore<Invocation>,
|
|
126
|
-
exports: &Exports,
|
|
127
|
-
packed: u64,
|
|
128
|
-
) -> Result<Vec<u8>, Trap> {
|
|
129
|
-
let mem = exports.require_memory()?;
|
|
130
|
-
let (ptr, len) = guest_mem::unpack_guest_buffer(packed);
|
|
131
|
-
guest_mem::read_buffer(store, mem, ptr, len).map_err(|err| {
|
|
132
|
-
Trap::Other(
|
|
133
|
-
match err {
|
|
134
|
-
ReadError::TooLarge => "result payload exceeds the 16 MiB limit",
|
|
135
|
-
ReadError::OutOfBounds => "the Sandbox result is out of bounds",
|
|
136
|
-
}
|
|
137
|
-
.to_string(),
|
|
138
|
-
)
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#[cfg(test)]
|
|
143
|
-
mod tests {
|
|
144
|
-
//! Witness what the context `install_wasi` builds grants at
|
|
145
|
-
//! each profile rung: `wasi:clocks` / `wasi:random` frozen under
|
|
146
|
-
//! `Hermetic` and live under `Permissive`, and no filesystem,
|
|
147
|
-
//! environment, or socket under either.
|
|
148
|
-
use wasmtime::{AsContextMut, Linker, Module};
|
|
149
|
-
use wasmtime_wasi::p1;
|
|
150
|
-
|
|
151
|
-
use super::*;
|
|
152
|
-
use crate::cache::shared_engine;
|
|
153
|
-
|
|
154
|
-
/// Preview1 `errno` for a descriptor the context does not hold.
|
|
155
|
-
const ERRNO_BADF: i64 = 8;
|
|
156
|
-
|
|
157
|
-
/// Preview1 probe with one export per ambient source: `clock_ns`
|
|
158
|
-
/// reads the realtime clock, `random_word` reads eight entropy bytes,
|
|
159
|
-
/// `environ_count` counts environment variables, and `prestat_errno`
|
|
160
|
-
/// / `accept_errno` ask descriptor 3 for a directory and a
|
|
161
|
-
/// connection. Preview1 numbers preopens from 3, so nothing there
|
|
162
|
-
/// means no directory and no socket was granted.
|
|
163
|
-
const PROBE_WAT: &str = r#"
|
|
164
|
-
(module
|
|
165
|
-
(import "wasi_snapshot_preview1" "clock_time_get"
|
|
166
|
-
(func $clock (param i32 i64 i32) (result i32)))
|
|
167
|
-
(import "wasi_snapshot_preview1" "random_get"
|
|
168
|
-
(func $random (param i32 i32) (result i32)))
|
|
169
|
-
(import "wasi_snapshot_preview1" "environ_sizes_get"
|
|
170
|
-
(func $environ (param i32 i32) (result i32)))
|
|
171
|
-
(import "wasi_snapshot_preview1" "fd_prestat_get"
|
|
172
|
-
(func $prestat (param i32 i32) (result i32)))
|
|
173
|
-
(import "wasi_snapshot_preview1" "sock_accept"
|
|
174
|
-
(func $accept (param i32 i32 i32) (result i32)))
|
|
175
|
-
(memory (export "memory") 1)
|
|
176
|
-
(func (export "clock_ns") (result i64)
|
|
177
|
-
(drop (call $clock (i32.const 0) (i64.const 1) (i32.const 0)))
|
|
178
|
-
(i64.load (i32.const 0)))
|
|
179
|
-
(func (export "random_word") (result i64)
|
|
180
|
-
(drop (call $random (i32.const 8) (i32.const 8)))
|
|
181
|
-
(i64.load (i32.const 8)))
|
|
182
|
-
(func (export "environ_count") (result i64)
|
|
183
|
-
(drop (call $environ (i32.const 16) (i32.const 20)))
|
|
184
|
-
(i64.extend_i32_u (i32.load (i32.const 16))))
|
|
185
|
-
(func (export "prestat_errno") (result i64)
|
|
186
|
-
(i64.extend_i32_u (call $prestat (i32.const 3) (i32.const 24))))
|
|
187
|
-
(func (export "accept_errno") (result i64)
|
|
188
|
-
(i64.extend_i32_u (call $accept (i32.const 3) (i32.const 0) (i32.const 24)))))
|
|
189
|
-
"#;
|
|
190
|
-
|
|
191
|
-
/// Instantiate the probe over a WASI context built at `profile` and
|
|
192
|
-
/// return a reader for its exports.
|
|
193
|
-
fn probe(profile: Profile) -> impl FnMut(&str) -> i64 {
|
|
194
|
-
let engine = shared_engine().expect("shared engine must be constructible");
|
|
195
|
-
let config = Config {
|
|
196
|
-
timeout: None,
|
|
197
|
-
memory_limit: None,
|
|
198
|
-
stdout_limit: None,
|
|
199
|
-
stderr_limit: None,
|
|
200
|
-
profile,
|
|
201
|
-
};
|
|
202
|
-
let mut store = WtStore::new(engine, Invocation::new(None));
|
|
203
|
-
store.set_epoch_deadline(crate::trap::NO_TIMEOUT_EPOCH_DELTA);
|
|
204
|
-
install_wasi(&mut store, &config, &[]).expect("WASI context must install");
|
|
205
|
-
|
|
206
|
-
let mut linker: Linker<Invocation> = Linker::new(engine);
|
|
207
|
-
p1::add_to_linker_sync(&mut linker, |state: &mut Invocation| state.wasi_mut())
|
|
208
|
-
.expect("WASI imports must link");
|
|
209
|
-
let module = Module::new(engine, PROBE_WAT).expect("probe module must compile");
|
|
210
|
-
let instance = linker
|
|
211
|
-
.instantiate(&mut store, &module)
|
|
212
|
-
.expect("probe module must instantiate");
|
|
213
|
-
|
|
214
|
-
move |name| {
|
|
215
|
-
instance
|
|
216
|
-
.get_typed_func::<(), i64>(store.as_context_mut(), name)
|
|
217
|
-
.expect("probe export must resolve")
|
|
218
|
-
.call(store.as_context_mut(), ())
|
|
219
|
-
.expect("probe export must run")
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/// Assert that the context built at `profile` grants no directory,
|
|
224
|
-
/// environment variable, or socket.
|
|
225
|
-
fn assert_grants_no_resource(profile: Profile) {
|
|
226
|
-
let mut read = probe(profile);
|
|
227
|
-
assert_eq!(
|
|
228
|
-
read("prestat_errno"),
|
|
229
|
-
ERRNO_BADF,
|
|
230
|
-
"a {profile:?} guest's WASI context must preopen no directory"
|
|
231
|
-
);
|
|
232
|
-
assert_eq!(
|
|
233
|
-
read("environ_count"),
|
|
234
|
-
0,
|
|
235
|
-
"a {profile:?} guest's WASI context must carry no environment variable"
|
|
236
|
-
);
|
|
237
|
-
assert_eq!(
|
|
238
|
-
read("accept_errno"),
|
|
239
|
-
ERRNO_BADF,
|
|
240
|
-
"a {profile:?} guest's WASI context must hold no socket"
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// @behavior RT-047 RT-048
|
|
245
|
-
#[test]
|
|
246
|
-
fn hermetic_denies_ambient_time_and_entropy() {
|
|
247
|
-
let mut read = probe(Profile::Hermetic);
|
|
248
|
-
assert_eq!(
|
|
249
|
-
read("clock_ns"),
|
|
250
|
-
0,
|
|
251
|
-
"a hermetic guest's wasi:clocks must read the Unix epoch, not host time"
|
|
252
|
-
);
|
|
253
|
-
assert_eq!(
|
|
254
|
-
read("random_word"),
|
|
255
|
-
0,
|
|
256
|
-
"a hermetic guest's wasi:random must yield the constant stream, not host entropy"
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// @behavior RT-049 RT-050
|
|
261
|
-
#[test]
|
|
262
|
-
fn permissive_grants_live_ambient_time_and_entropy() {
|
|
263
|
-
let mut read = probe(Profile::Permissive);
|
|
264
|
-
assert!(
|
|
265
|
-
read("clock_ns") > 0,
|
|
266
|
-
"a permissive guest's wasi:clocks must read live host time"
|
|
267
|
-
);
|
|
268
|
-
assert_ne!(
|
|
269
|
-
read("random_word"),
|
|
270
|
-
0,
|
|
271
|
-
"a permissive guest's wasi:random must yield host entropy"
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// @behavior RT-067
|
|
276
|
-
#[test]
|
|
277
|
-
fn hermetic_grants_no_filesystem_environment_or_socket() {
|
|
278
|
-
assert_grants_no_resource(Profile::Hermetic);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// @behavior RT-067
|
|
282
|
-
#[test]
|
|
283
|
-
fn permissive_grants_no_filesystem_environment_or_socket() {
|
|
284
|
-
assert_grants_no_resource(Profile::Permissive);
|
|
285
|
-
}
|
|
286
|
-
}
|