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
@@ -18,6 +18,7 @@ use kobako_transport::envelope::Outcome;
18
18
  /// pre-invocation sentinel — no bytes, cap not reached — shared by every
19
19
  /// frontend's before-first-invocation readout.
20
20
  #[derive(Default)]
21
+ #[non_exhaustive]
21
22
  pub struct Capture {
22
23
  pub bytes: Vec<u8>,
23
24
  pub truncated: bool,
@@ -39,12 +40,13 @@ pub enum Completion {
39
40
  Trap(Trap),
40
41
  }
41
42
 
42
- /// Resource usage of one guest invocation, measured across the same
43
- /// bracket as the wall-clock / memory caps: `wall_time` is the seconds
44
- /// spent inside the guest export call; `memory_peak` is the high-water
45
- /// `memory.grow` delta in bytes past the entry-time baseline. `Default`
46
- /// (all zeros) is the pre-invocation sentinel.
43
+ /// Resource usage of one guest invocation, each measured across the span
44
+ /// its cap governs: `wall_time` is the seconds from setting the guest up to
45
+ /// its export call returning, the span the deadline covers; `memory_peak`
46
+ /// is the high-water `memory.grow` delta in bytes past the entry-time
47
+ /// baseline. `Default` (all zeros) is the pre-invocation sentinel.
47
48
  #[derive(Clone, Copy, Default)]
49
+ #[non_exhaustive]
48
50
  pub struct Usage {
49
51
  pub wall_time: f64,
50
52
  pub memory_peak: usize,
@@ -54,9 +56,22 @@ pub struct Usage {
54
56
  /// kinds: captures and usage are collected on trap and outcome alike.
55
57
  /// What a frontend exposes from the trap path is its own contract
56
58
  /// decision.
59
+ #[non_exhaustive]
57
60
  pub struct Snapshot {
58
61
  pub completion: Completion,
59
62
  pub stdout: Capture,
60
63
  pub stderr: Capture,
61
64
  pub usage: Usage,
62
65
  }
66
+
67
+ impl Snapshot {
68
+ /// What an engine hands back from one invocation.
69
+ pub fn new(completion: Completion, stdout: Capture, stderr: Capture, usage: Usage) -> Self {
70
+ Self {
71
+ completion,
72
+ stdout,
73
+ stderr,
74
+ usage,
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,162 @@
1
+ //! What a change sets of an entry, as Linux's `fs/attr.c` takes a
2
+ //! `struct iattr`.
3
+
4
+ use std::fmt;
5
+ use std::time::SystemTime;
6
+
7
+ use super::dentry::Dentry;
8
+ use super::file::FileOperations;
9
+ use super::FilesystemError;
10
+ use crate::locking::RwSemaphoreGuard;
11
+
12
+ /// Apply `attr` to what `dentry` names, as Linux's `notify_change` reaches
13
+ /// `->setattr`, the moment it lands read once, as `current_time` is, so every
14
+ /// time it sets to now reads the same. The caller holds the entry's lock and
15
+ /// hands its guard over, as `notify_change` warns unless `inode_is_locked`;
16
+ /// the lock was reached through the entry, which a dentry never drops.
17
+ pub(crate) fn notify_change(
18
+ dentry: &Dentry,
19
+ held: &RwSemaphoreGuard,
20
+ attr: &Iattr,
21
+ ) -> Result<(), FilesystemError> {
22
+ let inode = dentry
23
+ .inode()
24
+ .expect("a dentry whose entry is locked names it");
25
+ debug_assert!(
26
+ inode.is_locked_by(held),
27
+ "notify_change takes the guard of the entry's own lock"
28
+ );
29
+ let attr = Iattr {
30
+ ctime: Some(SystemTime::now()),
31
+ ..*attr
32
+ };
33
+ inode.ops().setattr(dentry, &attr)
34
+ }
35
+
36
+ /// What a change sets of an entry, as Linux's `struct iattr`; a field left
37
+ /// `None` is one `ia_valid` does not name.
38
+ #[derive(Clone, Copy)]
39
+ #[non_exhaustive]
40
+ pub struct Iattr<'a> {
41
+ /// The length to truncate or extend a file to, extending with zeros.
42
+ pub size: Option<u64>,
43
+ /// The access and modification times to set.
44
+ pub atime: Option<SetTime>,
45
+ pub mtime: Option<SetTime>,
46
+ /// Whether the size comes from an open asking to truncate, as `ATTR_OPEN`
47
+ /// says, so a Filesystem that truncated in `->open` has nothing left to do.
48
+ pub open: bool,
49
+ /// The moment the change lands, which `notify_change` sets, as Linux's
50
+ /// `ia_ctime`: what a time set to now reads, and the entry's change time.
51
+ pub ctime: Option<SystemTime>,
52
+ /// The open file a truncate is made through, as `ATTR_FILE` names
53
+ /// `ia_file`, so a Filesystem acts through what that open holds, as 9p
54
+ /// takes its fid.
55
+ pub file: Option<&'a dyn FileOperations>,
56
+ }
57
+
58
+ impl Iattr<'static> {
59
+ /// A change truncating or extending a file to `len`, moving its modification
60
+ /// time to the moment it lands, as Linux's `do_truncate` names `ATTR_MTIME`
61
+ /// with `ATTR_SIZE`.
62
+ pub fn size(len: u64) -> Self {
63
+ Self {
64
+ size: Some(len),
65
+ atime: None,
66
+ mtime: Some(SetTime::Now),
67
+ open: false,
68
+ ctime: None,
69
+ file: None,
70
+ }
71
+ }
72
+
73
+ /// A change emptying a file an open asked to truncate.
74
+ pub(crate) fn open_truncate() -> Self {
75
+ Self {
76
+ open: true,
77
+ ..Self::size(0)
78
+ }
79
+ }
80
+
81
+ /// A change setting the times named, leaving any other as it stands.
82
+ pub fn times(atime: Option<SetTime>, mtime: Option<SetTime>) -> Self {
83
+ Self {
84
+ size: None,
85
+ atime,
86
+ mtime,
87
+ open: false,
88
+ ctime: None,
89
+ file: None,
90
+ }
91
+ }
92
+ }
93
+
94
+ impl<'a> Iattr<'a> {
95
+ /// This change made through the open file `file`, as `do_truncate` sets
96
+ /// `ATTR_FILE`.
97
+ pub(crate) fn through<'b>(self, file: &'b dyn FileOperations) -> Iattr<'b>
98
+ where
99
+ 'a: 'b,
100
+ {
101
+ Iattr {
102
+ file: Some(file),
103
+ ..self
104
+ }
105
+ }
106
+
107
+ /// The time `set` names, now read as the moment this change lands.
108
+ pub fn time_of(&self, set: SetTime) -> SystemTime {
109
+ match set {
110
+ SetTime::Now => self.ctime.unwrap_or_else(SystemTime::now),
111
+ SetTime::At(time) => time,
112
+ }
113
+ }
114
+ }
115
+
116
+ /// Two changes compare equal through the same open file.
117
+ impl PartialEq for Iattr<'_> {
118
+ fn eq(&self, other: &Self) -> bool {
119
+ let file = |attr: &Self| {
120
+ attr.file
121
+ .map(|file| file as *const dyn FileOperations as *const ())
122
+ };
123
+ (
124
+ self.size,
125
+ self.atime,
126
+ self.mtime,
127
+ self.open,
128
+ self.ctime,
129
+ file(self),
130
+ ) == (
131
+ other.size,
132
+ other.atime,
133
+ other.mtime,
134
+ other.open,
135
+ other.ctime,
136
+ file(other),
137
+ )
138
+ }
139
+ }
140
+
141
+ impl Eq for Iattr<'_> {}
142
+
143
+ impl fmt::Debug for Iattr<'_> {
144
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145
+ f.debug_struct("Iattr")
146
+ .field("size", &self.size)
147
+ .field("atime", &self.atime)
148
+ .field("mtime", &self.mtime)
149
+ .field("open", &self.open)
150
+ .field("ctime", &self.ctime)
151
+ .field("file", &self.file.is_some())
152
+ .finish()
153
+ }
154
+ }
155
+
156
+ /// A time a change sets, as Linux's `ATTR_ATIME` names the moment it lands and
157
+ /// `ATTR_ATIME_SET` the time it carries.
158
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
159
+ pub enum SetTime {
160
+ Now,
161
+ At(SystemTime),
162
+ }
@@ -0,0 +1,334 @@
1
+ //! Who a call is made for, as Linux's `struct cred`, and the hooks a security
2
+ //! module answers, as `security_*` call each registered LSM. The VFS asks them
3
+ //! where Linux does and knows nothing of what they decide.
4
+
5
+ use std::any::Any;
6
+ use std::sync::atomic::{AtomicUsize, Ordering};
7
+ use std::sync::Arc;
8
+
9
+ use super::dentry::Dentry;
10
+ use super::file::FMode;
11
+ use super::inode::Mask;
12
+ use super::mount::VfsMount;
13
+ use super::openat2::OpenHow;
14
+ use super::{FileType, FilesystemError};
15
+
16
+ /// What one security module keeps on an open file, as Linux's `f_security`
17
+ /// blob holds each module's part; `None` where it keeps nothing.
18
+ pub type FileSecurity = Option<Arc<dyn Any + Send + Sync>>;
19
+
20
+ /// A path a security hook is asked about: the mount it stands on, and its name
21
+ /// from that mount's root, built only for a module that reads it, as `struct path`
22
+ /// leaves `d_path` to the module that needs one. The name is what
23
+ /// `Dentry::path_raw` answers for the entry when the module asks.
24
+ #[derive(Clone, Copy)]
25
+ pub struct SecurityPath<'a> {
26
+ pub mnt: &'a Arc<VfsMount>,
27
+ pub name: &'a dyn Fn() -> String,
28
+ }
29
+
30
+ /// Answer `hook` for `dentry` on `mnt`, its name read from where the dentry
31
+ /// stands when a module asks, as a module builds one from the `struct path` a
32
+ /// `security_*` hook is handed.
33
+ pub(crate) fn on_path<T>(
34
+ mnt: &Arc<VfsMount>,
35
+ dentry: &Dentry,
36
+ hook: impl FnOnce(SecurityPath<'_>) -> T,
37
+ ) -> T {
38
+ hook(SecurityPath {
39
+ mnt,
40
+ name: &|| dentry.path_raw(),
41
+ })
42
+ }
43
+
44
+ /// What a security module answers, as a hook in Linux's `lsm_hook_defs.h`. A hook
45
+ /// a module does not have allows the call, and a module can only refuse.
46
+ pub trait SecurityOperations: Send + Sync {
47
+ /// Whether a call may do what `mask` asks of what `dentry` names, as
48
+ /// `security_inode_permission` is asked once the entry itself allowed it.
49
+ fn inode_permission(&self, _dentry: &Dentry, _mask: Mask) -> Result<(), FilesystemError> {
50
+ Ok(())
51
+ }
52
+
53
+ /// Whether a file may be made at `path`, as `security_path_mknod`.
54
+ fn path_mknod(&self, _path: SecurityPath<'_>) -> Result<(), FilesystemError> {
55
+ Ok(())
56
+ }
57
+
58
+ /// Whether a directory may be made at `path`, as `security_path_mkdir`.
59
+ fn path_mkdir(&self, _path: SecurityPath<'_>) -> Result<(), FilesystemError> {
60
+ Ok(())
61
+ }
62
+
63
+ /// Whether the directory at `path` may go, as `security_path_rmdir`.
64
+ fn path_rmdir(&self, _path: SecurityPath<'_>) -> Result<(), FilesystemError> {
65
+ Ok(())
66
+ }
67
+
68
+ /// Whether the file at `path` may go, as `security_path_unlink`.
69
+ fn path_unlink(&self, _path: SecurityPath<'_>) -> Result<(), FilesystemError> {
70
+ Ok(())
71
+ }
72
+
73
+ /// Whether a link may be made at `path`, as `security_path_symlink`.
74
+ fn path_symlink(&self, _path: SecurityPath<'_>) -> Result<(), FilesystemError> {
75
+ Ok(())
76
+ }
77
+
78
+ /// Whether what `old` names may also go by `new`, as `security_path_link`.
79
+ fn path_link(
80
+ &self,
81
+ _old: SecurityPath<'_>,
82
+ _new: SecurityPath<'_>,
83
+ ) -> Result<(), FilesystemError> {
84
+ Ok(())
85
+ }
86
+
87
+ /// Whether what `old` names may move to `new`, the dentries of each given, as
88
+ /// `security_path_rename` is handed both.
89
+ fn path_rename(
90
+ &self,
91
+ _old: SecurityPath<'_>,
92
+ _new: SecurityPath<'_>,
93
+ _dentries: (&Arc<Dentry>, &Arc<Dentry>),
94
+ ) -> Result<(), FilesystemError> {
95
+ Ok(())
96
+ }
97
+
98
+ /// Whether a file of `kind` at `path` may be opened for `mode` as `how` asks,
99
+ /// as `security_file_open` reads `f_mode` and `f_flags` once `may_open`
100
+ /// allowed it, a file the open just made included, which `FMode::CREATED`
101
+ /// marks. What the module keeps on the open file answers its later hooks, as
102
+ /// Landlock keeps what it allowed in the file's security blob.
103
+ fn file_open(
104
+ &self,
105
+ _path: SecurityPath<'_>,
106
+ _kind: FileType,
107
+ _mode: FMode,
108
+ _how: &OpenHow,
109
+ ) -> Result<FileSecurity, FilesystemError> {
110
+ Ok(None)
111
+ }
112
+
113
+ /// Whether the open file may be truncated, judged from what this module kept
114
+ /// on it as it opened, as Landlock's `hook_file_truncate` reads the file's
115
+ /// blob.
116
+ fn file_truncate(&self, _kept: &FileSecurity) -> Result<(), FilesystemError> {
117
+ Ok(())
118
+ }
119
+
120
+ /// Whether times may be set on the entry at `path`. Linux has no path hook
121
+ /// for times, so this is kobako's own, beside `path_truncate` and
122
+ /// `path_chmod` in kind, letting a module judge the path a call names.
123
+ fn path_utimes(&self, _path: SecurityPath<'_>) -> Result<(), FilesystemError> {
124
+ Ok(())
125
+ }
126
+
127
+ /// Whether where the link `dentry` names points may be read, as
128
+ /// `security_inode_readlink` is handed the link.
129
+ fn inode_readlink(&self, _dentry: &Dentry) -> Result<(), FilesystemError> {
130
+ Ok(())
131
+ }
132
+ }
133
+
134
+ /// How many bytes Filesystems hold on one caller's behalf between calls, and the
135
+ /// most they may, as Linux's `__vm_enough_memory` weighs what `vm_committed_as`
136
+ /// counts against its limit, so a guest's descriptors cannot multiply a directory
137
+ /// past what it was allotted.
138
+ pub(crate) struct VmCommitted {
139
+ limit: usize,
140
+ used: AtomicUsize,
141
+ }
142
+
143
+ impl VmCommitted {
144
+ pub(crate) fn new(limit: usize) -> Arc<Self> {
145
+ Arc::new(Self {
146
+ limit,
147
+ used: AtomicUsize::new(0),
148
+ })
149
+ }
150
+ }
151
+
152
+ /// Who a call is made for, holding the security modules registered for it, as
153
+ /// `cred->security` holds a Landlock domain, and the memory held on its behalf,
154
+ /// which every Cred restricted from it shares, as a task's memory cgroup does.
155
+ pub struct Cred {
156
+ security: Vec<Arc<dyn SecurityOperations>>,
157
+ committed: Option<Arc<VmCommitted>>,
158
+ }
159
+
160
+ /// One no module is registered for, which this crate's tests make calls as.
161
+ #[cfg(test)]
162
+ impl Default for Cred {
163
+ fn default() -> Self {
164
+ Self {
165
+ security: Vec::new(),
166
+ committed: None,
167
+ }
168
+ }
169
+ }
170
+
171
+ impl Cred {
172
+ /// One with `security` registered.
173
+ pub(crate) fn new(security: Vec<Arc<dyn SecurityOperations>>) -> Arc<Self> {
174
+ Self::within(security, None)
175
+ }
176
+
177
+ /// One with `security` registered and the memory held on its behalf counted
178
+ /// in `committed`, without bound where none.
179
+ pub(crate) fn within(
180
+ security: Vec<Arc<dyn SecurityOperations>>,
181
+ committed: Option<Arc<VmCommitted>>,
182
+ ) -> Arc<Self> {
183
+ Arc::new(Self {
184
+ security,
185
+ committed,
186
+ })
187
+ }
188
+
189
+ /// This Cred with `more` modules registered after its own, sharing the
190
+ /// memory held on its behalf, as `landlock_restrict_self` adds a domain.
191
+ pub(crate) fn with_modules(&self, more: Vec<Arc<dyn SecurityOperations>>) -> Arc<Self> {
192
+ let mut security = self.security.clone();
193
+ security.extend(more);
194
+ Self::within(security, self.committed.clone())
195
+ }
196
+
197
+ /// Ask each module in turn, the first refusal answering, as `call_int_hook`.
198
+ fn call_int_hook(
199
+ &self,
200
+ hook: impl Fn(&dyn SecurityOperations) -> Result<(), FilesystemError>,
201
+ ) -> Result<(), FilesystemError> {
202
+ self.security
203
+ .iter()
204
+ .try_for_each(|module| hook(module.as_ref()))
205
+ }
206
+
207
+ pub(crate) fn security_inode_permission(
208
+ &self,
209
+ dentry: &Dentry,
210
+ mask: Mask,
211
+ ) -> Result<(), FilesystemError> {
212
+ self.call_int_hook(|module| module.inode_permission(dentry, mask))
213
+ }
214
+
215
+ pub(crate) fn security_path_mknod(
216
+ &self,
217
+ path: SecurityPath<'_>,
218
+ ) -> Result<(), FilesystemError> {
219
+ self.call_int_hook(|module| module.path_mknod(path))
220
+ }
221
+
222
+ pub(crate) fn security_path_mkdir(
223
+ &self,
224
+ path: SecurityPath<'_>,
225
+ ) -> Result<(), FilesystemError> {
226
+ self.call_int_hook(|module| module.path_mkdir(path))
227
+ }
228
+
229
+ pub(crate) fn security_path_rmdir(
230
+ &self,
231
+ path: SecurityPath<'_>,
232
+ ) -> Result<(), FilesystemError> {
233
+ self.call_int_hook(|module| module.path_rmdir(path))
234
+ }
235
+
236
+ pub(crate) fn security_path_unlink(
237
+ &self,
238
+ path: SecurityPath<'_>,
239
+ ) -> Result<(), FilesystemError> {
240
+ self.call_int_hook(|module| module.path_unlink(path))
241
+ }
242
+
243
+ pub(crate) fn security_path_symlink(
244
+ &self,
245
+ path: SecurityPath<'_>,
246
+ ) -> Result<(), FilesystemError> {
247
+ self.call_int_hook(|module| module.path_symlink(path))
248
+ }
249
+
250
+ pub(crate) fn security_path_link(
251
+ &self,
252
+ old: SecurityPath<'_>,
253
+ new: SecurityPath<'_>,
254
+ ) -> Result<(), FilesystemError> {
255
+ self.call_int_hook(|module| module.path_link(old, new))
256
+ }
257
+
258
+ pub(crate) fn security_path_rename(
259
+ &self,
260
+ old: SecurityPath<'_>,
261
+ new: SecurityPath<'_>,
262
+ dentries: (&Arc<Dentry>, &Arc<Dentry>),
263
+ ) -> Result<(), FilesystemError> {
264
+ self.call_int_hook(|module| module.path_rename(old, new, dentries))
265
+ }
266
+
267
+ /// A file's room for what each module keeps on it, empty until an open
268
+ /// fills it, as `security_file_alloc` gives every file one blob per module.
269
+ pub(crate) fn security_file_alloc(&self) -> Vec<FileSecurity> {
270
+ vec![None; self.security.len()]
271
+ }
272
+
273
+ /// Ask each module about the open, keeping what each keeps on the file in
274
+ /// the order the modules stand, as `security_file_open` fills `f_security`.
275
+ pub(crate) fn security_file_open(
276
+ &self,
277
+ path: SecurityPath<'_>,
278
+ kind: FileType,
279
+ mode: FMode,
280
+ how: &OpenHow,
281
+ ) -> Result<Vec<FileSecurity>, FilesystemError> {
282
+ self.security
283
+ .iter()
284
+ .map(|module| module.file_open(path, kind, mode, how))
285
+ .collect()
286
+ }
287
+
288
+ /// Ask each module whether the file may be truncated, handing each what it
289
+ /// kept on the file as it opened.
290
+ pub(crate) fn security_file_truncate(
291
+ &self,
292
+ kept: &[FileSecurity],
293
+ ) -> Result<(), FilesystemError> {
294
+ self.security
295
+ .iter()
296
+ .zip(kept)
297
+ .try_for_each(|(module, kept)| module.file_truncate(kept))
298
+ }
299
+
300
+ pub(crate) fn security_path_utimes(
301
+ &self,
302
+ path: SecurityPath<'_>,
303
+ ) -> Result<(), FilesystemError> {
304
+ self.call_int_hook(|module| module.path_utimes(path))
305
+ }
306
+
307
+ pub(crate) fn security_inode_readlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
308
+ self.call_int_hook(|module| module.inode_readlink(dentry))
309
+ }
310
+
311
+ /// Count `bytes` more held on this caller's behalf, refusing them past its
312
+ /// limit, as `security_vm_enough_memory_mm` ends in `__vm_enough_memory`.
313
+ pub(crate) fn security_vm_enough_memory(&self, bytes: usize) -> Result<(), FilesystemError> {
314
+ let Some(committed) = &self.committed else {
315
+ return Ok(());
316
+ };
317
+ committed
318
+ .used
319
+ .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |used| {
320
+ used.checked_add(bytes)
321
+ .filter(|&total| total <= committed.limit)
322
+ })
323
+ .map(|_| ())
324
+ .map_err(|_| FilesystemError::OutOfMemory)
325
+ }
326
+
327
+ /// `bytes` counted earlier are held no more, as `vm_unacct_memory` gives
328
+ /// them back.
329
+ pub(crate) fn vm_unacct_memory(&self, bytes: usize) {
330
+ if let Some(committed) = &self.committed {
331
+ committed.used.fetch_sub(bytes, Ordering::Relaxed);
332
+ }
333
+ }
334
+ }