1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
//! The SANCTIONED unsafe basement for the Linux backend (kernel plan §10.8).
//!
//! This file is the ONE quarantine where the Linux backend's raw-syscall `unsafe`
//! is permitted to live. Every `unsafe` block below is registered in
//! `traceability/unsafe_ledger.yaml`:
//!
//! STEP (b) — REAL filesystem confinement:
//! 1. `probe_landlock_abi` — the raw `landlock_create_ruleset(NULL, 0,
//! LANDLOCK_CREATE_RULESET_VERSION)` version query. The landlock crate caps
//! the ABI it MODELS at its own latest known version, so the honest live
//! kernel ABI integer is read straight from the kernel here. This is the
//! backend's ONLY remaining filesystem-confinement `unsafe`: the live ABI
//! probe that backs `profile()`'s `Filesystem=Enforced` cell. The landlock
//! ENFORCEMENT (ruleset build + `restrict_self`) now lives in the LAUNCHER's
//! child window (`launcher/linux/sys.rs`); the backend's old self-spawn
//! `pre_exec` confinement (`spawn_confined`) was removed in the backend→launcher
//! rewire (step 7b) so there is exactly ONE confinement path.
//!
//! STEP 7a — the HOST-SIDE launcher harness basement (the launcher-rewire foundation,
//! consumed by the SAFE [`super::launch`] module):
//! 3. `seal_plan_memfd` — `memfd_create(MFD_CLOEXEC|MFD_ALLOW_SEALING)` then
//! `fcntl(F_ADD_SEALS, …)` to produce a tamper-proof, read-only plan transport
//! the launcher reads (anchors `linux-backend-memfd-seal` + `-add-seals`).
//! 4. `spawn_launcher_with_fds` — `Command::spawn` of the launcher bin with a
//! post-fork `pre_exec` that ONLY `dup2`/`fcntl`s a PRE-BUILT fd map onto fixed
//! target numbers (async-signal-safe: no allocation in the closure); the source
//! fds are relocated HIGH first by `relocate_high` (anchors
//! `linux-backend-launcher-relocate` + `-pre-exec`).
//!
//! The safe orchestration in [`super::backend_impl`] and [`super::launch`] NEVER
//! contains `unsafe` — it calls down into this basement through the narrow wrappers
//! below. The landlock RULESET construction itself is SAFE (the `landlock` crate is
//! pure safe Rust); only the `pre_exec`/`memfd`/relocate raw calls are unsafe.
//!
//! GATING CONTRACT (two interlocking fail-closed gates):
//! 1. The architecture lint (`syncbat_boundary::checks_runtime_shape`) exempts
//! this `sys.rs` from the blanket sync-first/safe-Rust ban, BUT exempts
//! NOTHING else under `backend/` — `mod.rs`/`backend_impl.rs` stay covered.
//! 2. The unsafe ledger (`integrity unsafe-ledger`, folded into `structural-check`)
//! requires EVERY `unsafe` block here to have a matching ledger entry and fails
//! closed on any unmatched block OR stale entry.
use io;
use ;
use CommandExt;
use ;
/// `LANDLOCK_CREATE_RULESET_VERSION` (uapi `linux/landlock.h`): asking
/// `landlock_create_ruleset` for the supported ABI version instead of creating a
/// ruleset. Stable kernel ABI constant.
const LANDLOCK_CREATE_RULESET_VERSION: c_uint = 1;
/// Probe the LIVE landlock ABI integer straight from the kernel.
///
/// Returns the supported ABI version (`>= 1`), or `0` when landlock is
/// unavailable (old kernel / disabled LSM) — the caller floors `Filesystem` to
/// `Unsupported` below the required ABI, so `plan()` fails closed.
///
/// SAFETY (LEDGER:linux-landlock-abi-probe): `landlock_create_ruleset` is invoked in its
/// documented VERSION-QUERY form — `attr = NULL`, `size = 0`, `flags =
/// LANDLOCK_CREATE_RULESET_VERSION`. In this form the kernel reads NO user
/// memory (the NULL/0 pair is exactly what the version query requires) and only
/// returns the supported ABI as a non-negative `int`, or `-1` with `errno` set.
/// No fd is created, nothing is mutated, no pointer is dereferenced. The call is
/// therefore sound for any caller state.
pub
// ─────────────────────────────────────────────────────────────────────────────
// HOST-SIDE LAUNCHER HARNESS BASEMENT (step 7a)
//
// Two new raw-syscall surfaces the SAFE harness (`super::launch`) calls down into:
// 1. `seal_plan_memfd` — materialise the encoded launcher plan into a sealed,
// read-only `memfd` (tamper-proof plan transport). Anchor
// `LEDGER:linux-backend-memfd-seal`.
// 2. `spawn_launcher_with_fds` — `Command::spawn` the launcher bin with a
// post-fork `pre_exec` that ONLY `dup2`/`fcntl`s a PRE-BUILT fd map onto the
// fixed target fd numbers (async-signal-safe: no allocation in the closure).
// Anchor `LEDGER:linux-backend-launcher-pre-exec`.
//
// `Command::spawn` is permitted HERE: the runtime-shape no-`.spawn()` single-thread
// gate scopes to `crates/bvisor/launcher/` only (the launcher itself uses clone3),
// NOT to this backend basement. This harness is the host coordinator the launcher
// is spawned FROM.
// ─────────────────────────────────────────────────────────────────────────────
/// `MFD_CLOEXEC | MFD_ALLOW_SEALING` for `memfd_create` — a close-on-exec anonymous
/// file that can later be sealed. Stable kernel ABI flags (`linux/memfd.h`).
const MFD_FLAGS: c_uint = MFD_CLOEXEC | MFD_ALLOW_SEALING;
/// The full seal set the harness applies once the plan bytes are written:
/// `F_SEAL_SEAL` (no further seals), `F_SEAL_SHRINK`, `F_SEAL_GROW`, and
/// `F_SEAL_WRITE` (the contents are now immutable). After this the launcher's
/// `parse_and_verify` digest check is over BYTES THAT CANNOT CHANGE.
const PLAN_SEALS: c_int =
F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE;
/// Seal `bytes` (the canonically-encoded, framed [`super::protocol::LinuxLaunchPlanV1`])
/// into a read-only `memfd` and return the owned, rewound, fully-sealed descriptor —
/// the tamper-proof plan transport the launcher reads to EOF.
///
/// The returned fd is positioned at offset 0 (the launcher reads from the start) and
/// carries `F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL`, so its bytes can
/// no longer change AFTER this call — the launcher's envelope digest check is then over
/// immutable content (sealing stops post-write tampering; `parse_and_verify` already
/// stops a forged digest).
///
/// # Errors
/// Any `io::Error` from `memfd_create`, the write, the seek, or `F_ADD_SEALS`. Fails
/// closed — a partial/unsealed memfd is never returned.
///
/// SAFETY (LEDGER:linux-backend-memfd-seal): `memfd_create` is invoked with a valid
/// NUL-terminated name pointer and the documented `MFD_CLOEXEC | MFD_ALLOW_SEALING`
/// flags; it creates a fresh anonymous fd (returned, or -1/errno) and reads no caller
/// memory beyond the name string. The returned fd is immediately wrapped in an
/// `OwnedFd` (single owner, closed on drop, no double-close). The subsequent write /
/// seek / `F_ADD_SEALS` run through the OWNED fd via safe `std::fs::File` + a single
/// `fcntl(F_ADD_SEALS, PLAN_SEALS)` that only sets seal bits and dereferences no
/// pointer. `F_ADD_SEALS` cannot succeed while any writable mapping/handle other than
/// this one exists; here only the just-written owned handle exists, so the seal is
/// total. Sound for any caller state.
pub
/// One inherited fd the harness hands the launcher: the parent-owned source fd, the
/// fixed `target` fd NUMBER the launcher will see (== the descriptor-table slot index),
/// and whether the target keeps `FD_CLOEXEC`. The error-pipe WRITE end keeps CLOEXEC (so
/// a successful workload `fexecve` auto-closes it → the launcher's read end sees EOF);
/// every other inherited fd clears it (so the launcher inherits the fd across its own
/// `execve`).
pub
/// One resolved placement the launcher's `pre_exec` performs: `dup2` `src` onto the fixed
/// `target`, then toggle the target's `FD_CLOEXEC`. Plain `RawFd`/`bool` — the closure
/// indexes a pre-built slice of these, so the post-fork `dup2`/`fcntl` sequence allocates
/// nothing.
/// Relocate a parent-owned fd to a HIGH number (`>= FD_RELOCATE_BASE`) via
/// `F_DUPFD_CLOEXEC`, returning the relocated `OwnedFd` and consuming the original. This
/// keeps every dup-FROM source ABOVE every fixed dup-TO target, so the `pre_exec` `dup2`
/// sequence can never clobber a not-yet-consumed source mid-sequence (the exact hazard
/// the launcher tests document). CLOEXEC on the high copy is fine — the `pre_exec` `dup2`
/// onto the final fixed fd clears CLOEXEC there (or re-sets it for the error-write end).
///
/// SAFETY (LEDGER:linux-backend-launcher-relocate): `F_DUPFD_CLOEXEC` returns a fresh,
/// exclusively-owned fd `>= FD_RELOCATE_BASE` (or -1/errno) duplicated from the borrowed
/// `fd`; on success it is adopted ONCE as an `OwnedFd` (no aliasing, no double-close), and
/// the low original is dropped (closed) so only the high copy survives. No pointer is
/// dereferenced; this runs in the single-threaded pre-spawn parent.
const FD_RELOCATE_BASE: RawFd = 100;
/// Spawn the launcher binary `launcher_path` with the prepared inherited fds placed at
/// their fixed target numbers, returning the live [`Child`]. `env` is the explicit
/// `(name, value)` fd-number environment the launcher reads (`BVISOR_*_FD`); the
/// launcher's process environment is otherwise CLEARED. `placements` lists every fd the
/// child's `pre_exec` must `dup2` into place; the host has already RELOCATED every source
/// HIGH so the fixed targets cannot collide with a not-yet-consumed source.
///
/// The `placements` and the relocated source fds are all built in the PARENT before
/// spawn; the `pre_exec` closure captures them by move and only `dup2`/`fcntl`s — it
/// allocates nothing, so it is async-signal-safe even though the host is multithreaded.
/// The owned source fds are dropped in the PARENT after spawn (the child holds its own
/// dup2'd copies at the fixed numbers).
///
/// # Errors
/// Any `io::Error` from the relocate or from `Command::spawn`.
///
/// SAFETY (LEDGER:linux-backend-launcher-pre-exec): the post-fork `pre_exec` closure runs
/// in the forked child between `fork` and `exec` in a (formerly) multithreaded address
/// space, so it must be async-signal-safe. EVERY allocation — the relocated source fds,
/// the `placements` Vec, the env — happens BEFORE the fork in the single call below; the
/// closure captures the owned `placements` Vec + the raw error-write target by move and
/// performs ONLY `dup2` (place each source onto its fixed target) and `fcntl(F_GETFD/
/// F_SETFD)` (toggle the target's CLOEXEC bit) by INDEXING the already-allocated Vec
/// (copy-on-write read — no allocator), returning the OS errno on a `dup2` failure so the
/// spawn fails closed (the launcher never runs with a half-wired fd table). It performs
/// NO heap allocation and takes NO lock. The source fds are owned by the parent and stay
/// valid until after `spawn` returns (dropped by the caller post-spawn); each `dup2`
/// duplicates into the child's OWN fd table, so there is no cross-process aliasing.
pub
/// Apply each [`FdPlacement`] in the forked child's async-signal-safe window: `dup2` the
/// relocated source onto its fixed target, then toggle the target's `FD_CLOEXEC`. Reads
/// only the borrowed pre-built `plan` slice — NO allocation, NO lock — so it is
/// async-signal-safe. Returns the OS errno on a `dup2` failure so `pre_exec` fails the
/// spawn closed (the launcher never runs with a half-wired fd table).
///
/// # Safety
/// Callable ONLY from the post-fork `pre_exec` window of [`spawn_launcher_with_fds`]
/// (its single call site, lexically inside that `unsafe` block, ledger anchor
/// `linux-backend-launcher-pre-exec`). It indexes the already-allocated `plan` slice
/// (copy-on-write read — no allocator) and issues ONLY async-signal-safe syscalls: `dup2`
/// (place each relocated source onto its fixed target fd number) and `fcntl(F_GETFD/
/// F_SETFD)` (toggle the target's CLOEXEC bit). It performs NO heap allocation, takes NO
/// lock, and dereferences no raw pointer beyond the slice it reads. Each `dup2` duplicates
/// into the CHILD's own fd table, so there is no cross-process aliasing; the parent-owned
/// source fds stay valid until after the parent's `spawn`.
unsafe
/// The new fd-flags value: SET `FD_CLOEXEC` when `keep` (the error-pipe write end), else
/// CLEAR it (every other inherited fd). Pure integer arithmetic — async-signal-safe.