ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
bootstrap_uv_exec.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Execute authenticated uv bytes without reopening a mutable cache path."""
4
5from __future__ import annotations
6
7import importlib
8import os
9import secrets
10import stat
11import subprocess
12import sys
13import tempfile
14from collections.abc import Iterator
15from contextlib import contextmanager, suppress
16from pathlib import Path
17from typing import NoReturn
18
19PROBE_EXECUTABLE_MODE = 0o500
20PRIVATE_TEMPORARY_DIRECTORY_MODE = 0o700
21PRIVATE_TEMPORARY_FILE_MODE = 0o600
22MAX_CACHE_PATH_COMPONENTS = 64
23MAX_TEMPORARY_NAME_ATTEMPTS = 16
24CACHE_DIRECTORY_MODE = 0o755
25DARWIN_ROOT_ALIASES = {
26 "tmp": ("private", "tmp"),
27 "var": ("private", "var"),
28}
29DARWIN_ROOT_ALIAS_POSITIONS = frozenset(
30 (0, "darwin", component) for component in DARWIN_ROOT_ALIASES
31)
32
33
34class UvExecError(RuntimeError):
35 """Report a fail-closed anonymous-execution boundary failure."""
36
37
38def fail(message: str) -> NoReturn:
39 """Raise one execution-boundary error."""
40 raise UvExecError(message)
41
42
43def _stat_identity(state: os.stat_result) -> tuple[int, int]:
44 """Return the filesystem identity fields used by alias binding."""
45 return state.st_dev, state.st_ino
46
47
48def _link_fingerprint(state: os.stat_result) -> tuple[int, ...]:
49 """Return metadata that must remain stable across one alias proof."""
50 return (
51 state.st_dev,
52 state.st_ino,
53 state.st_mode,
54 state.st_uid,
55 state.st_gid,
56 state.st_size,
57 state.st_mtime_ns,
58 state.st_ctime_ns,
59 )
60
61
62def _require_trusted_system_root(descriptor: int) -> None:
63 """Require a held descriptor to name the immutable system root."""
64 held = os.fstat(descriptor)
65 named = Path("/").stat(follow_symlinks=False)
66 unsafe_mode = stat.S_IMODE(held.st_mode) & 0o022
67 if _stat_identity(held) != _stat_identity(named):
68 fail("Darwin uv cache alias root changed identity")
69 if not stat.S_ISDIR(held.st_mode):
70 fail("Darwin uv cache alias root is not a directory")
71 if held.st_uid != 0:
72 fail("Darwin uv cache alias root is not root-owned")
73 if unsafe_mode:
74 fail("Darwin uv cache alias requires the trusted system root")
75
76
77def _open_physical_alias_target(
78 root_descriptor: int, components: tuple[str, ...], flags: int
79) -> int:
80 """Open an allowlisted alias target without following any component."""
81 descriptor = -1
82 current = root_descriptor
83 try:
84 for component in components:
85 next_descriptor = os.open(component, flags, dir_fd=current)
86 if descriptor >= 0:
87 os.close(descriptor)
88 descriptor = next_descriptor
89 current = descriptor
90 except OSError:
91 if descriptor >= 0:
92 os.close(descriptor)
93 raise
94 return descriptor
95
96
97def _open_verified_darwin_alias(root_descriptor: int, component: str, flags: int) -> int:
98 """Open one fixed Darwin root alias and bind it to its physical target."""
99 target = DARWIN_ROOT_ALIASES.get(component)
100 if target is None:
101 fail(f"unsupported Darwin uv cache root alias: {component}")
102 expected = "/".join(target)
103 before = os.stat(component, dir_fd=root_descriptor, follow_symlinks=False)
104 before_target = os.readlink(component, dir_fd=root_descriptor)
105 if not stat.S_ISLNK(before.st_mode):
106 fail(f"Darwin uv cache root alias is not a symlink: /{component}")
107 if before.st_uid != 0:
108 fail(f"Darwin uv cache root alias is not root-owned: /{component}")
109 if before_target != expected:
110 fail(f"untrusted Darwin uv cache root alias: /{component}")
111 alias_descriptor = -1
112 physical_descriptor = -1
113 succeeded = False
114 try:
115 alias_descriptor = os.open(
116 component,
117 flags & ~os.O_NOFOLLOW,
118 dir_fd=root_descriptor,
119 )
120 physical_descriptor = _open_physical_alias_target(root_descriptor, target, flags)
121 alias_state = os.fstat(alias_descriptor)
122 physical_state = os.fstat(physical_descriptor)
123 after = os.stat(component, dir_fd=root_descriptor, follow_symlinks=False)
124 after_target = os.readlink(component, dir_fd=root_descriptor)
125 if not stat.S_ISDIR(alias_state.st_mode):
126 fail(f"Darwin uv cache alias target is not a directory: /{component}")
127 if not stat.S_ISDIR(physical_state.st_mode):
128 fail(f"Darwin uv cache physical target is not a directory: /{component}")
129 if _stat_identity(alias_state) != _stat_identity(physical_state):
130 fail(f"Darwin uv cache root alias target mismatched: /{component}")
131 if _link_fingerprint(before) != _link_fingerprint(after):
132 fail(f"Darwin uv cache root alias changed identity: /{component}")
133 if after_target != expected:
134 fail(f"Darwin uv cache root alias changed target: /{component}")
135 succeeded = True
136 finally:
137 if alias_descriptor >= 0:
138 os.close(alias_descriptor)
139 if not succeeded and physical_descriptor >= 0:
140 os.close(physical_descriptor)
141 return physical_descriptor
142
143
144def open_parent_components(
145 descriptor: int,
146 components: tuple[str, ...],
147 flags: int,
148 *,
149 create: bool,
150 platform_name: str,
151) -> int:
152 """Walk parent components, allowing only the two proven Darwin aliases."""
153 try:
154 for index, component in enumerate(components):
155 try:
156 next_descriptor = -1
157 alias_key = index, platform_name, component
158 if alias_key in DARWIN_ROOT_ALIAS_POSITIONS:
159 _require_trusted_system_root(descriptor)
160 next_descriptor = _open_verified_darwin_alias(descriptor, component, flags)
161 if next_descriptor < 0:
162 next_descriptor = os.open(component, flags, dir_fd=descriptor)
163 except FileNotFoundError:
164 if not create:
165 raise
166 with suppress(FileExistsError):
167 os.mkdir(component, CACHE_DIRECTORY_MODE, dir_fd=descriptor)
168 next_descriptor = os.open(component, flags, dir_fd=descriptor)
169 previous = descriptor
170 descriptor = next_descriptor
171 os.close(previous)
172 except Exception:
173 os.close(descriptor)
174 raise
175 return descriptor
176
177
178def _open_parent_fd(path: Path, *, create: bool = False) -> int:
179 """Open or create an absolute parent without following path components."""
180 nofollow = getattr(os, "O_NOFOLLOW", None)
181 cloexec = getattr(os, "O_CLOEXEC", None)
182 directory = getattr(os, "O_DIRECTORY", None)
183 if nofollow is None or cloexec is None or directory is None:
184 fail("POSIX uv cache access requires O_NOFOLLOW, O_CLOEXEC, and O_DIRECTORY")
185 if not path.is_absolute() or path.name in ("", ".", ".."):
186 fail(f"uv cache artifact path is not an absolute file path: {path}")
187 components = path.parent.parts[1:]
188 if len(components) > MAX_CACHE_PATH_COMPONENTS:
189 fail(f"uv cache artifact path has too many components: {path}")
190 flags = os.O_RDONLY | nofollow | cloexec | directory
191 descriptor = -1
192 try:
193 descriptor = os.open(path.anchor, flags)
194 root_descriptor = descriptor
195 descriptor = -1
196 descriptor = open_parent_components(
197 root_descriptor,
198 components,
199 flags,
200 create=create,
201 platform_name=sys.platform,
202 )
203 except (OSError, NotImplementedError, TypeError) as exc:
204 if descriptor >= 0:
205 os.close(descriptor)
206 fail(f"cannot open cached uv parent {path.parent}: {exc}")
207 return descriptor
208
209
210def _new_temporary_fd(parent: int, mode: int = PRIVATE_TEMPORARY_FILE_MODE) -> tuple[int, str]:
211 """Create one unpredictable private file relative to a held parent FD."""
212 cloexec = getattr(os, "O_CLOEXEC", None)
213 nofollow = getattr(os, "O_NOFOLLOW", None)
214 if cloexec is None or nofollow is None:
215 fail("POSIX uv cache writes require O_CLOEXEC and O_NOFOLLOW")
216 if mode not in (PROBE_EXECUTABLE_MODE, PRIVATE_TEMPORARY_FILE_MODE):
217 fail("POSIX uv cache temporary mode is outside policy")
218 flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | cloexec | nofollow
219 for _attempt in range(MAX_TEMPORARY_NAME_ATTEMPTS):
220 name = f".ra8-uv-{secrets.token_hex(16)}.tmp"
221 try:
222 return os.open(name, flags, mode, dir_fd=parent), name
223 except FileExistsError:
224 continue
225 fail("cannot allocate a private uv cache temporary")
226
227
228def write_atomic_nofollow(path: Path, payload: bytes, mode: int) -> None:
229 """Atomically write bytes within one held no-follow parent directory."""
230 if not payload or mode not in (0o600, 0o700):
231 fail("uv cache write requires nonempty bytes and one private mode")
232 parent = _open_parent_fd(path, create=True)
233 descriptor = -1
234 temporary = ""
235 try:
236 descriptor, temporary = _new_temporary_fd(parent)
237 write_exact_fd(descriptor, payload)
238 os.fsync(descriptor)
239 os.fchmod(descriptor, mode)
240 os.replace(temporary, path.name, src_dir_fd=parent, dst_dir_fd=parent)
241 except (OSError, NotImplementedError, TypeError) as exc:
242 fail(f"cannot write cached uv artifact {path}: {exc}")
243 finally:
244 if descriptor >= 0:
245 os.close(descriptor)
246 if temporary:
247 with suppress(FileNotFoundError):
248 os.unlink(temporary, dir_fd=parent)
249 os.close(parent)
250
251
252def open_regular_nofollow(path: Path) -> int:
253 """Open one single-link regular file relative to a held safe parent."""
254 nonblock = getattr(os, "O_NONBLOCK", None)
255 cloexec = getattr(os, "O_CLOEXEC", None)
256 nofollow = getattr(os, "O_NOFOLLOW", None)
257 if nonblock is None or cloexec is None or nofollow is None:
258 fail("POSIX uv cache access requires O_NONBLOCK, O_CLOEXEC, and O_NOFOLLOW")
259 parent = _open_parent_fd(path)
260 descriptor = -1
261 try:
262 descriptor = os.open(
263 path.name,
264 os.O_RDONLY | nonblock | cloexec | nofollow,
265 dir_fd=parent,
266 )
267 state = os.fstat(descriptor)
268 except OSError as exc:
269 if descriptor >= 0:
270 os.close(descriptor)
271 fail(f"cannot open cached uv artifact {path}: {exc}")
272 finally:
273 os.close(parent)
274 if not stat.S_ISREG(state.st_mode) or state.st_nlink != 1:
275 os.close(descriptor)
276 fail(f"cached uv artifact is not one single-link regular file: {path}")
277 return descriptor
278
279
280def executable_fd_path(descriptor: int) -> str:
281 """Return a system descriptor path that cannot reopen a cache pathname."""
282 for root in (Path("/proc/self/fd"), Path("/dev/fd")):
283 if root.is_dir():
284 return str(root / str(descriptor))
285 fail("POSIX uv execution requires /proc/self/fd or /dev/fd")
286
287
288def write_exact_fd(descriptor: int, binary: bytes) -> None:
289 """Write every authenticated byte to one private executable descriptor."""
290 with os.fdopen(os.dup(descriptor), "wb") as target:
291 written = target.write(binary)
292 target.flush()
293 if written != len(binary):
294 fail("cannot stage the authenticated uv executable")
295 os.lseek(descriptor, 0, os.SEEK_SET)
296
297
298def linux_sealed_exec_fd(binary: bytes) -> int:
299 """Return a sealed Linux memory descriptor containing authenticated uv."""
300 cloexec = getattr(os, "MFD_CLOEXEC", None)
301 allow_sealing = getattr(os, "MFD_ALLOW_SEALING", None)
302 if cloexec is None or allow_sealing is None or not hasattr(os, "memfd_create"):
303 fail("Linux authenticated uv execution requires sealed memfd support")
304 seals = importlib.import_module("fcntl")
305 seal_names = ("F_SEAL_WRITE", "F_SEAL_GROW", "F_SEAL_SHRINK", "F_SEAL_SEAL")
306 if any(not hasattr(seals, name) for name in (*seal_names, "F_ADD_SEALS", "F_GET_SEALS")):
307 fail("Linux authenticated uv execution requires file-seal support")
308 descriptor = os.memfd_create("ra8-authenticated-uv", cloexec | allow_sealing)
309 succeeded = False
310 try:
311 write_exact_fd(descriptor, binary)
312 os.fchmod(descriptor, PROBE_EXECUTABLE_MODE)
313 mask = sum(getattr(seals, name) for name in seal_names)
314 seals.fcntl(descriptor, seals.F_ADD_SEALS, mask)
315 if seals.fcntl(descriptor, seals.F_GET_SEALS) & mask != mask:
316 fail("authenticated uv memory descriptor did not seal")
317 succeeded = True
318 finally:
319 if not succeeded:
320 os.close(descriptor)
321 return descriptor
322
323
324def _verify_portable_exec_fd(
325 descriptor: int,
326 binary: bytes,
327 identity: tuple[int, int],
328 *,
329 linked: bool,
330) -> None:
331 """Authenticate one portable execution FD before and after unlinking."""
332 controls = importlib.import_module("fcntl")
333 access_mode = controls.fcntl(descriptor, controls.F_GETFL) & os.O_ACCMODE
334 descriptor_flags = controls.fcntl(descriptor, controls.F_GETFD)
335 state = os.fstat(descriptor)
336 expected_links = 1 if linked else 0
337 if access_mode != os.O_RDONLY:
338 fail("authenticated uv descriptor did not reopen read-only")
339 if descriptor_flags & controls.FD_CLOEXEC == 0:
340 fail("authenticated uv descriptor is not close-on-exec")
341 if _stat_identity(state) != identity:
342 fail("authenticated uv descriptor changed identity")
343 if not stat.S_ISREG(state.st_mode) or state.st_nlink != expected_links:
344 fail("authenticated uv descriptor is not one private regular file")
345 if stat.S_IMODE(state.st_mode) != PROBE_EXECUTABLE_MODE:
346 fail("authenticated uv descriptor has the wrong executable mode")
347 if state.st_uid != os.geteuid() or state.st_size != len(binary):
348 fail("authenticated uv descriptor has untrusted ownership or size")
349 try:
350 payload = os.pread(descriptor, len(binary) + 1, 0)
351 except (AttributeError, OSError) as exc:
352 fail(f"cannot read authenticated uv descriptor: {exc}")
353 if not secrets.compare_digest(payload, binary):
354 fail("authenticated uv descriptor bytes changed")
355
356
357def _unlink_matching_temporary(
358 parent: int,
359 name: str,
360 identity: tuple[int, int],
361 *,
362 required: bool,
363) -> None:
364 """Unlink a temporary name only while it retains the authenticated inode."""
365 try:
366 state = os.stat(name, dir_fd=parent, follow_symlinks=False)
367 except FileNotFoundError:
368 if required:
369 fail("authenticated uv temporary vanished before unlink")
370 return
371 if _stat_identity(state) != identity:
372 if required:
373 fail("authenticated uv temporary changed identity before unlink")
374 return
375 os.unlink(name, dir_fd=parent)
376
377
378def _require_private_temporary_parent(descriptor: int) -> None:
379 """Require the held portable-snapshot directory to be caller-private."""
380 state = os.fstat(descriptor)
381 if not stat.S_ISDIR(state.st_mode):
382 fail("authenticated uv temporary parent is not a directory")
383 if state.st_uid != os.geteuid():
384 fail("authenticated uv temporary parent has the wrong owner")
385 if stat.S_IMODE(state.st_mode) != PRIVATE_TEMPORARY_DIRECTORY_MODE:
386 fail("authenticated uv temporary parent is not private")
387
388
389def _verify_portable_exec_name(
390 parent: int,
391 name: str,
392 binary: bytes,
393 identity: tuple[int, int],
394) -> None:
395 """Reopen and authenticate the private executable name relative to its parent."""
396 flags = os.O_RDONLY | os.O_NONBLOCK | os.O_CLOEXEC | os.O_NOFOLLOW
397 descriptor = -1
398 try:
399 descriptor = os.open(name, flags, dir_fd=parent)
400 _verify_portable_exec_fd(descriptor, binary, identity, linked=True)
401 finally:
402 if descriptor >= 0:
403 os.close(descriptor)
404
405
406@contextmanager
407def portable_named_exec_snapshot(binary: bytes) -> Iterator[tuple[int, str]]:
408 """Yield a read-only executable under one private non-Linux POSIX name.
409
410 The private-name window excludes other OS identities and all cache-path
411 races. Like the surrounding checkout and caller, it is not an integrity
412 boundary against a malicious peer process running under the same UID.
413 """
414 if not binary:
415 fail("authenticated uv executable bytes are empty")
416 reader = -1
417 writer = -1
418 parent = -1
419 temporary = ""
420 identity = (-1, -1)
421 try:
422 with tempfile.TemporaryDirectory(prefix="ra8-uv-probe-") as raw:
423 probe_path = Path(raw) / "probe"
424 parent = _open_parent_fd(probe_path)
425 _require_private_temporary_parent(parent)
426 writer, temporary = _new_temporary_fd(parent, PROBE_EXECUTABLE_MODE)
427 write_exact_fd(writer, binary)
428 os.fsync(writer)
429 os.fchmod(writer, PROBE_EXECUTABLE_MODE)
430 writer_state = os.fstat(writer)
431 identity = _stat_identity(writer_state)
432 flags = os.O_RDONLY | os.O_NONBLOCK | os.O_CLOEXEC | os.O_NOFOLLOW
433 reader = os.open(temporary, flags, dir_fd=parent)
434 if _stat_identity(os.fstat(reader)) != identity:
435 fail("authenticated uv reader did not reopen the staged inode")
436 os.close(writer)
437 writer = -1
438 _verify_portable_exec_fd(reader, binary, identity, linked=True)
439 _verify_portable_exec_name(parent, temporary, binary, identity)
440 try:
441 yield reader, str(Path(raw) / temporary)
442 finally:
443 _verify_portable_exec_fd(reader, binary, identity, linked=True)
444 _verify_portable_exec_name(parent, temporary, binary, identity)
445 _unlink_matching_temporary(parent, temporary, identity, required=True)
446 os.fsync(parent)
447 temporary = ""
448 _verify_portable_exec_fd(reader, binary, identity, linked=False)
449 except (OSError, NotImplementedError, TypeError) as exc:
450 fail(f"cannot stage portable authenticated uv executable: {exc}")
451 finally:
452 if writer >= 0:
453 os.close(writer)
454 if temporary and parent >= 0:
455 with suppress(OSError):
456 _unlink_matching_temporary(
457 parent,
458 temporary,
459 identity,
460 required=False,
461 )
462 if parent >= 0:
463 os.close(parent)
464 if reader >= 0:
465 os.close(reader)
466
467
468@contextmanager
469def authenticated_executable_fd(binary: bytes) -> Iterator[tuple[int, str]]:
470 """Yield one authenticated execution descriptor and its invocation path."""
471 if os.name != "posix":
472 fail("authenticated uv execution requires POSIX; use WSL on Windows")
473 if sys.platform.startswith("linux"):
474 descriptor = linux_sealed_exec_fd(binary)
475 try:
476 yield descriptor, executable_fd_path(descriptor)
477 finally:
478 os.close(descriptor)
479 else:
480 with portable_named_exec_snapshot(binary) as snapshot:
481 yield snapshot
482
483
484def run_uv_snapshot(
485 binary: bytes,
486 arguments: list[str],
487 *,
488 capture_output: bool = False,
489 timeout: int | None = None,
490) -> subprocess.CompletedProcess[str]:
491 """Run exact uv arguments from an authenticated immutable snapshot."""
492 try:
493 with authenticated_executable_fd(binary) as (descriptor, executable):
494 return subprocess.run( # noqa: S603 -- exact immutable FD.
495 [executable, *arguments],
496 check=False,
497 capture_output=capture_output,
498 text=True,
499 timeout=timeout,
500 pass_fds=(descriptor,),
501 )
502 except (OSError, subprocess.TimeoutExpired) as exc:
503 fail(f"authenticated uv execution failed: {exc}")