kobako 0.29.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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +217 -10
  4. data/Cargo.lock +139 -141
  5. data/README.md +96 -574
  6. data/ROADMAP.md +2 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +269 -1
  8. data/crates/kobako-runtime/Cargo.toml +25 -15
  9. data/crates/kobako-runtime/README.md +7 -3
  10. data/crates/kobako-runtime/src/dispatch.rs +1 -1
  11. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  12. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  13. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  14. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  15. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  16. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  17. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  18. data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
  19. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  20. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  21. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
  29. data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
  30. data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
  31. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  32. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  33. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  37. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  38. data/crates/kobako-runtime/src/filesystem.rs +21 -387
  39. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  40. data/crates/kobako-runtime/src/lib.rs +11 -6
  41. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  42. data/crates/kobako-runtime/src/locking.rs +218 -0
  43. data/crates/kobako-runtime/src/mount.rs +31 -104
  44. data/crates/kobako-runtime/src/profile.rs +137 -1
  45. data/crates/kobako-runtime/src/sched.rs +210 -0
  46. data/crates/kobako-runtime/src/security.rs +696 -0
  47. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  48. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  49. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  50. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  51. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  52. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  53. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  54. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  55. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  56. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  57. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  58. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  59. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  60. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  61. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  62. data/crates/kobako-runtime/src/vfs.rs +60 -0
  63. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  64. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  65. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  66. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  67. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  68. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  70. data/crates/kobako-transport/CHANGELOG.md +8 -2
  71. data/crates/kobako-transport/Cargo.toml +3 -3
  72. data/crates/kobako-transport/README.md +6 -5
  73. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  74. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  75. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  76. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  77. data/crates/kobako-transport/src/envelope/fault.rs +2 -2
  78. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  79. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  80. data/crates/kobako-transport/src/envelope/reply.rs +2 -2
  81. data/crates/kobako-transport/src/envelope/request.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  83. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  84. data/crates/kobako-transport/src/envelope.rs +2 -2
  85. data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
  86. data/crates/kobako-wasmtime/Cargo.toml +15 -14
  87. data/crates/kobako-wasmtime/README.md +9 -5
  88. data/crates/kobako-wasmtime/src/abi.rs +12 -20
  89. data/crates/kobako-wasmtime/src/ambient.rs +1 -2
  90. data/crates/kobako-wasmtime/src/cache.rs +5 -5
  91. data/crates/kobako-wasmtime/src/config.rs +22 -7
  92. data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
  93. data/crates/kobako-wasmtime/src/driver.rs +29 -96
  94. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  95. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  96. data/crates/kobako-wasmtime/src/imports.rs +14 -8
  97. data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
  98. data/crates/kobako-wasmtime/src/invocation.rs +26 -46
  99. data/crates/kobako-wasmtime/src/lib.rs +1 -4
  100. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  101. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
  102. data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
  103. data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
  104. data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
  105. data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
  106. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  107. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
  108. data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
  109. data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
  110. data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
  111. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  112. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  113. data/crates/kobako-wasmtime/src/preview1.rs +464 -413
  114. data/crates/kobako-wasmtime/src/trap.rs +9 -9
  115. data/crates/kobako-wasmtime/src/wasi.rs +53 -37
  116. data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
  117. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  118. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
  119. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
  120. data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
  121. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
  122. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  123. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  124. data/data/kobako.wasm +0 -0
  125. data/ext/kobako/Cargo.toml +1 -1
  126. data/ext/kobako/src/runtime/bridge.rs +1 -1
  127. data/ext/kobako/src/runtime/errors.rs +1 -1
  128. data/ext/kobako/src/runtime/escape.rs +28 -14
  129. data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
  130. data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
  131. data/ext/kobako/src/runtime/filesystem.rs +109 -137
  132. data/ext/kobako/src/runtime/gvl.rs +43 -8
  133. data/ext/kobako/src/runtime/snapshot.rs +1 -1
  134. data/ext/kobako/src/runtime.rs +6 -1
  135. data/lib/kobako/catalog/extensions.rb +5 -5
  136. data/lib/kobako/catalog/flags.rb +2 -2
  137. data/lib/kobako/catalog/handles.rb +6 -6
  138. data/lib/kobako/catalog/mount.rb +2 -10
  139. data/lib/kobako/catalog/services.rb +2 -2
  140. data/lib/kobako/catalog/snippets.rb +7 -7
  141. data/lib/kobako/catalog.rb +1 -1
  142. data/lib/kobako/codec/decoder.rb +10 -10
  143. data/lib/kobako/codec/ext_types.rb +3 -5
  144. data/lib/kobako/codec/handle_walk.rb +4 -6
  145. data/lib/kobako/codec/nesting.rb +7 -7
  146. data/lib/kobako/codec/utils.rb +1 -1
  147. data/lib/kobako/codec/value_budget.rb +3 -3
  148. data/lib/kobako/context.rb +5 -5
  149. data/lib/kobako/filesystem.rb +44 -13
  150. data/lib/kobako/payload/arguments.rb +2 -2
  151. data/lib/kobako/pool.rb +4 -4
  152. data/lib/kobako/sandbox.rb +28 -28
  153. data/lib/kobako/sandbox_options.rb +2 -3
  154. data/lib/kobako/snippet/bytecode.rb +1 -1
  155. data/lib/kobako/snippet/source.rb +1 -1
  156. data/lib/kobako/transport/dispatcher.rb +5 -5
  157. data/lib/kobako/transport/exposure.rb +2 -2
  158. data/lib/kobako/transport/reflection.rb +7 -7
  159. data/lib/kobako/transport/run.rb +5 -5
  160. data/lib/kobako/transport/yielder.rb +1 -1
  161. data/lib/kobako/version.rb +1 -1
  162. data/lib/kobako.rb +34 -0
  163. data/release-please-config.json +1 -0
  164. data/sig/kobako/catalog/extensions.rbs +3 -3
  165. data/sig/kobako/catalog/handles.rbs +3 -3
  166. data/sig/kobako/catalog/mount.rbs +0 -4
  167. data/sig/kobako/catalog/services.rbs +1 -1
  168. data/sig/kobako/catalog/snippets.rbs +3 -3
  169. data/sig/kobako/catalog.rbs +1 -1
  170. data/sig/kobako/codec/decoder.rbs +1 -1
  171. data/sig/kobako/codec/handle_walk.rbs +1 -1
  172. data/sig/kobako/codec/nesting.rbs +2 -2
  173. data/sig/kobako/codec/utils.rbs +1 -1
  174. data/sig/kobako/codec/value_budget.rbs +1 -1
  175. data/sig/kobako/codec.rbs +1 -2
  176. data/sig/kobako/context.rbs +1 -1
  177. data/sig/kobako/filesystem.rbs +19 -14
  178. data/sig/kobako/guest_yielder.rbs +1 -1
  179. data/sig/kobako/payload/arguments.rbs +1 -1
  180. data/sig/kobako/pool.rbs +2 -2
  181. data/sig/kobako/runtime.rbs +1 -1
  182. data/sig/kobako/sandbox.rbs +3 -5
  183. data/sig/kobako/sandbox_options.rbs +1 -1
  184. data/sig/kobako/transport/dispatcher.rbs +2 -2
  185. data/sig/kobako/transport/exposure.rbs +1 -1
  186. data/sig/kobako/transport/reflection.rbs +2 -2
  187. data/sig/kobako/transport/run.rbs +3 -3
  188. data/sig/kobako/transport.rbs +1 -1
  189. metadata +67 -19
  190. data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
  191. data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
  192. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
  193. data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
  194. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  195. data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
  196. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
  197. data/crates/kobako-wasmtime/src/io.rs +0 -89
  198. data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
  199. data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
  200. /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
  201. /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
@@ -1,271 +0,0 @@
1
- //! Guest linear-memory I/O for the ABI's buffer handoffs, whether the
2
- //! invocation body drives one through its `Store` or a dispatch or yield
3
- //! import drives one through its `Caller`. preview1 reads and writes its
4
- //! arguments through its own `GuestMemory`.
5
- //!
6
- //! The two shared steps answer an error kind, and each caller words it,
7
- //! because depth decides what the same problem means: before the export
8
- //! call it is a `Trap` or an intact runtime, inside a callback it is a
9
- //! wire failure the guest receives.
10
-
11
- use wasmtime::{AsContext, AsContextMut, Caller, Memory};
12
-
13
- use crate::exports::{Exports, RUNTIME_INCOMPATIBLE};
14
- use crate::invocation::Invocation;
15
- use kobako_transport::abi::{unpack_ptr_len, MAX_DISPATCH_PAYLOAD};
16
- use kobako_transport::envelope::Downcall;
17
-
18
- // The size-limit messages here and in `io` name the cap as 16 MiB, so
19
- // moving the cap fails the build until they are reworded with it.
20
- const _: () = assert!(MAX_DISPATCH_PAYLOAD == 16 << 20);
21
-
22
- /// User-facing message for the "the loaded Wasm module is not a
23
- /// Kobako-shaped runtime at all" failure mode, such as an instance with no
24
- /// `memory` export.
25
- pub(crate) const SANDBOX_RUNTIME_NOT_KOBAKO: &str =
26
- "the loaded Wasm module is not a Kobako-compatible runtime";
27
-
28
- /// Why a host write into guest memory failed.
29
- pub(crate) enum WriteError {
30
- TooLarge,
31
- /// The allocator or the memory is missing or mistyped; carries the
32
- /// message for it.
33
- Incompatible(&'static str),
34
- Trapped(wasmtime::Error),
35
- /// The allocator ran and answered 0.
36
- OutOfMemory,
37
- OutOfBounds,
38
- }
39
-
40
- /// Why a host read of a guest-named buffer failed.
41
- pub(crate) enum ReadError {
42
- TooLarge,
43
- OutOfBounds,
44
- }
45
-
46
- /// Allocate a guest buffer through `__kobako_alloc` and copy `bytes` into
47
- /// it, answering the buffer's address.
48
- pub(crate) fn alloc_and_write(
49
- mut ctx: impl AsContextMut,
50
- exports: &Exports,
51
- bytes: &[u8],
52
- ) -> Result<u32, WriteError> {
53
- let len = checked_payload_len(bytes.len()).map_err(|_| WriteError::TooLarge)?;
54
- let alloc = exports
55
- .alloc
56
- .as_ref()
57
- .ok_or(WriteError::Incompatible(RUNTIME_INCOMPATIBLE))?;
58
- let memory = exports
59
- .memory
60
- .ok_or(WriteError::Incompatible(SANDBOX_RUNTIME_NOT_KOBAKO))?;
61
- let ptr = alloc
62
- .call(&mut ctx, len as u32)
63
- .map_err(WriteError::Trapped)?;
64
- if ptr == 0 {
65
- return Err(WriteError::OutOfMemory);
66
- }
67
- memory
68
- .write(&mut ctx, ptr as usize, bytes)
69
- .map_err(|_| WriteError::OutOfBounds)?;
70
- Ok(ptr)
71
- }
72
-
73
- /// Copy out the buffer the guest names; a length past the 16 MiB cap is
74
- /// refused before memory is touched.
75
- pub(crate) fn read_buffer(
76
- ctx: impl AsContext,
77
- memory: Memory,
78
- ptr: usize,
79
- len: usize,
80
- ) -> Result<Vec<u8>, ReadError> {
81
- if len > MAX_DISPATCH_PAYLOAD {
82
- return Err(ReadError::TooLarge);
83
- }
84
- let data = memory.data(&ctx);
85
- let range = guest_buffer_range(ptr, len, data.len()).map_err(|_| ReadError::OutOfBounds)?;
86
- Ok(data[range].to_vec())
87
- }
88
-
89
- /// The handles of the instance a host import runs inside.
90
- fn callback_exports(caller: &Caller<'_, Invocation>) -> Result<Exports, &'static str> {
91
- caller.data().exports().ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)
92
- }
93
-
94
- /// Keep a trap the guest raised during a callback into it for the dispatch
95
- /// import to end the invocation with, and answer the callback's own reason.
96
- fn keep_trap(
97
- caller: &mut Caller<'_, Invocation>,
98
- trap: wasmtime::Error,
99
- reason: &'static str,
100
- ) -> &'static str {
101
- caller.data_mut().record_reentry_trap(trap);
102
- reason
103
- }
104
-
105
- /// The request the guest hands the upcall import. Its `(ptr, len)` are
106
- /// guest addresses, so they are read unsigned.
107
- pub(crate) fn read_request(
108
- caller: &mut Caller<'_, Invocation>,
109
- req_ptr: i32,
110
- req_len: i32,
111
- ) -> Result<Vec<u8>, &'static str> {
112
- let memory = callback_exports(caller)?
113
- .memory
114
- .ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)?;
115
- read_buffer(
116
- &*caller,
117
- memory,
118
- req_ptr as u32 as usize,
119
- req_len as u32 as usize,
120
- )
121
- .map_err(|err| match err {
122
- ReadError::TooLarge => "request payload exceeds the 16 MiB limit",
123
- ReadError::OutOfBounds => "the Sandbox produced an out-of-bounds request",
124
- })
125
- }
126
-
127
- /// Write `bytes` into the guest from inside a callback. A guest that has
128
- /// already trapped is not called again.
129
- pub(crate) fn write_from_callback(
130
- caller: &mut Caller<'_, Invocation>,
131
- bytes: &[u8],
132
- ) -> Result<u32, &'static str> {
133
- if caller.data().reentry_trapped() {
134
- return Err("the Sandbox already trapped during this invocation");
135
- }
136
- let exports = callback_exports(caller)?;
137
- alloc_and_write(&mut *caller, &exports, bytes).map_err(|err| match err {
138
- WriteError::TooLarge => "payload exceeds the 16 MiB limit",
139
- WriteError::Incompatible(message) => message,
140
- WriteError::Trapped(trap) => keep_trap(
141
- caller,
142
- trap,
143
- "the Sandbox trapped while allocating memory for the request",
144
- ),
145
- WriteError::OutOfMemory => "the Sandbox ran out of memory while preparing the request",
146
- WriteError::OutOfBounds => "could not write the request into the Sandbox's memory",
147
- })
148
- }
149
-
150
- /// Every host write boundary routes its length through here, so the
151
- /// wire-violation reason is uniform.
152
- pub(crate) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
153
- if len > MAX_DISPATCH_PAYLOAD {
154
- return Err("payload exceeds the 16 MiB limit");
155
- }
156
- // The cap above sits below `i32::MAX`, so this conversion cannot wrap.
157
- i32::try_from(len).map_err(|_| "payload exceeds the 16 MiB limit")
158
- }
159
-
160
- pub(crate) fn guest_buffer_range(
161
- ptr: usize,
162
- len: usize,
163
- mem_size: usize,
164
- ) -> Result<core::ops::Range<usize>, &'static str> {
165
- let end = ptr.checked_add(len).ok_or("ptr + len overflow")?;
166
- if end > mem_size {
167
- return Err("range exceeds Sandbox memory size");
168
- }
169
- Ok(ptr..end)
170
- }
171
-
172
- /// The `(ptr, len)` the guest packs for any answer it hands back, the
173
- /// outcome and a block's result alike.
174
- pub(crate) fn unpack_guest_buffer(packed: u64) -> (usize, usize) {
175
- let (ptr, len) = unpack_ptr_len(packed);
176
- (ptr as usize, len as usize)
177
- }
178
-
179
- pub(crate) fn drive_yield(
180
- caller: &mut Caller<'_, Invocation>,
181
- frame: u32,
182
- args: &[u8],
183
- ) -> Result<Vec<u8>, &'static str> {
184
- let request = Downcall::Yield {
185
- frame,
186
- args: args.to_vec(),
187
- }
188
- .encode();
189
- let len_i32 = checked_payload_len(request.len())?;
190
- let req_ptr = write_from_callback(caller, &request)? as i32;
191
-
192
- let exports = callback_exports(caller)?;
193
- let yield_fn = exports.downcall.ok_or(RUNTIME_INCOMPATIBLE)?;
194
- let memory = exports.memory.ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)?;
195
- let packed = yield_fn
196
- .call(&mut *caller, (req_ptr, len_i32))
197
- .map_err(|trap| keep_trap(caller, trap, "the Sandbox trapped while invoking a block"))?;
198
- let (reply_ptr, reply_len) = unpack_guest_buffer(packed);
199
- if reply_len == 0 {
200
- return Err("the Sandbox returned an empty block result");
201
- }
202
- read_buffer(&*caller, memory, reply_ptr, reply_len).map_err(|err| match err {
203
- ReadError::TooLarge => "block result payload exceeds the 16 MiB limit",
204
- ReadError::OutOfBounds => "the Sandbox returned an out-of-bounds block result",
205
- })
206
- }
207
-
208
- #[cfg(test)]
209
- mod tests {
210
- use super::{
211
- checked_payload_len, guest_buffer_range, unpack_guest_buffer, MAX_DISPATCH_PAYLOAD,
212
- };
213
-
214
- // @behavior WE-059
215
- #[test]
216
- fn checked_payload_len_accepts_zero_and_the_cap() {
217
- assert_eq!(checked_payload_len(0), Ok(0));
218
- assert_eq!(
219
- checked_payload_len(MAX_DISPATCH_PAYLOAD),
220
- Ok(MAX_DISPATCH_PAYLOAD as i32)
221
- );
222
- }
223
-
224
- // @behavior WE-060
225
- #[test]
226
- fn checked_payload_len_rejects_past_the_cap() {
227
- assert!(checked_payload_len(MAX_DISPATCH_PAYLOAD + 1).is_err());
228
- assert!(checked_payload_len(usize::MAX).is_err());
229
- }
230
-
231
- // @behavior WE-061
232
- #[test]
233
- fn guest_buffer_range_returns_half_open_range() {
234
- assert_eq!(guest_buffer_range(10, 5, 100), Ok(10..15));
235
- }
236
-
237
- // @behavior WE-062
238
- #[test]
239
- fn guest_buffer_range_accepts_zero_length_at_any_in_bounds_ptr() {
240
- assert_eq!(guest_buffer_range(0, 0, 0), Ok(0..0));
241
- assert_eq!(guest_buffer_range(42, 0, 100), Ok(42..42));
242
- }
243
-
244
- // @behavior WE-063
245
- #[test]
246
- fn guest_buffer_range_rejects_ptr_plus_len_overflow() {
247
- assert!(guest_buffer_range(usize::MAX, 1, usize::MAX).is_err());
248
- }
249
-
250
- // @behavior WE-064
251
- #[test]
252
- fn guest_buffer_range_rejects_end_past_memory() {
253
- assert!(guest_buffer_range(10, 100, 50).is_err());
254
- assert_eq!(guest_buffer_range(0, 50, 50), Ok(0..50));
255
- }
256
-
257
- // @behavior WE-001
258
- #[test]
259
- fn unpack_guest_buffer_extracts_high_ptr_low_len() {
260
- assert_eq!(
261
- unpack_guest_buffer(0xAABB_CCDD_1122_3344),
262
- (0xAABB_CCDD, 0x1122_3344)
263
- );
264
- }
265
-
266
- // @behavior WE-065
267
- #[test]
268
- fn unpack_guest_buffer_zero_decodes_to_zero_pair() {
269
- assert_eq!(unpack_guest_buffer(0), (0, 0));
270
- }
271
- }
@@ -1,89 +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 and reads the
3
- //! Outcome the downcall answered with.
4
-
5
- use wasmtime::Store as WtStore;
6
-
7
- use crate::exports::Exports;
8
- use crate::guest_mem::{self, ReadError, WriteError};
9
- use crate::invocation::Invocation;
10
- use kobako_runtime::error::{InvokeError, SetupError, Trap};
11
- use kobako_transport::envelope::{CompileReply, Outcome};
12
-
13
- /// A missing or trapping allocator is an engine fault, a `Trap`; one that
14
- /// runs but returns 0 leaves the runtime intact, so it is a `SetupError`.
15
- pub(crate) fn write_request(
16
- store: &mut WtStore<Invocation>,
17
- exports: &Exports,
18
- envelope: &[u8],
19
- ) -> Result<(i32, i32), InvokeError> {
20
- let trap = |message: String| -> InvokeError { Trap::Other(message).into() };
21
- let ptr =
22
- guest_mem::alloc_and_write(&mut *store, exports, envelope).map_err(|err| match err {
23
- WriteError::OutOfMemory => {
24
- SetupError::Intact("could not allocate input buffer (out of memory)".to_string())
25
- .into()
26
- }
27
- WriteError::TooLarge => trap("payload exceeds the 16 MiB limit".to_string()),
28
- WriteError::Incompatible(message) => trap(message.to_string()),
29
- WriteError::Trapped(e) => crate::trap::cap_trap(&e)
30
- .unwrap_or_else(|| Trap::Other(format!("failed to allocate input buffer: {e}")))
31
- .into(),
32
- WriteError::OutOfBounds => {
33
- trap("the input buffer lies outside the Sandbox's memory".to_string())
34
- }
35
- })?;
36
- // The write has already held the length to the payload cap.
37
- Ok((ptr as i32, envelope.len() as i32))
38
- }
39
-
40
- /// A guest that answered without an Outcome the envelope can frame — an
41
- /// empty answer included — has nothing left to attribute, so it is a
42
- /// `Trap` like every other way of failing to read the result.
43
- pub(crate) fn read_outcome(
44
- store: &WtStore<Invocation>,
45
- exports: &Exports,
46
- packed: u64,
47
- ) -> Result<Outcome, Trap> {
48
- let bytes = read_answer(store, exports, packed)?;
49
- Outcome::decode(&bytes).map_err(|_| {
50
- Trap::Other(
51
- if bytes.is_empty() {
52
- "Sandbox exited without producing a result"
53
- } else {
54
- "Sandbox produced an unrecognised result"
55
- }
56
- .to_string(),
57
- )
58
- })
59
- }
60
-
61
- /// Held to the same 16 MiB bound as an Outcome; an answer that does not
62
- /// frame is a `Trap` the caller treats like any other failed compile.
63
- pub(crate) fn read_compile_reply(
64
- store: &WtStore<Invocation>,
65
- exports: &Exports,
66
- packed: u64,
67
- ) -> Result<CompileReply, Trap> {
68
- let bytes = read_answer(store, exports, packed)?;
69
- CompileReply::decode(&bytes)
70
- .map_err(|_| Trap::Other("Sandbox produced an unrecognised compile answer".to_string()))
71
- }
72
-
73
- fn read_answer(
74
- store: &WtStore<Invocation>,
75
- exports: &Exports,
76
- packed: u64,
77
- ) -> Result<Vec<u8>, Trap> {
78
- let mem = exports.require_memory()?;
79
- let (ptr, len) = guest_mem::unpack_guest_buffer(packed);
80
- guest_mem::read_buffer(store, mem, ptr, len).map_err(|err| {
81
- Trap::Other(
82
- match err {
83
- ReadError::TooLarge => "result payload exceeds the 16 MiB limit",
84
- ReadError::OutOfBounds => "the Sandbox result is out of bounds",
85
- }
86
- .to_string(),
87
- )
88
- })
89
- }
@@ -1,60 +0,0 @@
1
- //! The checks preview1 makes on an enum or flags argument before a call
2
- //! looks at its descriptor: a value outside what the witx defines traps,
3
- //! as wasmtime-wasi's generated bindings refuse to convert it.
4
-
5
- use super::errno::{Answer, Errno, Failure};
6
-
7
- /// The bits of `fdflags`.
8
- pub(crate) const FDFLAGS: u64 = 0x1f;
9
- /// The bits of `rights`.
10
- pub(crate) const RIGHTS: u64 = (1 << 30) - 1;
11
- /// The bits of `lookupflags`.
12
- pub(crate) const LOOKUPFLAGS: u64 = 0x1;
13
- /// The bits of `oflags`.
14
- pub(crate) const OFLAGS: u64 = 0xf;
15
- /// The bits of `riflags`.
16
- pub(crate) const RIFLAGS: u64 = 0x3;
17
- /// The bits of `sdflags`.
18
- pub(crate) const SDFLAGS: u64 = 0x3;
19
-
20
- pub(crate) const FDFLAGS_APPEND: u32 = 1 << 0;
21
- pub(crate) const FDFLAGS_NONBLOCK: u32 = 1 << 2;
22
- /// The `fdflags` asking each write to be synchronized: `dsync`, `rsync`,
23
- /// and `sync`.
24
- pub(crate) const FDFLAGS_SYNCHRONIZED: u32 = (1 << 1) | (1 << 3) | (1 << 4);
25
- pub(crate) const RIGHTS_FD_READ: u64 = 1 << 1;
26
- pub(crate) const RIGHTS_FD_WRITE: u64 = 1 << 6;
27
-
28
- /// The highest `advice` variant.
29
- pub(crate) const ADVICE_MAX: u32 = 5;
30
- /// The highest `whence` variant.
31
- pub(crate) const WHENCE_MAX: u32 = 2;
32
-
33
- pub(crate) fn flags(value: impl Into<u64>, defined: u64) -> Answer {
34
- if value.into() & !defined != 0 {
35
- return Err(Failure::trap("flags value outside what the witx defines"));
36
- }
37
- Ok(())
38
- }
39
-
40
- pub(crate) fn variant(value: u32, max: u32) -> Answer {
41
- if value > max {
42
- return Err(Failure::trap("enum value outside what the witx defines"));
43
- }
44
- Ok(())
45
- }
46
-
47
- /// `fstflags`, where setting a timestamp and setting it to now are
48
- /// exclusive: a defined value asking both is `INVAL`.
49
- pub(crate) fn fstflags(value: u32) -> Answer {
50
- const ATIM: u32 = 1 << 0;
51
- const ATIM_NOW: u32 = 1 << 1;
52
- const MTIM: u32 = 1 << 2;
53
- const MTIM_NOW: u32 = 1 << 3;
54
- flags(value, 0xf)?;
55
- let both = |set, now| value & set != 0 && value & now != 0;
56
- if both(ATIM, ATIM_NOW) || both(MTIM, MTIM_NOW) {
57
- return Err(Errno::Inval.into());
58
- }
59
- Ok(())
60
- }
@@ -1,188 +0,0 @@
1
- //! Resolving a guest path beneath a directory descriptor.
2
- //!
3
- //! The runtime is the boundary: a path never climbs above the directory
4
- //! descriptor it is resolved beneath, as a capability never widens, and
5
- //! the Mount is asked what the guest may do there before the Filesystem
6
- //! is, so a Filesystem of a third party's need check neither.
7
- //! A `..` steps out of what the Filesystem holds rather than out of the
8
- //! path's text, as a host resolves it, so `missing/..` names nothing and
9
- //! a `..` out of a link is refused rather than taken from its text. A link
10
- //! is followed only within the directory the descriptor opened, as
11
- //! wasmtime-wasi's cap-std `Dir` follows one.
12
-
13
- use kobako_runtime::filesystem::{FileType, Filesystem, FilesystemError, Metadata};
14
-
15
- use super::errno::{Answer, Errno};
16
- use crate::descriptors::{Identity, OpenDirectory};
17
-
18
- /// Linux's limits on a path, its terminating NUL counted, and on one name
19
- /// within it.
20
- pub(crate) const PATH_MAX: usize = 4096;
21
- pub(crate) const NAME_MAX: usize = 255;
22
-
23
- /// A guest path resolved beneath a directory: relative to the mount's
24
- /// root, and whether it ended in `/` or `/.`, which only a directory
25
- /// satisfies.
26
- pub(crate) struct ResolvedPath {
27
- pub(crate) path: String,
28
- pub(crate) names_directory: bool,
29
- }
30
-
31
- impl ResolvedPath {
32
- /// The path, refused as `NOTDIR` when it must name a directory and
33
- /// `lookup` finds anything else there: `metadata` for a call that
34
- /// follows a link at the end, `symlink_metadata` for one that acts on
35
- /// the link itself.
36
- pub(crate) fn check_directory(
37
- self,
38
- filesystem: &(dyn Filesystem + 'static),
39
- lookup: fn(&(dyn Filesystem + 'static), &str) -> Result<Metadata, FilesystemError>,
40
- ) -> Answer<String> {
41
- if self.names_directory {
42
- match lookup(filesystem, &self.path) {
43
- Ok(metadata) if metadata.file_type != FileType::Directory => {
44
- return Err(Errno::Notdir.into())
45
- }
46
- Ok(_) | Err(FilesystemError::NotFound) => {}
47
- Err(err) => return Err(err.into()),
48
- }
49
- }
50
- Ok(self.path)
51
- }
52
-
53
- /// The path with a link standing at its end followed within the
54
- /// directory `dir` opened, for a call following it. A link pointing
55
- /// at nothing there is `LOOP` to a call that `creates`, since what it
56
- /// would create could lie outside `dir`, and `NOENT` otherwise.
57
- pub(crate) fn follow_last(self, dir: &OpenDirectory, creates: bool) -> Answer<Self> {
58
- let Some(base) = bounded_base(dir) else {
59
- return Ok(self);
60
- };
61
- let filesystem = dir.mount.filesystem();
62
- let Some(rest) = beneath_base(base, &self.path) else {
63
- return Ok(self);
64
- };
65
- match filesystem.symlink_metadata(&self.path) {
66
- Ok(metadata) if metadata.file_type == FileType::Symlink => {}
67
- _ => return Ok(self),
68
- }
69
- let path = match filesystem.canonicalize_beneath(base, rest) {
70
- Ok(found) => join(base, &found),
71
- Err(FilesystemError::NotFound) if creates => return Err(Errno::Loop.into()),
72
- Err(err) => return Err(err.into()),
73
- };
74
- Ok(Self { path, ..self })
75
- }
76
- }
77
-
78
- /// The directory a descriptor's links are kept within, or `None` where
79
- /// the Filesystem keeps them within the Mount's root already, or a Mount
80
- /// follows no link at all.
81
- fn bounded_base(dir: &OpenDirectory) -> Option<&str> {
82
- (!dir.path.is_empty() && dir.mount.follows_links()).then_some(dir.path.as_str())
83
- }
84
-
85
- /// What `path` names beneath `base`, or `None` for `base` itself.
86
- fn beneath_base<'a>(base: &str, path: &'a str) -> Option<&'a str> {
87
- path.strip_prefix(base)?.strip_prefix('/')
88
- }
89
-
90
- fn join(base: &str, path: &str) -> String {
91
- if path.is_empty() {
92
- base.to_string()
93
- } else {
94
- format!("{base}/{path}")
95
- }
96
- }
97
-
98
- /// `path` with the links on its way to its last name followed within the
99
- /// directory `dir` opened, so none leads the call out of it. A way the
100
- /// Filesystem finds nothing on stays as given, for the call to answer.
101
- fn within(dir: &OpenDirectory, path: String) -> Answer<String> {
102
- let Some(base) = bounded_base(dir) else {
103
- return Ok(path);
104
- };
105
- let Some((way, name)) = beneath_base(base, &path).and_then(|rest| rest.rsplit_once('/')) else {
106
- return Ok(path);
107
- };
108
- match dir.mount.filesystem().canonicalize_beneath(base, way) {
109
- Ok(found) => Ok(join(&join(base, &found), name)),
110
- Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => Ok(path),
111
- Err(err) => Err(err.into()),
112
- }
113
- }
114
-
115
- /// `path`, beneath `dir`. An empty path names nothing; an absolute path or
116
- /// a `..` above `dir` itself is not the guest's to reach; a `..` or a final `.`
117
- /// after a component that is missing, no directory, or a link is `NOENT`,
118
- /// `NOTDIR`, or `LOOP`. A
119
- /// name past `NAME_MAX`, or a resolved path the Filesystem could not be
120
- /// handed within `PATH_MAX`, is `NAMETOOLONG`.
121
- pub(crate) fn resolve(dir: &OpenDirectory, path: &str) -> Answer<ResolvedPath> {
122
- confirm(dir)?;
123
- if path.is_empty() {
124
- return Err(Errno::Noent.into());
125
- }
126
- if path.starts_with('/') {
127
- return Err(Errno::Notcapable.into());
128
- }
129
- let mut components: Vec<&str> = dir.path.split('/').filter(|c| !c.is_empty()).collect();
130
- let opened = components.len();
131
- let mut last = "";
132
- for component in path.split('/') {
133
- last = component;
134
- match component {
135
- "" | "." => {}
136
- ".." => {
137
- if components.len() <= opened {
138
- return Err(Errno::Notcapable.into());
139
- }
140
- step_out_of(dir.mount.filesystem().as_ref(), &components.join("/"))?;
141
- components.pop();
142
- }
143
- component if component.len() > NAME_MAX => return Err(Errno::Nametoolong.into()),
144
- component => components.push(component),
145
- }
146
- }
147
- let path = components.join("/");
148
- if path.len() >= PATH_MAX {
149
- return Err(Errno::Nametoolong.into());
150
- }
151
- // A final `.` resolves through the name before it, as a `..` does.
152
- if last == "." && components.len() > opened {
153
- step_out_of(dir.mount.filesystem().as_ref(), &path)?;
154
- }
155
- Ok(ResolvedPath {
156
- path: within(dir, path)?,
157
- names_directory: matches!(last, "" | "."),
158
- })
159
- }
160
-
161
- /// `dir` still stands at its path, `NOENT` once another directory or
162
- /// nothing does, as a POSIX descriptor never reaches a directory made in
163
- /// its place. A directory opened with no identity follows its path alone.
164
- pub(crate) fn confirm(dir: &OpenDirectory) -> Answer {
165
- let Some(identity) = dir.identity else {
166
- return Ok(());
167
- };
168
- match dir.mount.filesystem().metadata(&dir.path) {
169
- Ok(found) if Identity::of(&found) == Some(identity) => Ok(()),
170
- Ok(_) | Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => {
171
- Err(Errno::Noent.into())
172
- }
173
- Err(err) => Err(err.into()),
174
- }
175
- }
176
-
177
- /// A `..` leaves `path` only when it is a directory the Filesystem holds.
178
- /// A host leaves a link by the parent of where it points, which no guest
179
- /// is told, so a link there is refused as `LOOP`, as `openat2(2)`
180
- /// refuses a link it may not follow.
181
- fn step_out_of(filesystem: &dyn Filesystem, path: &str) -> Answer {
182
- match filesystem.symlink_metadata(path) {
183
- Ok(metadata) if metadata.file_type == FileType::Directory => Ok(()),
184
- Ok(metadata) if metadata.file_type == FileType::Symlink => Err(Errno::Loop.into()),
185
- Ok(_) => Err(Errno::Notdir.into()),
186
- Err(err) => Err(err.into()),
187
- }
188
- }