kobako 0.28.0 → 0.29.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 +170 -0
- data/Cargo.lock +111 -82
- data/README.md +154 -36
- data/ROADMAP.md +5 -0
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +86 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +13 -8
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +11 -1
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-transport/CHANGELOG.md +17 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +1 -1
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +32 -14
- data/crates/kobako-transport/src/envelope/request.rs +18 -8
- data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
- data/crates/kobako-wasmtime/Cargo.toml +21 -16
- data/crates/kobako-wasmtime/README.md +5 -3
- data/crates/kobako-wasmtime/src/abi.rs +66 -32
- data/crates/kobako-wasmtime/src/ambient.rs +63 -4
- data/crates/kobako-wasmtime/src/cache.rs +185 -74
- data/crates/kobako-wasmtime/src/config.rs +58 -1
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
- data/crates/kobako-wasmtime/src/driver.rs +46 -44
- data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
- data/crates/kobako-wasmtime/src/invocation.rs +35 -46
- data/crates/kobako-wasmtime/src/io.rs +5 -202
- data/crates/kobako-wasmtime/src/lib.rs +4 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +39 -19
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
- data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -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/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +7 -1
- 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 +120 -199
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +11 -0
- data/lib/kobako/catalog/snippets.rb +5 -9
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +1 -0
- data/lib/kobako/codec/ext_types.rb +1 -1
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +38 -11
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/pool.rb +2 -2
- data/lib/kobako/sandbox.rb +40 -12
- data/lib/kobako/sandbox_options.rb +39 -12
- data/lib/kobako/snippet/{binary.rb → bytecode.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 +30 -26
- data/lib/kobako/transport/exposure.rb +5 -7
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/yielder.rb +8 -30
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +4 -0
- data/sig/kobako/catalog/snippets.rbs +1 -1
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +2 -0
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +7 -1
- data/sig/kobako/sandbox_options.rbs +24 -2
- 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 +3 -3
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +2 -2
- metadata +60 -7
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
//! The guest's WASI context for one instance: the Preamble on stdin,
|
|
2
|
+
//! a capture pipe for each of stdout and stderr, the ambient authority
|
|
3
|
+
//! the instance's purpose grants, and a preopen for each Mount an
|
|
4
|
+
//! invocation was handed.
|
|
5
|
+
|
|
6
|
+
use wasmtime_wasi::p1::WasiP1Ctx;
|
|
7
|
+
use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
|
|
8
|
+
use wasmtime_wasi::WasiCtxBuilder;
|
|
9
|
+
|
|
10
|
+
use crate::ambient::{self, Clocks};
|
|
11
|
+
use crate::config::Config;
|
|
12
|
+
use crate::descriptors::Descriptors;
|
|
13
|
+
use crate::{capture, guest_mem};
|
|
14
|
+
use kobako_runtime::error::Trap;
|
|
15
|
+
use kobako_runtime::grant::Grant;
|
|
16
|
+
use kobako_runtime::mount::Mount;
|
|
17
|
+
use kobako_runtime::profile::Profile;
|
|
18
|
+
use kobako_transport::abi::FRAME_LEN_SIZE;
|
|
19
|
+
|
|
20
|
+
/// One instance's WASI context: its descriptor table, the clocks its
|
|
21
|
+
/// waits pass on, and the capture pipes stdout and stderr write into, so
|
|
22
|
+
/// the captures are read back from the same owner that wired them.
|
|
23
|
+
pub(crate) struct Wasi {
|
|
24
|
+
ctx: WasiP1Ctx,
|
|
25
|
+
descriptors: Descriptors,
|
|
26
|
+
clocks: Clocks,
|
|
27
|
+
stdout: MemoryOutputPipe,
|
|
28
|
+
stderr: MemoryOutputPipe,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl Wasi {
|
|
32
|
+
/// An invocation's context, granting the posture the Sandbox requested
|
|
33
|
+
/// and presenting each of the invocation's Mounts as a preopen, from
|
|
34
|
+
/// descriptor 3 in order.
|
|
35
|
+
pub(crate) fn for_invocation(
|
|
36
|
+
config: &Config,
|
|
37
|
+
preamble: &[u8],
|
|
38
|
+
grants: &[Grant],
|
|
39
|
+
) -> Result<Self, Trap> {
|
|
40
|
+
Self::new(
|
|
41
|
+
preamble,
|
|
42
|
+
config.stdout_limit,
|
|
43
|
+
config.stderr_limit,
|
|
44
|
+
config.open_file_limit,
|
|
45
|
+
config.profile,
|
|
46
|
+
grants.iter().filter_map(to_mount).collect(),
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// A compile's context, at the strongest posture whatever the Sandbox
|
|
51
|
+
/// requested: compiling the Snippets needs no ambient authority, so
|
|
52
|
+
/// it is granted none.
|
|
53
|
+
pub(crate) fn for_compile(config: &Config, preamble: &[u8]) -> Result<Self, Trap> {
|
|
54
|
+
Self::new(
|
|
55
|
+
preamble,
|
|
56
|
+
config.stdout_limit,
|
|
57
|
+
config.stderr_limit,
|
|
58
|
+
config.open_file_limit,
|
|
59
|
+
Profile::Hermetic,
|
|
60
|
+
Vec::new(),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// The ABI version probe's context. Instantiating it runs the
|
|
65
|
+
/// artifact's start section, yet reading a version needs no ambient
|
|
66
|
+
/// authority and no output: the probe takes the strongest posture and
|
|
67
|
+
/// pipes sized to keep nothing.
|
|
68
|
+
pub(crate) fn for_probe() -> Result<Self, Trap> {
|
|
69
|
+
Self::new(&[], Some(0), Some(0), None, Profile::Hermetic, Vec::new())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// An uncapped output channel has no ceiling on the host: a guest
|
|
73
|
+
/// rewriting one buffer grows the pipe, not its own memory. The 16 MiB
|
|
74
|
+
/// cap keeps the Preamble's `u32` length prefix from wrapping.
|
|
75
|
+
fn new(
|
|
76
|
+
preamble: &[u8],
|
|
77
|
+
stdout_limit: Option<usize>,
|
|
78
|
+
stderr_limit: Option<usize>,
|
|
79
|
+
open_file_limit: Option<usize>,
|
|
80
|
+
profile: Profile,
|
|
81
|
+
mounts: Vec<Mount>,
|
|
82
|
+
) -> Result<Self, Trap> {
|
|
83
|
+
// The Preamble carries the same 16 MiB cap as a downcall request
|
|
84
|
+
// (`io::write_request`): past it, the length prefix would silently wrap.
|
|
85
|
+
guest_mem::checked_payload_len(preamble.len())
|
|
86
|
+
.map_err(|msg| Trap::Other(msg.to_string()))?;
|
|
87
|
+
|
|
88
|
+
let mut stdin_content: Vec<u8> = Vec::with_capacity(FRAME_LEN_SIZE + preamble.len());
|
|
89
|
+
stdin_content.extend_from_slice(&(preamble.len() as u32).to_be_bytes());
|
|
90
|
+
stdin_content.extend_from_slice(preamble);
|
|
91
|
+
|
|
92
|
+
let stdout = MemoryOutputPipe::new(capture::pipe_capacity(stdout_limit));
|
|
93
|
+
let stderr = MemoryOutputPipe::new(capture::pipe_capacity(stderr_limit));
|
|
94
|
+
|
|
95
|
+
// stdio lives in kobako's descriptor table, which answers every
|
|
96
|
+
// call that reads a descriptor (`crate::preview1`); the builder
|
|
97
|
+
// keeps only what the remaining imports read.
|
|
98
|
+
let descriptors = Descriptors::new(
|
|
99
|
+
MemoryInputPipe::new(stdin_content),
|
|
100
|
+
stdout.clone(),
|
|
101
|
+
stderr.clone(),
|
|
102
|
+
mounts,
|
|
103
|
+
open_file_limit,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
let mut builder = WasiCtxBuilder::new();
|
|
107
|
+
// The profile decides the ambient authority granted: the
|
|
108
|
+
// hermetic rung denies the preview1 time and entropy imports (see
|
|
109
|
+
// `ambient`), the permissive rung leaves the live WASI sources.
|
|
110
|
+
// Ambient filesystem, environment, and network stay absent on both rungs —
|
|
111
|
+
// the builder grants none unless asked. The exhaustive match makes
|
|
112
|
+
// a future ladder rung a compile error here, not authority granted
|
|
113
|
+
// silently.
|
|
114
|
+
let clocks = match profile {
|
|
115
|
+
Profile::Hermetic => {
|
|
116
|
+
builder.wall_clock(ambient::FrozenWallClock);
|
|
117
|
+
builder.monotonic_clock(ambient::FrozenMonotonicClock);
|
|
118
|
+
builder.secure_random(ambient::deterministic_rng());
|
|
119
|
+
Clocks::Frozen
|
|
120
|
+
}
|
|
121
|
+
Profile::Permissive => {
|
|
122
|
+
let monotonic = ambient::LiveMonotonicClock::new();
|
|
123
|
+
builder.monotonic_clock(monotonic);
|
|
124
|
+
Clocks::Live(monotonic)
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
Ok(Self {
|
|
129
|
+
ctx: builder.build_p1(),
|
|
130
|
+
descriptors,
|
|
131
|
+
clocks,
|
|
132
|
+
stdout,
|
|
133
|
+
stderr,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub(crate) fn ctx_mut(&mut self) -> &mut WasiP1Ctx {
|
|
138
|
+
&mut self.ctx
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pub(crate) fn descriptors(&self) -> &Descriptors {
|
|
142
|
+
&self.descriptors
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
pub(crate) fn descriptors_mut(&mut self) -> &mut Descriptors {
|
|
146
|
+
&mut self.descriptors
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
pub(crate) fn clocks(&self) -> Clocks {
|
|
150
|
+
self.clocks
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pub(crate) fn stdout_bytes(&self) -> Vec<u8> {
|
|
154
|
+
self.stdout.contents().to_vec()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub(crate) fn stderr_bytes(&self) -> Vec<u8> {
|
|
158
|
+
self.stderr.contents().to_vec()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// A Mount as this invocation presents it. A kind this driver does not
|
|
163
|
+
/// support has no descriptor to stand behind; `Driver::supports` keeps a
|
|
164
|
+
/// frontend from handing one over.
|
|
165
|
+
fn to_mount(grant: &Grant) -> Option<Mount> {
|
|
166
|
+
match grant {
|
|
167
|
+
Grant::Mount(mount) => Some(mount.for_invocation()),
|
|
168
|
+
_ => None,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#[cfg(test)]
|
|
173
|
+
mod tests {
|
|
174
|
+
//! Witness what an invocation's context grants at each profile rung:
|
|
175
|
+
//! `wasi:clocks` / `wasi:random` frozen under `Hermetic` and live
|
|
176
|
+
//! under `Permissive`, and no ambient filesystem, environment, or
|
|
177
|
+
//! socket under either.
|
|
178
|
+
use wasmtime::{AsContextMut, Module, Store};
|
|
179
|
+
|
|
180
|
+
use super::*;
|
|
181
|
+
use crate::cache::shared_engine;
|
|
182
|
+
use crate::imports;
|
|
183
|
+
use crate::invocation::Invocation;
|
|
184
|
+
|
|
185
|
+
/// Preview1 `errno` for a descriptor the context does not hold.
|
|
186
|
+
const ERRNO_BADF: i64 = 8;
|
|
187
|
+
|
|
188
|
+
/// Preview1 probe with one export per ambient source: `clock_ns`
|
|
189
|
+
/// reads the realtime clock, `random_word` reads eight entropy bytes,
|
|
190
|
+
/// `environ_count` counts environment variables, and `prestat_errno`
|
|
191
|
+
/// / `accept_errno` ask descriptor 3 for a directory and a
|
|
192
|
+
/// connection. Preview1 numbers preopens from 3, so nothing there
|
|
193
|
+
/// means no directory and no socket was granted.
|
|
194
|
+
const PROBE_WAT: &str = r#"
|
|
195
|
+
(module
|
|
196
|
+
(import "wasi_snapshot_preview1" "clock_time_get"
|
|
197
|
+
(func $clock (param i32 i64 i32) (result i32)))
|
|
198
|
+
(import "wasi_snapshot_preview1" "random_get"
|
|
199
|
+
(func $random (param i32 i32) (result i32)))
|
|
200
|
+
(import "wasi_snapshot_preview1" "environ_sizes_get"
|
|
201
|
+
(func $environ (param i32 i32) (result i32)))
|
|
202
|
+
(import "wasi_snapshot_preview1" "fd_prestat_get"
|
|
203
|
+
(func $prestat (param i32 i32) (result i32)))
|
|
204
|
+
(import "wasi_snapshot_preview1" "sock_accept"
|
|
205
|
+
(func $accept (param i32 i32 i32) (result i32)))
|
|
206
|
+
(memory (export "memory") 1)
|
|
207
|
+
(func (export "clock_ns") (result i64)
|
|
208
|
+
(drop (call $clock (i32.const 0) (i64.const 1) (i32.const 0)))
|
|
209
|
+
(i64.load (i32.const 0)))
|
|
210
|
+
(func (export "random_word") (result i64)
|
|
211
|
+
(drop (call $random (i32.const 8) (i32.const 8)))
|
|
212
|
+
(i64.load (i32.const 8)))
|
|
213
|
+
(func (export "environ_count") (result i64)
|
|
214
|
+
(drop (call $environ (i32.const 16) (i32.const 20)))
|
|
215
|
+
(i64.extend_i32_u (i32.load (i32.const 16))))
|
|
216
|
+
(func (export "prestat_errno") (result i64)
|
|
217
|
+
(i64.extend_i32_u (call $prestat (i32.const 3) (i32.const 24))))
|
|
218
|
+
(func (export "accept_errno") (result i64)
|
|
219
|
+
(i64.extend_i32_u (call $accept (i32.const 3) (i32.const 0) (i32.const 24)))))
|
|
220
|
+
"#;
|
|
221
|
+
|
|
222
|
+
/// Instantiate the probe over an invocation's WASI context built at
|
|
223
|
+
/// `profile` and return a reader for its exports.
|
|
224
|
+
fn probe(profile: Profile) -> impl FnMut(&str) -> i64 {
|
|
225
|
+
let engine = shared_engine().expect("shared engine must be constructible");
|
|
226
|
+
let config = Config {
|
|
227
|
+
timeout: None,
|
|
228
|
+
setup_timeout: None,
|
|
229
|
+
memory_limit: None,
|
|
230
|
+
stdout_limit: None,
|
|
231
|
+
stderr_limit: None,
|
|
232
|
+
open_file_limit: None,
|
|
233
|
+
profile,
|
|
234
|
+
};
|
|
235
|
+
let wasi = Wasi::for_invocation(&config, &[], &[]).expect("WASI context must build");
|
|
236
|
+
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
237
|
+
store.set_epoch_deadline(crate::trap::NO_TIMEOUT_EPOCH_DELTA);
|
|
238
|
+
|
|
239
|
+
let linker = imports::linker().expect("host imports must link");
|
|
240
|
+
let module = Module::new(engine, PROBE_WAT).expect("probe module must compile");
|
|
241
|
+
let instance = linker
|
|
242
|
+
.instantiate(&mut store, &module)
|
|
243
|
+
.expect("probe module must instantiate");
|
|
244
|
+
|
|
245
|
+
move |name| {
|
|
246
|
+
instance
|
|
247
|
+
.get_typed_func::<(), i64>(store.as_context_mut(), name)
|
|
248
|
+
.expect("probe export must resolve")
|
|
249
|
+
.call(store.as_context_mut(), ())
|
|
250
|
+
.expect("probe export must run")
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// Assert that the context built at `profile` grants no ambient
|
|
255
|
+
/// directory, environment variable, or socket.
|
|
256
|
+
fn assert_grants_no_ambient_resource(profile: Profile) {
|
|
257
|
+
let mut read = probe(profile);
|
|
258
|
+
assert_eq!(
|
|
259
|
+
read("prestat_errno"),
|
|
260
|
+
ERRNO_BADF,
|
|
261
|
+
"a {profile:?} guest's WASI context must preopen no directory"
|
|
262
|
+
);
|
|
263
|
+
assert_eq!(
|
|
264
|
+
read("environ_count"),
|
|
265
|
+
0,
|
|
266
|
+
"a {profile:?} guest's WASI context must carry no environment variable"
|
|
267
|
+
);
|
|
268
|
+
assert_eq!(
|
|
269
|
+
read("accept_errno"),
|
|
270
|
+
ERRNO_BADF,
|
|
271
|
+
"a {profile:?} guest's WASI context must hold no socket"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// @behavior RT-047 RT-048
|
|
276
|
+
#[test]
|
|
277
|
+
fn hermetic_denies_ambient_time_and_entropy() {
|
|
278
|
+
let mut read = probe(Profile::Hermetic);
|
|
279
|
+
assert_eq!(
|
|
280
|
+
read("clock_ns"),
|
|
281
|
+
0,
|
|
282
|
+
"a hermetic guest's wasi:clocks must read the Unix epoch, not host time"
|
|
283
|
+
);
|
|
284
|
+
assert_eq!(
|
|
285
|
+
read("random_word"),
|
|
286
|
+
0,
|
|
287
|
+
"a hermetic guest's wasi:random must yield the constant stream, not host entropy"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// @behavior RT-049 RT-050
|
|
292
|
+
#[test]
|
|
293
|
+
fn permissive_grants_live_ambient_time_and_entropy() {
|
|
294
|
+
let mut read = probe(Profile::Permissive);
|
|
295
|
+
assert!(
|
|
296
|
+
read("clock_ns") > 0,
|
|
297
|
+
"a permissive guest's wasi:clocks must read live host time"
|
|
298
|
+
);
|
|
299
|
+
assert_ne!(
|
|
300
|
+
read("random_word"),
|
|
301
|
+
0,
|
|
302
|
+
"a permissive guest's wasi:random must yield host entropy"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// @behavior RT-067
|
|
307
|
+
#[test]
|
|
308
|
+
fn hermetic_grants_no_ambient_resource() {
|
|
309
|
+
assert_grants_no_ambient_resource(Profile::Hermetic);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// @behavior RT-067
|
|
313
|
+
#[test]
|
|
314
|
+
fn permissive_grants_no_ambient_resource() {
|
|
315
|
+
assert_grants_no_ambient_resource(Profile::Permissive);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//! A compile reads no host time or entropy, whatever posture the Sandbox
|
|
2
|
+
//! requested. Compiling runs no script, so the bundled guest has no way to
|
|
3
|
+
//! show what its clock read; a fixture guest whose compile answers the
|
|
4
|
+
//! ambient bytes it read shows it instead.
|
|
5
|
+
|
|
6
|
+
use std::path::Path;
|
|
7
|
+
|
|
8
|
+
use kobako_runtime::{Profile, Runtime};
|
|
9
|
+
use kobako_transport::envelope::{Preamble, Snippet, Snippets};
|
|
10
|
+
use kobako_wasmtime::{Config, Driver};
|
|
11
|
+
|
|
12
|
+
const FIXTURE: &str = concat!(
|
|
13
|
+
env!("CARGO_MANIFEST_DIR"),
|
|
14
|
+
"/../../test/fixtures/minimal_compile_ambient.wat"
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// @behavior RT-072
|
|
18
|
+
#[test]
|
|
19
|
+
fn a_compile_reads_no_host_time_or_entropy_under_a_permissive_request() {
|
|
20
|
+
let mut config = Config::default();
|
|
21
|
+
config.timeout = None;
|
|
22
|
+
config.setup_timeout = None;
|
|
23
|
+
config.memory_limit = None;
|
|
24
|
+
config.stdout_limit = None;
|
|
25
|
+
config.stderr_limit = None;
|
|
26
|
+
config.open_file_limit = None;
|
|
27
|
+
config.profile = Profile::Permissive;
|
|
28
|
+
let driver = Driver::new(Path::new(FIXTURE), config).expect("the fixture guest must load");
|
|
29
|
+
let mut preamble = Preamble::default();
|
|
30
|
+
preamble.snippets = Snippets {
|
|
31
|
+
entries: vec![Snippet::Source {
|
|
32
|
+
name: "Only".into(),
|
|
33
|
+
body: "1".into(),
|
|
34
|
+
}],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
assert_eq!(
|
|
38
|
+
driver.compile(&preamble),
|
|
39
|
+
Some(vec![vec![0; 16]]),
|
|
40
|
+
"a permissive request through Driver#compile must read the Unix epoch and the \
|
|
41
|
+
constant stream, not host time and entropy"
|
|
42
|
+
);
|
|
43
|
+
}
|