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,205 @@
|
|
|
1
|
+
//! The guest's linear memory as one preview1 call sees it: little-endian
|
|
2
|
+
//! fields at guest addresses. An address outside the memory, or one not
|
|
3
|
+
//! aligned for what it points at, traps, as preview1 requires.
|
|
4
|
+
|
|
5
|
+
use std::cell::Cell;
|
|
6
|
+
use std::ops::Range;
|
|
7
|
+
|
|
8
|
+
use super::beneath::PATH_MAX;
|
|
9
|
+
use super::errno::{Answer, Errno, Failure};
|
|
10
|
+
|
|
11
|
+
fn out_of_bounds() -> Failure {
|
|
12
|
+
Failure::trap("pointer out of bounds of guest memory")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// The most of the guest's memory one call may have the host copy or
|
|
16
|
+
/// walk, as wasmtime's default hostcall fuel bounds a preview1 call; past
|
|
17
|
+
/// it the call answers NOMEM.
|
|
18
|
+
const HOSTCALL_FUEL: u64 = 128 << 20;
|
|
19
|
+
|
|
20
|
+
pub(crate) struct GuestMemory<'a> {
|
|
21
|
+
bytes: &'a mut [u8],
|
|
22
|
+
fuel: Cell<u64>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl<'a> GuestMemory<'a> {
|
|
26
|
+
pub(crate) fn new(bytes: &'a mut [u8]) -> Self {
|
|
27
|
+
Self {
|
|
28
|
+
bytes,
|
|
29
|
+
fuel: Cell::new(HOSTCALL_FUEL),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Spend `count` elements of `size` bytes of this call's fuel, as
|
|
34
|
+
/// wasmtime-wasi spends it before it copies an array.
|
|
35
|
+
pub(crate) fn consume(&self, count: u32, size: u32) -> Answer {
|
|
36
|
+
let cost = u64::from(count) * u64::from(size);
|
|
37
|
+
let left = self.fuel.get().checked_sub(cost).ok_or(Errno::Nomem)?;
|
|
38
|
+
self.fuel.set(left);
|
|
39
|
+
Ok(())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn range(&self, ptr: u32, len: u32) -> Answer<Range<usize>> {
|
|
43
|
+
let start = ptr as usize;
|
|
44
|
+
match start.checked_add(len as usize) {
|
|
45
|
+
Some(end) if end <= self.bytes.len() => Ok(start..end),
|
|
46
|
+
_ => Err(out_of_bounds()),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Hold `ptr` to the alignment of the struct or integer it points at.
|
|
51
|
+
pub(crate) fn check_aligned(ptr: u32, align: u32) -> Answer {
|
|
52
|
+
if !ptr.is_multiple_of(align) {
|
|
53
|
+
return Err(Failure::trap("pointer not aligned for what it points at"));
|
|
54
|
+
}
|
|
55
|
+
Ok(())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// The address of element `index` of an array of `size`-byte elements
|
|
59
|
+
/// at `base`.
|
|
60
|
+
pub(crate) fn element(base: u32, index: u32, size: u32) -> Answer<u32> {
|
|
61
|
+
Self::field(base, index.checked_mul(size).ok_or_else(out_of_bounds)?)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// The address `offset` bytes past `base`, which traps rather than
|
|
65
|
+
/// wrapping past the end of the address space.
|
|
66
|
+
pub(crate) fn field(base: u32, offset: u32) -> Answer<u32> {
|
|
67
|
+
base.checked_add(offset).ok_or_else(out_of_bounds)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub(crate) fn bytes(&self, ptr: u32, len: u32) -> Answer<&[u8]> {
|
|
71
|
+
let range = self.range(ptr, len)?;
|
|
72
|
+
Ok(&self.bytes[range])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub(crate) fn bytes_mut(&mut self, ptr: u32, len: u32) -> Answer<&mut [u8]> {
|
|
76
|
+
let range = self.range(ptr, len)?;
|
|
77
|
+
Ok(&mut self.bytes[range])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn read<const N: usize>(&self, ptr: u32) -> Answer<[u8; N]> {
|
|
81
|
+
Self::check_aligned(ptr, N as u32)?;
|
|
82
|
+
let mut out = [0; N];
|
|
83
|
+
out.copy_from_slice(self.bytes(ptr, N as u32)?);
|
|
84
|
+
Ok(out)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub(crate) fn read_u8(&self, ptr: u32) -> Answer<u8> {
|
|
88
|
+
self.read::<1>(ptr).map(|b| b[0])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub(crate) fn read_u16(&self, ptr: u32) -> Answer<u16> {
|
|
92
|
+
self.read(ptr).map(u16::from_le_bytes)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub(crate) fn read_u32(&self, ptr: u32) -> Answer<u32> {
|
|
96
|
+
self.read(ptr).map(u32::from_le_bytes)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pub(crate) fn read_u64(&self, ptr: u32) -> Answer<u64> {
|
|
100
|
+
self.read(ptr).map(u64::from_le_bytes)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Copy `bytes` into guest memory at `ptr`, which needs no alignment.
|
|
104
|
+
pub(crate) fn write(&mut self, ptr: u32, bytes: &[u8]) -> Answer {
|
|
105
|
+
self.bytes_mut(ptr, bytes.len() as u32)?
|
|
106
|
+
.copy_from_slice(bytes);
|
|
107
|
+
Ok(())
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn write_aligned<const N: usize>(&mut self, ptr: u32, bytes: [u8; N]) -> Answer {
|
|
111
|
+
Self::check_aligned(ptr, N as u32)?;
|
|
112
|
+
self.write(ptr, &bytes)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pub(crate) fn write_u8(&mut self, ptr: u32, value: u8) -> Answer {
|
|
116
|
+
self.write(ptr, &[value])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pub(crate) fn write_u16(&mut self, ptr: u32, value: u16) -> Answer {
|
|
120
|
+
self.write_aligned(ptr, value.to_le_bytes())
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
pub(crate) fn write_u32(&mut self, ptr: u32, value: u32) -> Answer {
|
|
124
|
+
self.write_aligned(ptr, value.to_le_bytes())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pub(crate) fn write_u64(&mut self, ptr: u32, value: u64) -> Answer {
|
|
128
|
+
self.write_aligned(ptr, value.to_le_bytes())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// A guest path, which preview1 requires to be UTF-8, charged to the
|
|
132
|
+
/// call's fuel before it is read, as wasmtime-wasi charges it. One
|
|
133
|
+
/// that would not fit Linux's `PATH_MAX` with its terminating NUL is
|
|
134
|
+
/// refused before it is copied, as Linux's `getname` refuses it, and
|
|
135
|
+
/// one holding a NUL names nothing a host can hold, as cap-std refuses
|
|
136
|
+
/// it.
|
|
137
|
+
pub(crate) fn path(&self, ptr: u32, len: u32) -> Answer<String> {
|
|
138
|
+
self.consume(len, 1)?;
|
|
139
|
+
let bytes = self.bytes(ptr, len)?;
|
|
140
|
+
if bytes.len() >= PATH_MAX {
|
|
141
|
+
return Err(Errno::Nametoolong.into());
|
|
142
|
+
}
|
|
143
|
+
if bytes.contains(&0) {
|
|
144
|
+
return Err(Errno::Inval.into());
|
|
145
|
+
}
|
|
146
|
+
String::from_utf8(bytes.to_vec()).map_err(|_| Errno::Ilseq.into())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// The first non-empty buffer of an `iovec` / `ciovec` array, as
|
|
150
|
+
/// `(ptr, len)`, or an empty one when every buffer is empty. Like
|
|
151
|
+
/// wasmtime-wasi, kobako moves one buffer per call, and the buffer
|
|
152
|
+
/// itself is bounded only once the call has found its descriptor.
|
|
153
|
+
pub(crate) fn first_non_empty_buffer(&self, array: u32, count: u32) -> Answer<(u32, u32)> {
|
|
154
|
+
self.consume(count, 8)?;
|
|
155
|
+
for index in 0..count {
|
|
156
|
+
let entry = Self::element(array, index, 8)?;
|
|
157
|
+
let ptr = self.read_u32(entry)?;
|
|
158
|
+
let len = self.read_u32(Self::field(entry, 4)?)?;
|
|
159
|
+
if len != 0 {
|
|
160
|
+
self.consume(len, 1)?;
|
|
161
|
+
return Ok((ptr, len));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
Ok((0, 0))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[cfg(test)]
|
|
169
|
+
mod tests {
|
|
170
|
+
use super::*;
|
|
171
|
+
|
|
172
|
+
// Neither the array nor the buffer exists in this small memory, so the
|
|
173
|
+
// budget alone, spent before anything is read, refuses each.
|
|
174
|
+
// @behavior RT-096
|
|
175
|
+
#[test]
|
|
176
|
+
fn a_call_past_its_hostcall_fuel_answers_nomem() {
|
|
177
|
+
let mut array_bytes = [0u8; 64];
|
|
178
|
+
let mut buffer_bytes = [0u8; 64];
|
|
179
|
+
buffer_bytes[4..8].copy_from_slice(&(128u32 << 20).wrapping_add(1).to_le_bytes());
|
|
180
|
+
assert!(
|
|
181
|
+
matches!(
|
|
182
|
+
(
|
|
183
|
+
GuestMemory::new(&mut array_bytes).first_non_empty_buffer(0, (16 << 20) + 1),
|
|
184
|
+
GuestMemory::new(&mut buffer_bytes).first_non_empty_buffer(0, 1),
|
|
185
|
+
),
|
|
186
|
+
(
|
|
187
|
+
Err(Failure::Errno(Errno::Nomem)),
|
|
188
|
+
Err(Failure::Errno(Errno::Nomem))
|
|
189
|
+
)
|
|
190
|
+
),
|
|
191
|
+
"an iovec array or a buffer past 128 MiB through one preview1 call must answer \
|
|
192
|
+
NOMEM before the host copies any of it"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A guest memory of the full 4 GiB lets a field's address pass the end
|
|
197
|
+
// of the address space, which only the arithmetic itself can refuse.
|
|
198
|
+
#[test]
|
|
199
|
+
fn a_field_past_the_address_space_traps_rather_than_wrapping() {
|
|
200
|
+
assert!(
|
|
201
|
+
matches!(GuestMemory::field(u32::MAX - 4, 8), Err(Failure::Trap(_))),
|
|
202
|
+
"a field address past u32::MAX through GuestMemory::field must trap rather than wrap"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|