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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
//! The guest's descriptor table: what each preview1 file descriptor
|
|
2
|
+
//! stands for within one invocation.
|
|
3
|
+
//!
|
|
4
|
+
//! kobako answers every preview1 call that reads this table, so the guest
|
|
5
|
+
//! sees one table and no call falls through to a second one that
|
|
6
|
+
//! disagrees. Numbering follows Linux's `alloc_fd`: the lowest closed number
|
|
7
|
+
//! is reused before a fresh one is taken.
|
|
8
|
+
|
|
9
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
10
|
+
use std::sync::Arc;
|
|
11
|
+
|
|
12
|
+
use kobako_runtime::filesystem::FilesystemError;
|
|
13
|
+
use kobako_runtime::mount::Mount;
|
|
14
|
+
use kobako_runtime::profile::Profile;
|
|
15
|
+
use kobako_runtime::security::{restrict_self, WalkGuard};
|
|
16
|
+
use kobako_runtime::vfs::{Cred, File};
|
|
17
|
+
|
|
18
|
+
/// What one descriptor stands for, as Linux's fd table holds a `struct file`.
|
|
19
|
+
pub(crate) struct Descriptor {
|
|
20
|
+
pub(crate) file: File,
|
|
21
|
+
/// The guest path a preopen names; a Mount's own root has one.
|
|
22
|
+
pub(crate) preopen_path: Option<String>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl Descriptor {
|
|
26
|
+
/// The descriptor of `file`, naming no preopen.
|
|
27
|
+
pub(crate) fn for_file(file: File) -> Self {
|
|
28
|
+
Self {
|
|
29
|
+
file,
|
|
30
|
+
preopen_path: None,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub(crate) struct Descriptors {
|
|
36
|
+
used: BTreeMap<u32, Descriptor>,
|
|
37
|
+
free: BTreeSet<u32>,
|
|
38
|
+
limit: Option<usize>,
|
|
39
|
+
/// Who the guest's calls are made for, as a task holds its `cred`.
|
|
40
|
+
cred: Arc<Cred>,
|
|
41
|
+
/// What a descriptor's call asks before it starts.
|
|
42
|
+
walk_guard: WalkGuard,
|
|
43
|
+
/// The isolation the guest runs under, which decides what it sees of
|
|
44
|
+
/// the times a Filesystem reports.
|
|
45
|
+
profile: Profile,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl Descriptors {
|
|
49
|
+
/// The table every invocation starts from: stdin, then the two
|
|
50
|
+
/// Channels, then one preopen per Mount from descriptor 3. It holds at
|
|
51
|
+
/// most `limit` descriptors, those included, as a process's
|
|
52
|
+
/// `RLIMIT_NOFILE` counts every one it holds. Each Mount's root opens as
|
|
53
|
+
/// `opener`, the Cred the Driver prepared, before its Policy applies.
|
|
54
|
+
pub(crate) fn new(
|
|
55
|
+
stdin: File,
|
|
56
|
+
stdout: File,
|
|
57
|
+
stderr: File,
|
|
58
|
+
mounts: Vec<Mount>,
|
|
59
|
+
limit: Option<usize>,
|
|
60
|
+
opener: Arc<Cred>,
|
|
61
|
+
profile: Profile,
|
|
62
|
+
) -> Result<Self, FilesystemError> {
|
|
63
|
+
let mut used = BTreeMap::from([
|
|
64
|
+
(0, Descriptor::for_file(stdin)),
|
|
65
|
+
(1, Descriptor::for_file(stdout)),
|
|
66
|
+
(2, Descriptor::for_file(stderr)),
|
|
67
|
+
]);
|
|
68
|
+
// Each Mount's root opens before its Policy applies, as a Linux task
|
|
69
|
+
// opens what it keeps and then calls `landlock_restrict_self`, so a
|
|
70
|
+
// Policy judges only what the guest asks for.
|
|
71
|
+
for (fd, mount) in (3..).zip(&mounts) {
|
|
72
|
+
used.insert(
|
|
73
|
+
fd,
|
|
74
|
+
Descriptor {
|
|
75
|
+
file: File::root(mount.mnt(), &opener)?,
|
|
76
|
+
preopen_path: Some(mount.at().to_string()),
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
let cred = restrict_self(&opener, &mounts);
|
|
81
|
+
let walk_guard = WalkGuard::new(&mounts);
|
|
82
|
+
Ok(Self {
|
|
83
|
+
used,
|
|
84
|
+
free: BTreeSet::new(),
|
|
85
|
+
limit,
|
|
86
|
+
cred,
|
|
87
|
+
walk_guard,
|
|
88
|
+
profile,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Who the guest's calls are made for.
|
|
93
|
+
pub(crate) fn cred(&self) -> &Arc<Cred> {
|
|
94
|
+
&self.cred
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// What every call through a descriptor asks first, so a Policy judges the
|
|
98
|
+
/// path it stands at.
|
|
99
|
+
pub(crate) fn walk_guard(&self) -> &WalkGuard {
|
|
100
|
+
&self.walk_guard
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// The isolation the guest runs under.
|
|
104
|
+
pub(crate) fn profile(&self) -> Profile {
|
|
105
|
+
self.profile
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pub(crate) fn get(&self, fd: u32) -> Option<&Descriptor> {
|
|
109
|
+
self.used.get(&fd)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub(crate) fn get_mut(&mut self, fd: u32) -> Option<&mut Descriptor> {
|
|
113
|
+
self.used.get_mut(&fd)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Take `fd` out of the table, freeing its number for reuse.
|
|
117
|
+
pub(crate) fn remove(&mut self, fd: u32) -> Option<Descriptor> {
|
|
118
|
+
let descriptor = self.used.remove(&fd)?;
|
|
119
|
+
self.free.insert(fd);
|
|
120
|
+
Some(descriptor)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Place `descriptor` at `fd`, a number the caller has just freed or
|
|
124
|
+
/// `lowest_unused` found, as Linux's `fd_install` does.
|
|
125
|
+
pub(crate) fn place(&mut self, fd: u32, descriptor: Descriptor) {
|
|
126
|
+
self.free.remove(&fd);
|
|
127
|
+
self.used.insert(fd, descriptor);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// The number the next descriptor takes, the lowest freed one or the one
|
|
131
|
+
/// past the highest in use, as Linux's `get_unused_fd_flags` finds it;
|
|
132
|
+
/// `None` when the table holds as many as it may. Nothing is placed
|
|
133
|
+
/// until `place`, as `fd_install` fills what was found.
|
|
134
|
+
pub(crate) fn lowest_unused(&self) -> Option<u32> {
|
|
135
|
+
if self.limit.is_some_and(|limit| self.used.len() >= limit) {
|
|
136
|
+
return None;
|
|
137
|
+
}
|
|
138
|
+
match self.free.first() {
|
|
139
|
+
Some(fd) => Some(*fd),
|
|
140
|
+
None => match self.used.last_key_value() {
|
|
141
|
+
Some((last, _)) => last.checked_add(1),
|
|
142
|
+
None => Some(0),
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Add `descriptor` under the number `lowest_unused` finds; `None` when
|
|
148
|
+
/// the table is full.
|
|
149
|
+
#[cfg(test)]
|
|
150
|
+
pub(crate) fn push(&mut self, descriptor: Descriptor) -> Option<u32> {
|
|
151
|
+
let fd = self.lowest_unused()?;
|
|
152
|
+
self.place(fd, descriptor);
|
|
153
|
+
Some(fd)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#[cfg(test)]
|
|
158
|
+
mod tests {
|
|
159
|
+
use super::*;
|
|
160
|
+
use kobako_runtime::filesystem::{create_pipe_files, PipeInodeInfo};
|
|
161
|
+
use kobako_runtime::security::prepare_creds_within;
|
|
162
|
+
|
|
163
|
+
fn pipe_end() -> File {
|
|
164
|
+
create_pipe_files(&PipeInodeInfo::new(1), &prepare_creds_within(&[], None)).1
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// @behavior RT-189
|
|
168
|
+
#[test]
|
|
169
|
+
fn a_new_descriptor_takes_the_lowest_closed_number() {
|
|
170
|
+
let mut table = Descriptors::new(
|
|
171
|
+
pipe_end(),
|
|
172
|
+
pipe_end(),
|
|
173
|
+
pipe_end(),
|
|
174
|
+
Vec::new(),
|
|
175
|
+
None,
|
|
176
|
+
prepare_creds_within(&[], None),
|
|
177
|
+
Profile::Hermetic,
|
|
178
|
+
)
|
|
179
|
+
.unwrap_or_else(|_| panic!("a table holding no Mount must make"));
|
|
180
|
+
let mut open = || {
|
|
181
|
+
table
|
|
182
|
+
.push(Descriptor::for_file(pipe_end()))
|
|
183
|
+
.expect("a table with no limit takes every descriptor")
|
|
184
|
+
};
|
|
185
|
+
let numbers = [open(), open(), open()];
|
|
186
|
+
assert_eq!(numbers, [3, 4, 5], "descriptors must number from 3");
|
|
187
|
+
table.remove(3);
|
|
188
|
+
table.remove(4);
|
|
189
|
+
assert_eq!(
|
|
190
|
+
(
|
|
191
|
+
table.push(Descriptor::for_file(pipe_end())),
|
|
192
|
+
table.push(Descriptor::for_file(pipe_end())),
|
|
193
|
+
table.push(Descriptor::for_file(pipe_end())),
|
|
194
|
+
),
|
|
195
|
+
(Some(3), Some(4), Some(6)),
|
|
196
|
+
"path_open's descriptors must take the lowest closed number first, as Linux's alloc_fd \
|
|
197
|
+
does, and then the one past the highest"
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -9,29 +9,32 @@
|
|
|
9
9
|
|
|
10
10
|
use std::path::Path;
|
|
11
11
|
use std::sync::Arc;
|
|
12
|
-
use std::time::Instant;
|
|
13
12
|
|
|
14
13
|
use wasmtime::{
|
|
15
14
|
AsContextMut, InstancePre as WtInstancePre, ResourceLimiter, Store as WtStore, TypedFunc,
|
|
16
15
|
};
|
|
17
16
|
|
|
18
|
-
use crate::cache::
|
|
17
|
+
use crate::cache::load_engine;
|
|
19
18
|
use crate::config::Config;
|
|
20
19
|
use crate::exports::{self, Exports};
|
|
20
|
+
use crate::guest::Guest;
|
|
21
21
|
use crate::invocation::Invocation;
|
|
22
|
-
use crate::
|
|
22
|
+
use crate::wasi::Wasi;
|
|
23
|
+
use crate::{instance_pre, trap};
|
|
23
24
|
use kobako_runtime::dispatch::DispatchHandler;
|
|
25
|
+
use kobako_runtime::endpoint;
|
|
24
26
|
use kobako_runtime::error::{InvokeError, SetupError, Trap};
|
|
27
|
+
use kobako_runtime::grant::{Grant, GrantKind};
|
|
25
28
|
use kobako_runtime::profile::Profile;
|
|
26
29
|
use kobako_runtime::runtime::{Entry, Runtime as ContractRuntime};
|
|
27
30
|
use kobako_runtime::snapshot::{Capture, Completion, Snapshot, Usage};
|
|
28
|
-
use kobako_transport::envelope::
|
|
31
|
+
use kobako_transport::envelope::Preamble;
|
|
29
32
|
|
|
30
33
|
/// The wasmtime execution unit behind one sandbox runtime.
|
|
31
34
|
pub struct Driver {
|
|
32
35
|
// Pre-linked instantiation template (import wiring, type checks,
|
|
33
36
|
// and the ABI version check done once in
|
|
34
|
-
// `instance_pre::
|
|
37
|
+
// `instance_pre::load_instance_pre`). Every invocation
|
|
35
38
|
// instantiates a fresh instance from it and discards the whole
|
|
36
39
|
// Store afterwards — the per-invocation instance discipline.
|
|
37
40
|
instance_pre: WtInstancePre<Invocation>,
|
|
@@ -45,15 +48,22 @@ impl Driver {
|
|
|
45
48
|
/// neither leaves the driver.
|
|
46
49
|
pub fn new(path: &Path, config: Config) -> Result<Self, SetupError> {
|
|
47
50
|
Ok(Self {
|
|
48
|
-
instance_pre: instance_pre::
|
|
51
|
+
instance_pre: instance_pre::load_instance_pre(path, config.setup_timeout)?,
|
|
49
52
|
config,
|
|
50
53
|
})
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
/// The deadline and the `wall_time` bracket open with the Store, so
|
|
57
|
+
/// instantiating the guest and writing its request count against the
|
|
58
|
+
/// `timeout` as the export call does.
|
|
59
|
+
fn new_store(&self, wasi: Wasi) -> Result<WtStore<Invocation>, SetupError> {
|
|
60
|
+
let mut store = WtStore::new(
|
|
61
|
+
load_engine()?,
|
|
62
|
+
Invocation::new(self.config.memory_limit, wasi),
|
|
63
|
+
);
|
|
55
64
|
store.limiter(|state: &mut Invocation| -> &mut dyn ResourceLimiter { state.limiter_mut() });
|
|
56
|
-
store.
|
|
65
|
+
trap::arm_deadline(&mut store, self.config.timeout);
|
|
66
|
+
store.data_mut().start_wall_clock();
|
|
57
67
|
Ok(store)
|
|
58
68
|
}
|
|
59
69
|
|
|
@@ -63,7 +73,11 @@ impl Driver {
|
|
|
63
73
|
let instance = self
|
|
64
74
|
.instance_pre
|
|
65
75
|
.instantiate(store.as_context_mut())
|
|
66
|
-
.map_err(|e|
|
|
76
|
+
.map_err(|e| {
|
|
77
|
+
trap::cap_trap(&e).unwrap_or_else(|| {
|
|
78
|
+
Trap::Other(format!("failed to instantiate the Sandbox runtime: {e}"))
|
|
79
|
+
})
|
|
80
|
+
})?;
|
|
67
81
|
let exports = Exports::resolve(&instance, store.as_context_mut());
|
|
68
82
|
store.data_mut().set_exports(exports.clone());
|
|
69
83
|
Ok(exports)
|
|
@@ -83,58 +97,46 @@ impl Driver {
|
|
|
83
97
|
Params: wasmtime::WasmParams,
|
|
84
98
|
Results: wasmtime::WasmResults,
|
|
85
99
|
{
|
|
86
|
-
self.
|
|
87
|
-
|
|
100
|
+
self.arm_memory_cap(store, exports);
|
|
101
|
+
// A wait for a lock the call meets ends at the same deadline the guest's
|
|
102
|
+
// code does, so another Sandbox holding it cannot keep this one past it.
|
|
103
|
+
let deadline = store.data().deadline();
|
|
104
|
+
let result = kobako_runtime::sched::run_task(deadline, || {
|
|
105
|
+
export.call(store.as_context_mut(), params)
|
|
106
|
+
});
|
|
88
107
|
disarm_caps(store);
|
|
89
108
|
result
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
/// The pre-initialized image's allocation is folded into the memory
|
|
93
|
-
/// baseline rather than the budget
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
Some(timeout) => {
|
|
97
|
-
let deadline = Instant::now() + timeout;
|
|
98
|
-
store.data_mut().set_deadline(Some(deadline));
|
|
99
|
-
store.set_epoch_deadline(1);
|
|
100
|
-
}
|
|
101
|
-
None => {
|
|
102
|
-
store.data_mut().set_deadline(None);
|
|
103
|
-
store.set_epoch_deadline(trap::NO_TIMEOUT_EPOCH_DELTA);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
112
|
+
/// baseline rather than the budget, so the cap arms only once the
|
|
113
|
+
/// image is in place.
|
|
114
|
+
fn arm_memory_cap(&self, store: &mut WtStore<Invocation>, exports: &Exports) {
|
|
106
115
|
let baseline = match exports.memory {
|
|
107
116
|
Some(m) => m.data_size(store.as_context_mut()),
|
|
108
117
|
None => 0,
|
|
109
118
|
};
|
|
110
119
|
store.data_mut().arm_memory_cap(baseline);
|
|
111
|
-
store.data_mut().start_wall_clock();
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
/// Built the same for every `completion`: captures and usage must
|
|
115
123
|
/// survive a trap just as they do an outcome.
|
|
116
124
|
fn build_snapshot(&self, store: &WtStore<Invocation>, completion: Completion) -> Snapshot {
|
|
117
125
|
let data = store.data();
|
|
118
|
-
let usage = Usage
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
let mut usage = Usage::default();
|
|
127
|
+
usage.wall_time = data.wall_time().as_secs_f64();
|
|
128
|
+
usage.memory_peak = data.memory_peak();
|
|
129
|
+
let clip = |bytes, limit| {
|
|
130
|
+
let mut capture = Capture::default();
|
|
131
|
+
(capture.bytes, capture.truncated) = endpoint::clip_capture(bytes, limit);
|
|
132
|
+
capture
|
|
121
133
|
};
|
|
122
|
-
|
|
123
|
-
capture::clip_capture(data.stdout_bytes(), self.config.stdout_limit);
|
|
124
|
-
let (stderr_bytes, stderr_truncated) =
|
|
125
|
-
capture::clip_capture(data.stderr_bytes(), self.config.stderr_limit);
|
|
126
|
-
Snapshot {
|
|
134
|
+
Snapshot::new(
|
|
127
135
|
completion,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
truncated: stdout_truncated,
|
|
131
|
-
},
|
|
132
|
-
stderr: Capture {
|
|
133
|
-
bytes: stderr_bytes,
|
|
134
|
-
truncated: stderr_truncated,
|
|
135
|
-
},
|
|
136
|
+
clip(data.wasi().stdout_bytes(), self.config.stdout_limit),
|
|
137
|
+
clip(data.wasi().stderr_bytes(), self.config.stderr_limit),
|
|
136
138
|
usage,
|
|
137
|
-
|
|
139
|
+
)
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
|
|
@@ -148,135 +150,68 @@ impl ContractRuntime for Driver {
|
|
|
148
150
|
entry: Entry<'_>,
|
|
149
151
|
preamble: &Preamble,
|
|
150
152
|
handler: Option<Arc<dyn DispatchHandler>>,
|
|
153
|
+
grants: &[Grant],
|
|
151
154
|
) -> Result<Snapshot, InvokeError> {
|
|
152
|
-
let
|
|
155
|
+
let stdin =
|
|
156
|
+
endpoint::stdin_content(preamble).map_err(|msg| Trap::Other(msg.to_string()))?;
|
|
157
|
+
let wasi = Wasi::for_invocation(&self.config, stdin, grants)?;
|
|
158
|
+
let mut store = self.new_store(wasi)?;
|
|
153
159
|
if let Some(handler) = handler {
|
|
154
160
|
store.data_mut().bind_on_dispatch(handler);
|
|
155
161
|
}
|
|
156
|
-
io::install_wasi(&mut store, &self.config, &preamble.encode())?;
|
|
157
162
|
let exports = self.instantiate(&mut store)?;
|
|
158
163
|
let downcall = exports::require(exports.downcall.as_ref())?;
|
|
159
|
-
let request =
|
|
160
|
-
|
|
161
|
-
Entry::Run {
|
|
162
|
-
entrypoint,
|
|
163
|
-
payload,
|
|
164
|
-
} => Downcall::Run(Run {
|
|
165
|
-
entrypoint: entrypoint.to_owned(),
|
|
166
|
-
payload,
|
|
167
|
-
}),
|
|
168
|
-
}
|
|
169
|
-
.encode();
|
|
170
|
-
let (ptr, len) = io::write_request(&mut store, &exports, &request)?;
|
|
164
|
+
let request = endpoint::request_of(entry);
|
|
165
|
+
let (ptr, len) = endpoint::write_request(&mut guest_of(&mut store), &request)?;
|
|
171
166
|
let called = self.call_with_caps(&mut store, &exports, downcall, (ptr, len));
|
|
172
167
|
let completion = match called {
|
|
173
|
-
Ok(packed) => match
|
|
168
|
+
Ok(packed) => match endpoint::read_outcome(&mut guest_of(&mut store), packed) {
|
|
174
169
|
Ok(outcome) => Completion::Outcome(outcome),
|
|
175
170
|
Err(t) => Completion::Trap(t),
|
|
176
171
|
},
|
|
177
|
-
Err(e) => Completion::Trap(trap::
|
|
172
|
+
Err(e) => Completion::Trap(trap::to_trap(e)),
|
|
178
173
|
};
|
|
179
174
|
Ok(self.build_snapshot(&store, completion))
|
|
180
175
|
}
|
|
181
176
|
|
|
182
177
|
/// Exactly the rung `Config` requested. `Hermetic` freezes ambient time
|
|
183
|
-
/// and entropy; both rungs wire no filesystem, environment, or network,
|
|
178
|
+
/// and entropy; both rungs wire no ambient filesystem, environment, or network,
|
|
184
179
|
/// and the linker adds only `__kobako_upcall` beyond that WASI surface.
|
|
185
180
|
fn profile(&self) -> Profile {
|
|
186
181
|
self.config.profile
|
|
187
182
|
}
|
|
188
183
|
|
|
184
|
+
/// A Mount becomes a preopen of kobako's own descriptor table, which
|
|
185
|
+
/// answers every preview1 call beneath it.
|
|
186
|
+
fn supports(&self, kind: GrantKind) -> bool {
|
|
187
|
+
matches!(kind, GrantKind::Mount)
|
|
188
|
+
}
|
|
189
|
+
|
|
189
190
|
/// Bounded by the same caps as an invocation, on an instance of its
|
|
190
191
|
/// own that no script ever runs on, so what it answers carries
|
|
191
192
|
/// nothing a script could have written.
|
|
192
193
|
fn compile(&self, preamble: &Preamble) -> Option<Vec<Vec<u8>>> {
|
|
193
|
-
let
|
|
194
|
-
|
|
194
|
+
let wasi = Wasi::for_compile(&self.config, endpoint::stdin_content(preamble).ok()?).ok()?;
|
|
195
|
+
let mut store = self.new_store(wasi).ok()?;
|
|
195
196
|
let exports = self.instantiate(&mut store).ok()?;
|
|
196
197
|
let downcall = exports::require(exports.downcall.as_ref()).ok()?;
|
|
197
198
|
let (ptr, len) =
|
|
198
|
-
|
|
199
|
+
endpoint::write_request(&mut guest_of(&mut store), &endpoint::compile_request())
|
|
200
|
+
.ok()?;
|
|
199
201
|
let packed = self
|
|
200
202
|
.call_with_caps(&mut store, &exports, downcall, (ptr, len))
|
|
201
203
|
.ok()?;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.filter(|bytecodes| replay_fits(preamble, bytecodes)),
|
|
205
|
-
CompileReply::Error(_) | CompileReply::Unsupported(_) => None,
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/// Each invocation sends the Preamble with every bytecode in place of its
|
|
211
|
-
/// source, so an answer that would carry it past the payload cap would trap
|
|
212
|
-
/// every invocation the source alone fits.
|
|
213
|
-
fn replay_fits(preamble: &Preamble, bytecodes: &[Vec<u8>]) -> bool {
|
|
214
|
-
let mut replay = preamble.clone();
|
|
215
|
-
let sources = replay
|
|
216
|
-
.snippets
|
|
217
|
-
.entries
|
|
218
|
-
.iter_mut()
|
|
219
|
-
.filter(|entry| matches!(entry, Snippet::Source { .. }));
|
|
220
|
-
for (entry, body) in sources.zip(bytecodes) {
|
|
221
|
-
*entry = Snippet::Bytecode { body: body.clone() };
|
|
204
|
+
let reply = endpoint::read_compile_reply(&mut guest_of(&mut store), packed).ok()?;
|
|
205
|
+
endpoint::compiled_bytecodes(preamble, reply)
|
|
222
206
|
}
|
|
223
|
-
guest_mem::checked_payload_len(replay.encode().len()).is_ok()
|
|
224
207
|
}
|
|
225
208
|
|
|
226
|
-
///
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let sources = preamble
|
|
230
|
-
.snippets
|
|
231
|
-
.entries
|
|
232
|
-
.iter()
|
|
233
|
-
.filter(|entry| matches!(entry, Snippet::Source { .. }))
|
|
234
|
-
.count();
|
|
235
|
-
(bytecodes.len() == sources).then_some(bytecodes)
|
|
209
|
+
/// The instance the runtime's host ABI endpoint drives, through `store`.
|
|
210
|
+
fn guest_of(store: &mut WtStore<Invocation>) -> Guest<'_> {
|
|
211
|
+
Guest(store.as_context_mut())
|
|
236
212
|
}
|
|
237
213
|
|
|
238
214
|
fn disarm_caps(store: &mut WtStore<Invocation>) {
|
|
239
215
|
store.data_mut().stop_wall_clock();
|
|
240
216
|
store.data_mut().disarm_memory_cap();
|
|
241
217
|
}
|
|
242
|
-
|
|
243
|
-
#[cfg(test)]
|
|
244
|
-
mod tests {
|
|
245
|
-
use kobako_transport::envelope::Snippets;
|
|
246
|
-
|
|
247
|
-
use super::*;
|
|
248
|
-
|
|
249
|
-
/// One source between two bytecode snippets: a compile answers one
|
|
250
|
-
/// bytecode for it.
|
|
251
|
-
fn one_source() -> Preamble {
|
|
252
|
-
let mut preamble = Preamble::default();
|
|
253
|
-
preamble.snippets = Snippets {
|
|
254
|
-
entries: vec![
|
|
255
|
-
Snippet::Bytecode { body: vec![0xaa] },
|
|
256
|
-
Snippet::Source {
|
|
257
|
-
name: "Only".into(),
|
|
258
|
-
body: "1".into(),
|
|
259
|
-
},
|
|
260
|
-
Snippet::Bytecode { body: vec![0xbb] },
|
|
261
|
-
],
|
|
262
|
-
};
|
|
263
|
-
preamble
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// @behavior WE-089
|
|
267
|
-
#[test]
|
|
268
|
-
fn a_compiled_answer_of_another_length_leaves_nothing_compiled() {
|
|
269
|
-
let preamble = one_source();
|
|
270
|
-
|
|
271
|
-
assert_eq!(
|
|
272
|
-
[
|
|
273
|
-
one_per_source(&preamble, Vec::new()),
|
|
274
|
-
one_per_source(&preamble, vec![vec![0x01], vec![0x02]]),
|
|
275
|
-
one_per_source(&preamble, vec![vec![0x01]]),
|
|
276
|
-
],
|
|
277
|
-
[None, None, Some(vec![vec![0x01]])],
|
|
278
|
-
"a compiled answer through Driver#compile must be kept only when it carries one \
|
|
279
|
-
bytecode per source snippet"
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
@@ -10,19 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
use wasmtime::{AsContextMut, Instance as WtInstance, Memory, TypedFunc};
|
|
12
12
|
|
|
13
|
-
use
|
|
13
|
+
use kobako_runtime::endpoint::RUNTIME_INCOMPATIBLE;
|
|
14
14
|
use kobako_runtime::error::Trap;
|
|
15
15
|
|
|
16
|
-
/// For a required guest export that is absent or mistyped, wherever it is
|
|
17
|
-
/// looked up. Names neither the export nor a frontend's artifact path;
|
|
18
|
-
/// what a caller can do is rebuild the runtime.
|
|
19
|
-
pub(crate) const RUNTIME_INCOMPATIBLE: &str =
|
|
20
|
-
"the Sandbox runtime is incompatible; rebuild it against the kobako version in use";
|
|
21
|
-
|
|
22
16
|
/// The resolved host-driven export handles. Each is `Option` because test
|
|
23
17
|
/// fixtures (a minimal "ping" module) need not provide them; real
|
|
24
|
-
/// `kobako.wasm` always does, and the run path surfaces a `Trap`
|
|
25
|
-
///
|
|
18
|
+
/// `kobako.wasm` always does, and the run path surfaces a `Trap` when a
|
|
19
|
+
/// handle is `None`.
|
|
26
20
|
///
|
|
27
21
|
/// The handles are indices into the owning Store, not borrows of the
|
|
28
22
|
/// `Instance` — they stay valid for the Store's lifetime, which is why
|
|
@@ -49,11 +43,6 @@ impl Exports {
|
|
|
49
43
|
memory: instance.get_memory(&mut ctx, "memory"),
|
|
50
44
|
}
|
|
51
45
|
}
|
|
52
|
-
|
|
53
|
-
pub(crate) fn require_memory(&self) -> Result<Memory, Trap> {
|
|
54
|
-
self.memory
|
|
55
|
-
.ok_or_else(|| Trap::Other(SANDBOX_RUNTIME_NOT_KOBAKO.to_string()))
|
|
56
|
-
}
|
|
57
46
|
}
|
|
58
47
|
|
|
59
48
|
pub(crate) fn require<Params, Results>(
|
|
@@ -77,7 +66,7 @@ mod tests {
|
|
|
77
66
|
|
|
78
67
|
use wasmtime::ExternType;
|
|
79
68
|
|
|
80
|
-
use crate::cache::
|
|
69
|
+
use crate::cache::load_module;
|
|
81
70
|
|
|
82
71
|
const WASM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../data/kobako.wasm");
|
|
83
72
|
|
|
@@ -98,7 +87,7 @@ mod tests {
|
|
|
98
87
|
);
|
|
99
88
|
return;
|
|
100
89
|
}
|
|
101
|
-
let module =
|
|
90
|
+
let module = load_module(path).expect("the Guest Binary must load");
|
|
102
91
|
|
|
103
92
|
let mut functions: Vec<&str> = module
|
|
104
93
|
.exports()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//! One wasmtime instance as the runtime's host ABI endpoint drives it, from the
|
|
2
|
+
//! invocation body's `Store` and from a host import's `Caller` alike.
|
|
3
|
+
|
|
4
|
+
use kobako_runtime::endpoint::{Calls, GuestInstance};
|
|
5
|
+
use kobako_runtime::error::Trap;
|
|
6
|
+
use wasmtime::StoreContextMut;
|
|
7
|
+
|
|
8
|
+
use crate::invocation::Invocation;
|
|
9
|
+
use crate::trap;
|
|
10
|
+
|
|
11
|
+
pub(crate) struct Guest<'a>(pub(crate) StoreContextMut<'a, Invocation>);
|
|
12
|
+
|
|
13
|
+
impl GuestInstance for Guest<'_> {
|
|
14
|
+
type Fault = wasmtime::Error;
|
|
15
|
+
|
|
16
|
+
fn calls(&mut self) -> &mut Calls<wasmtime::Error> {
|
|
17
|
+
self.0.data_mut().calls_mut()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fn memory(&mut self) -> Option<&mut [u8]> {
|
|
21
|
+
let memory = self.0.data().exports()?.memory?;
|
|
22
|
+
Some(memory.data_mut(&mut self.0))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn alloc(&mut self, len: u32) -> Option<wasmtime::Result<u32>> {
|
|
26
|
+
let alloc = self.0.data().exports()?.alloc?;
|
|
27
|
+
Some(alloc.call(&mut self.0, len))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn downcall(&mut self, ptr: i32, len: i32) -> Option<wasmtime::Result<u64>> {
|
|
31
|
+
let downcall = self.0.data().exports()?.downcall?;
|
|
32
|
+
Some(downcall.call(&mut self.0, (ptr, len)))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn cap_trap(fault: &wasmtime::Error) -> Option<Trap> {
|
|
36
|
+
trap::cap_trap(fault)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//! The host imports a Guest Binary resolves: the WASI preview1 set and
|
|
2
|
+
//! the wire ABI's `__kobako_upcall`.
|
|
3
|
+
//!
|
|
4
|
+
//! Both closures read all their state from the `Invocation` inside the
|
|
5
|
+
//! calling Store, never from a Runtime, so the wiring is the same for
|
|
6
|
+
//! every Sandbox and `crate::instance_pre` can cache what it resolves.
|
|
7
|
+
|
|
8
|
+
use wasmtime::{AsContextMut, Caller, Linker};
|
|
9
|
+
use wasmtime_wasi::p1;
|
|
10
|
+
|
|
11
|
+
use crate::cache::load_engine;
|
|
12
|
+
use crate::guest::Guest;
|
|
13
|
+
use crate::invocation::Invocation;
|
|
14
|
+
use crate::preview1;
|
|
15
|
+
use kobako_runtime::endpoint::{self, UpcallEnd, ABANDONED};
|
|
16
|
+
use kobako_runtime::error::SetupError;
|
|
17
|
+
|
|
18
|
+
pub(crate) fn linker() -> Result<Linker<Invocation>, SetupError> {
|
|
19
|
+
let mut linker: Linker<Invocation> = Linker::new(load_engine()?);
|
|
20
|
+
|
|
21
|
+
// Wire the wasmtime-wasi preview1 WASI imports against each
|
|
22
|
+
// invocation's own `Wasi` context, then answer every call that reads
|
|
23
|
+
// a descriptor from kobako's own table, which routes guest fd 1/2 to
|
|
24
|
+
// its capture pipes.
|
|
25
|
+
p1::add_to_linker_sync(&mut linker, |state: &mut Invocation| {
|
|
26
|
+
state.wasi_mut().ctx_mut()
|
|
27
|
+
})
|
|
28
|
+
.and_then(|_| preview1::shadow(&mut linker))
|
|
29
|
+
.map_err(|e| SetupError::Dead(format!("failed to set up the WASI runtime: {e}")))?;
|
|
30
|
+
|
|
31
|
+
// `__kobako_upcall`, `(ptr: i32, len: i32) -> i64` in the ABI;
|
|
32
|
+
// `endpoint::handle` owns what it answers.
|
|
33
|
+
linker
|
|
34
|
+
.func_wrap(
|
|
35
|
+
"env",
|
|
36
|
+
"__kobako_upcall",
|
|
37
|
+
|mut caller: Caller<'_, Invocation>,
|
|
38
|
+
req_ptr: i32,
|
|
39
|
+
req_len: i32|
|
|
40
|
+
-> wasmtime::Result<i64> {
|
|
41
|
+
let mut guest = Guest(caller.as_context_mut());
|
|
42
|
+
endpoint::handle(&mut guest, req_ptr, req_len).map_err(|end| match end {
|
|
43
|
+
UpcallEnd::Fault(trap) => trap,
|
|
44
|
+
UpcallEnd::Abandoned => wasmtime::Error::msg(ABANDONED),
|
|
45
|
+
UpcallEnd::Unwritable(reason) => wasmtime::Error::msg(reason),
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
.map_err(|e| SetupError::Dead(format!("failed to set up the host callback bridge: {e}")))?;
|
|
50
|
+
|
|
51
|
+
Ok(linker)
|
|
52
|
+
}
|