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