ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
devcontainer_image_selftest_supervisor.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Supervise injected image-selftest failures without reusable PID authority."""
4
5from __future__ import annotations
6
7import errno
8import hashlib
9import os
10import select
11import signal
12import stat
13import sys
14import time
15import types
16from collections.abc import Callable
17from contextlib import suppress
18from dataclasses import dataclass
19from pathlib import Path
20from typing import NoReturn
21
22MANAGED_SIGNALS = (signal.SIGHUP, signal.SIGINT, signal.SIGTERM)
23DEADLINE_SECONDS = 10.0
24POLL_SECONDS = 0.01
25CONTROLLER_ARG_COUNT = 8
26SUPERVISOR_ARG_COUNT = 7
27HIDDEN_SELFTEST_ARG_COUNT = 4
28ROOT_SELFTEST_ARG_COUNT = 3
29WATCHDOG_TIMEOUT_SECONDS = 30.0
30SELFTEST_WATCHDOG_TIMEOUT_SECONDS = 1.0
31RECEIPT_MODE = 0o600
32RECEIPT_MAX_BYTES = 64
33WRITE_ATTEMPT_MULTIPLIER = 2
34STATUS_SIZE = 2
35INJECTED_FAILURE_STATUS = 1
36PUBLIC_REFUSAL_STATUS = 125
37INTEGRITY_REFUSAL_STATUS = 126
38HARDLINK_COUNT = 2
39STALL_STATUS = 124
40MIN_POPULATED_GROUP_MEMBERS = 2
41ENTRY_MAX_BYTES = 1024 * 1024
42ENTRY_READ_STEPS = 257
43ENTRY_MODES = (0o700, 0o755)
44CASES_MODE = 0o644
45CASES_MAX_BYTES = 128 * 1024
46CASES_READ_STEPS = 33
47CASES_RAW_SHA256 = "897a5be60eec486f9f9615fead84db22f8526dba189df305f561bc1c7b5e49e7"
48CASES_ARG = "--cases-fd"
49PROCESS_MODE = 0o644
50PROCESS_MAX_BYTES = 64 * 1024
51PROCESS_READ_STEPS = 17
52PROCESS_RAW_SHA256 = "7dbb7b6fa4c477d8baea6ed43f2f1bb87a1555015dda5984fed1efb614b182f8"
53PROCESS_ARG = "--process-fd"
54PRIVATE_MODE = 0o700
55SUITE_ROOT_PREFIX = "ra8-devcontainer-image-selftest."
56SUITE_ROOT_SUFFIX_LENGTH = 32
57CANONICAL_TMP = Path(
58 "/tmp" # noqa: S108 -- fixed physical parent; random mode-0700 inode-bound direct child
59)
60
61
62@dataclass(frozen=True)
63class SupervisorRequest:
64 """Describe one immutable public supervision request."""
65
66 entry: str
67 bound: Path
68 outer: Path
69 status: Path
70 mode: str
71
72
73@dataclass(frozen=True)
74class ControllerLaunch:
75 """Bind one controller's entry, status, deadline, and test-only controls."""
76
77 entry: str
78 status: Path
79 watchdog_timeout: float
80 missing_entry_selftest: bool = False
81 entry_mutator: Callable[[], None] | None = None
82 pre_open_mutator: Callable[[], None] | None = None
83
84
85@dataclass(frozen=True)
86class SupervisionControls:
87 """Describe optional private observations without widening the public CLI."""
88
89 test_descriptor: int | None = None
90 pause_before_bound: bool = False
91 watchdog_timeout: float = WATCHDOG_TIMEOUT_SECONDS
92 missing_entry_selftest: bool = False
93 entry_mutator: Callable[[], None] | None = None
94 pre_open_mutator: Callable[[], None] | None = None
95
96
97def _refuse_entry(message: str) -> NoReturn:
98 """Raise one uniform entry-authority refusal."""
99 raise RuntimeError(message)
100
101
102def _entry_digest(descriptor: int) -> str:
103 """Hash one bounded entry descriptor without reopening its pathname."""
104 digest = hashlib.sha256()
105 total = 0
106 for _step in range(ENTRY_READ_STEPS):
107 chunk = os.pread(descriptor, 4096, total)
108 if not chunk:
109 break
110 total += len(chunk)
111 if total > ENTRY_MAX_BYTES:
112 message = "entry exceeds its byte bound"
113 _refuse_entry(message)
114 digest.update(chunk)
115 else:
116 message = "entry read exceeded its step bound"
117 _refuse_entry(message)
118 return digest.hexdigest()
119
120
121def _read_cases_source(descriptor: int) -> bytes:
122 """Read one bounded authenticated cases module without using its pathname."""
123 metadata = os.fstat(descriptor)
124 safe = (
125 stat.S_ISREG(metadata.st_mode)
126 and metadata.st_nlink == 1
127 and metadata.st_uid == os.getuid()
128 and metadata.st_gid == os.getgid()
129 and stat.S_IMODE(metadata.st_mode) == CASES_MODE
130 and 0 < metadata.st_size <= CASES_MAX_BYTES
131 )
132 if not safe:
133 message = "supervisor cases metadata is unsafe"
134 _refuse_entry(message)
135 parts = []
136 offset = 0
137 for _step in range(CASES_READ_STEPS):
138 chunk = os.pread(descriptor, 4096, offset)
139 if not chunk:
140 break
141 parts.append(chunk)
142 offset += len(chunk)
143 source = b"".join(parts)
144 if len(source) != metadata.st_size or len(source) > CASES_MAX_BYTES:
145 message = "supervisor cases read is incomplete"
146 _refuse_entry(message)
147 return source
148
149
150def _read_process_source(descriptor: int) -> bytes:
151 """Read the bounded authenticated process module without its pathname."""
152 metadata = os.fstat(descriptor)
153 safe = (
154 stat.S_ISREG(metadata.st_mode)
155 and metadata.st_nlink == 1
156 and metadata.st_uid == os.getuid()
157 and metadata.st_gid == os.getgid()
158 and stat.S_IMODE(metadata.st_mode) == PROCESS_MODE
159 and 0 < metadata.st_size <= PROCESS_MAX_BYTES
160 )
161 if not safe:
162 message = "supervisor process metadata is unsafe"
163 _refuse_entry(message)
164 parts = []
165 offset = 0
166 for _step in range(PROCESS_READ_STEPS):
167 chunk = os.pread(descriptor, 4096, offset)
168 if not chunk:
169 break
170 parts.append(chunk)
171 offset += len(chunk)
172 source = b"".join(parts)
173 if len(source) != metadata.st_size or len(source) > PROCESS_MAX_BYTES:
174 message = "supervisor process read is incomplete"
175 _refuse_entry(message)
176 return source
177
178
179def _entry_metadata_is_safe(metadata: os.stat_result) -> bool:
180 """Apply the complete reusable entry metadata predicate."""
181 return (
182 stat.S_ISREG(metadata.st_mode)
183 and metadata.st_nlink == 1
184 and metadata.st_uid == os.getuid()
185 and metadata.st_gid == os.getgid()
186 and stat.S_IMODE(metadata.st_mode) in ENTRY_MODES
187 and 0 < metadata.st_size <= ENTRY_MAX_BYTES
188 )
189
190
191def _suite_root_metadata_is_safe(metadata: os.stat_result) -> bool:
192 """Apply the complete private suite-root metadata predicate."""
193 return (
194 stat.S_ISDIR(metadata.st_mode)
195 and metadata.st_uid == os.getuid()
196 and metadata.st_gid == os.getgid()
197 and stat.S_IMODE(metadata.st_mode) == PRIVATE_MODE
198 )
199
200
201def _suite_root_path_is_safe(root: Path, metadata: os.stat_result) -> bool:
202 """Require one direct canonical-/tmp random suite-root pathname."""
203 try:
204 canonical = CANONICAL_TMP.resolve(strict=True)
205 resolved = root.resolve(strict=True)
206 except OSError:
207 return False
208 suffix = root.name.removeprefix(SUITE_ROOT_PREFIX)
209 return (
210 root.is_absolute()
211 and root.parent == canonical
212 and resolved == root
213 and len(suffix) == SUITE_ROOT_SUFFIX_LENGTH
214 and all(character in "0123456789abcdef" for character in suffix)
215 and _suite_root_metadata_is_safe(metadata)
216 )
217
218
219def _open_suite_root_authority(root: Path) -> tuple[int, tuple[int, int]]:
220 """Bind a suite root before any receipt path is derived from it."""
221 before = root.lstat()
222 if not _suite_root_path_is_safe(root, before):
223 message = "suite root path is unsafe"
224 _refuse_entry(message)
225 descriptor = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
226 try:
227 after = os.fstat(descriptor)
228 identity = (after.st_dev, after.st_ino)
229 if not _suite_root_metadata_is_safe(after) or identity != (
230 before.st_dev,
231 before.st_ino,
232 ):
233 message = "suite root identity changed while opening"
234 _refuse_entry(message)
235 except BaseException:
236 os.close(descriptor)
237 raise
238 else:
239 return descriptor, identity
240
241
242def _close_suite_root_authority(descriptor: int, root: Path, identity: tuple[int, int]) -> bool:
243 """Revalidate and close one retained root only after process cleanup."""
244 safe = False
245 try:
246 opened = os.fstat(descriptor)
247 current = root.lstat()
248 safe = (
249 _suite_root_metadata_is_safe(opened)
250 and _suite_root_path_is_safe(root, current)
251 and (opened.st_dev, opened.st_ino) == identity
252 and (current.st_dev, current.st_ino) == identity
253 )
254 except OSError:
255 safe = False
256 finally:
257 os.close(descriptor)
258 return safe
259
260
261def _anchored_root_path(descriptor: int) -> Path:
262 """Name one retained root descriptor without reopening its pathname."""
263 return Path(f"/proc/self/fd/{descriptor}")
264
265
266def _anchored_root_descriptor(path: Path) -> int:
267 """Recover the retained root descriptor from one anchored child path."""
268 parent = str(path.parent)
269 prefix = "/proc/self/fd/"
270 suffix = parent.removeprefix(prefix)
271 if parent == f"{prefix}{suffix}" and suffix.isdigit() and path.name not in ("", ".", ".."):
272 return int(suffix)
273 message = "receipt path is not rooted in a retained descriptor"
274 _refuse_entry(message)
275
276
277def _open_entry_authority(
278 path: str, pre_open_mutator: Callable[[], None] | None = None
279) -> tuple[int, tuple[int, int], str]:
280 """Bind one trusted regular entry by metadata, inode, and initial digest."""
281 before = os.lstat(path)
282 if pre_open_mutator is not None:
283 pre_open_mutator()
284 descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
285 try:
286 after = os.fstat(descriptor)
287 identity = (after.st_dev, after.st_ino)
288 safe = _entry_metadata_is_safe(before) and _entry_metadata_is_safe(after)
289 safe = safe and (before.st_dev, before.st_ino) == identity
290 if not safe:
291 message = "entry metadata is unsafe"
292 _refuse_entry(message)
293 return descriptor, identity, _entry_digest(descriptor)
294 except BaseException:
295 os.close(descriptor)
296 raise
297
298
299def _write_exact(descriptor: int, payload: bytes, declared_max: int) -> None:
300 """Write every payload byte with a finite progress and interruption bound."""
301 if declared_max < 0 or len(payload) > declared_max:
302 message = "receipt payload exceeds its declared bound"
303 raise ValueError(message)
304 if not payload:
305 return
306 attempts = 0
307 max_attempts = max(1, min(len(payload), declared_max)) * WRITE_ATTEMPT_MULTIPLIER
308 offset = 0
309 while offset < len(payload):
310 attempts += 1
311 if attempts > max_attempts:
312 raise OSError(errno.EIO, "receipt write made no bounded progress")
313 try:
314 accepted = os.write(descriptor, payload[offset:])
315 except OSError as error:
316 if error.errno == errno.EINTR:
317 continue
318 raise
319 remaining = len(payload) - offset
320 if accepted <= 0 or accepted > remaining:
321 raise OSError(errno.EIO, "receipt write returned invalid progress")
322 offset += accepted
323
324
325def _write_exclusive(path: Path, value: str) -> None:
326 """Write one non-link receipt without replacing an existing object."""
327 descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
328 try:
329 payload = value.encode("ascii")
330 _write_exact(descriptor, payload, ENTRY_MAX_BYTES)
331 os.fsync(descriptor)
332 finally:
333 os.close(descriptor)
334
335
336def _write_bound_receipt(path: Path, value: str) -> None:
337 """Write the child PID through one pre-created regular receipt."""
338 descriptor = os.open(path, os.O_WRONLY | os.O_NOFOLLOW)
339 try:
340 metadata = os.fstat(descriptor)
341 if (
342 not stat.S_ISREG(metadata.st_mode)
343 or metadata.st_nlink != 1
344 or metadata.st_uid != os.getuid()
345 or stat.S_IMODE(metadata.st_mode) != RECEIPT_MODE
346 ):
347 message = "bound receipt metadata is unsafe"
348 raise RuntimeError(message)
349 os.ftruncate(descriptor, 0)
350 payload = value.encode("ascii")
351 _write_exact(descriptor, payload, RECEIPT_MAX_BYTES)
352 os.fsync(descriptor)
353 finally:
354 os.close(descriptor)
355
356
357def _write_status_atomic(path: Path, value: str) -> None:
358 """Publish a complete status without replacing a pre-existing final path."""
359 temporary = path.with_name(f"{path.name}.tmp.{os.getpid()}")
360 _write_exclusive(temporary, value)
361 try:
362 os.link(temporary, path, follow_symlinks=False)
363 finally:
364 temporary.unlink(missing_ok=True)
365
366
367def _reset_managed_signals() -> None:
368 """Give the nested Bash fresh dispositions after controller isolation."""
369 for managed in MANAGED_SIGNALS:
370 signal.signal(managed, signal.SIG_DFL)
371
372
373def _spawn_payload(entry_authority: str, private_descriptors: tuple[int, ...]) -> int:
374 """Fork the fixed Bash payload from a bound descriptor or explicit negative."""
375 if entry_authority == "missing-entry-selftest":
376 entry = "/proc/self/fd/2147483647"
377 else:
378 descriptor = int(entry_authority)
379 metadata = os.fstat(descriptor)
380 if not _entry_metadata_is_safe(metadata):
381 message = "controller entry descriptor is unsafe"
382 _refuse_entry(message)
383 entry = f"/proc/self/fd/{descriptor}"
384 child = os.fork()
385 if child == 0:
386 for private_descriptor in private_descriptors:
387 with suppress(OSError):
388 os.close(private_descriptor)
389 _reset_managed_signals()
390 try:
391 os.execl( # noqa: S606 -- fixed Bash and descriptor-bound entry
392 "/bin/bash",
393 "bash",
394 "-p",
395 "--",
396 entry,
397 "--selftest",
398 )
399 except OSError:
400 os._exit(127)
401 return child
402
403
404def _poll_payload(child: int) -> int | None:
405 """Return and reap one terminal payload, or report that it is still live."""
406 waited, raw_status = os.waitpid(child, os.WNOHANG)
407 return None if waited == 0 else os.waitstatus_to_exitcode(raw_status)
408
409
410def _controller_status_root_is_safe(
411 status_receipt: Path, root_descriptor: int, expected_identity: str
412) -> bool:
413 """Bind the private controller to its inherited suite-root descriptor."""
414 try:
415 metadata = os.fstat(root_descriptor)
416 resolved = _anchored_root_path(root_descriptor).resolve(strict=True)
417 except OSError:
418 return False
419 identity = f"{metadata.st_dev}:{metadata.st_ino}"
420 return (
421 status_receipt.parent == _anchored_root_path(root_descriptor)
422 and _suite_root_metadata_is_safe(metadata)
423 and _suite_root_path_is_safe(resolved, metadata)
424 and identity == expected_identity
425 )
426
427
428def _controller(
429 entry_authority: str,
430 status_receipt: Path,
431 death_descriptor: int,
432 root_authority: tuple[int, str],
433 watchdog_timeout: float,
434) -> int:
435 """Publish nested status while enforcing parent death and a fixed deadline."""
436 process = os.getpid()
437 root_descriptor, root_identity = root_authority
438 if (
439 process != os.getpgrp()
440 or process != os.getsid(0)
441 or not _controller_status_root_is_safe(status_receipt, root_descriptor, root_identity)
442 ):
443 return 64
444 for managed in MANAGED_SIGNALS:
445 signal.signal(managed, signal.SIG_IGN)
446 signal.pthread_sigmask(signal.SIG_UNBLOCK, MANAGED_SIGNALS)
447 published = False
448 steps = int(watchdog_timeout / POLL_SECONDS) + 1
449 try:
450 child = _spawn_payload(entry_authority, (death_descriptor, root_descriptor))
451 for _step in range(steps):
452 ready, _, _ = select.select((death_descriptor,), (), (), POLL_SECONDS)
453 if ready:
454 os.read(death_descriptor, 1)
455 break
456 if not published:
457 child_status = _poll_payload(child)
458 if child_status is not None:
459 _write_status_atomic(status_receipt, f"{child_status}\n")
460 published = True
461 finally:
462 for private_descriptor in (death_descriptor, root_descriptor):
463 with suppress(OSError):
464 os.close(private_descriptor)
465 os.killpg(os.getpgrp(), signal.SIGKILL)
466 return 126
467
468
469def _wait_status(path: Path) -> int:
470 """Return a status within the controller watchdog plus cleanup margin."""
471 deadline = time.monotonic() + WATCHDOG_TIMEOUT_SECONDS + DEADLINE_SECONDS
472 while time.monotonic() < deadline:
473 try:
474 descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
475 except FileNotFoundError:
476 time.sleep(POLL_SECONDS)
477 continue
478 try:
479 metadata = os.fstat(descriptor)
480 if metadata.st_nlink != 1:
481 time.sleep(POLL_SECONDS)
482 continue
483 safe = (
484 stat.S_ISREG(metadata.st_mode)
485 and metadata.st_uid == os.getuid()
486 and stat.S_IMODE(metadata.st_mode) == RECEIPT_MODE
487 and metadata.st_size == STATUS_SIZE
488 )
489 value = os.read(descriptor, 3)
490 finally:
491 os.close(descriptor)
492 if safe and value == b"1\n":
493 return 1
494 message = "controller status receipt is malformed"
495 raise RuntimeError(message)
496 message = "controller status receipt timed out"
497 raise TimeoutError(message)
498
499
500def _install_interruption_handlers(supervisor: BoundGroup) -> None:
501 """Install managed-signal handlers that retry bound cleanup before exit."""
502
503 def interrupted(signum: int, _frame: object) -> None:
504 signal.pthread_sigmask(signal.SIG_BLOCK, MANAGED_SIGNALS)
505 for managed in MANAGED_SIGNALS:
506 signal.signal(managed, signal.SIG_IGN)
507 cleaned = supervisor.contain()
508 os._exit(128 + signum if cleaned else 1)
509
510 for managed in MANAGED_SIGNALS:
511 signal.signal(managed, interrupted)
512
513
514def _supervise(
515 request: SupervisorRequest,
516 controls: SupervisionControls | None = None,
517) -> int:
518 """Run one injected failure with pre-spawn signal deferral and exact cleanup."""
519 supervisor = BoundGroup()
520 active = controls if controls is not None else SupervisionControls()
521 owned_test_descriptor = active.test_descriptor
522 if not supervisor.enable_subreaper():
523 return INTEGRITY_REFUSAL_STATUS
524 old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, MANAGED_SIGNALS)
525 _install_interruption_handlers(supervisor)
526 try:
527 source_descriptor = int(Path(__file__).name)
528 launch = ControllerLaunch(
529 request.entry,
530 request.status,
531 active.watchdog_timeout,
532 active.missing_entry_selftest,
533 active.entry_mutator,
534 active.pre_open_mutator,
535 )
536 supervisor.spawn(source_descriptor, launch)
537 if supervisor.pid is None:
538 return 126
539 if owned_test_descriptor is not None:
540 _write_exact(
541 owned_test_descriptor,
542 f"{supervisor.pid}\n".encode("ascii"),
543 RECEIPT_MAX_BYTES,
544 )
545 os.close(owned_test_descriptor)
546 owned_test_descriptor = None
547 if active.pause_before_bound:
548 time.sleep(min(DEADLINE_SECONDS * 2, active.watchdog_timeout * 2))
549 return 124
550 _write_bound_receipt(request.bound, f"{supervisor.pid}\n")
551 if request.mode == "signal-pre-bind":
552 os.kill(os.getpid(), signal.SIGTERM)
553 signal.pthread_sigmask(signal.SIG_SETMASK, old_mask)
554 _write_exclusive(request.outer, f"{supervisor.pid}\n")
555 child_status = _wait_status(request.status)
556 supervisor.require_running("after publishing status")
557 time.sleep(POLL_SECONDS * 2)
558 supervisor.require_running("through the observation interval")
559 except (OSError, RuntimeError, TimeoutError):
560 cleaned = supervisor.cleanup()
561 return PUBLIC_REFUSAL_STATUS if cleaned else INTEGRITY_REFUSAL_STATUS
562 else:
563 cleaned = supervisor.cleanup()
564 return child_status if cleaned else INTEGRITY_REFUSAL_STATUS
565 finally:
566 if owned_test_descriptor is not None:
567 with suppress(OSError):
568 os.close(owned_test_descriptor)
569 supervisor.contain()
570 signal.pthread_sigmask(signal.SIG_SETMASK, old_mask)
571
572
573def _dispatch_controller(argv: list[str]) -> int | None:
574 """Dispatch the private controller and stat-parser modes."""
575 if len(argv) == CONTROLLER_ARG_COUNT and argv[1] == "--controller":
576 watchdog_timeout = float(argv[7])
577 if watchdog_timeout not in (WATCHDOG_TIMEOUT_SECONDS, SELFTEST_WATCHDOG_TIMEOUT_SECONDS):
578 return 64
579 return _controller(
580 argv[2],
581 Path(argv[3]),
582 int(argv[4]),
583 (int(argv[5]), argv[6]),
584 watchdog_timeout,
585 )
586 return None
587
588
589def _load_cases_dispatch(descriptor: int) -> Callable[[list[str]], int | None]:
590 """Load the authenticated source-only cases dispatcher from its bound FD."""
591 try:
592 source = _read_cases_source(descriptor)
593 digest = hashlib.sha256(source).hexdigest()
594 if digest != CASES_RAW_SHA256:
595 message = "supervisor cases digest drifted"
596 _refuse_entry(message)
597 namespace = {
598 "__name__": "_ra8_supervisor_cases",
599 "__file__": f"/proc/self/fd/{descriptor}",
600 "_RA8_SUPERVISOR_CASES_VERSION": 1,
601 }
602 exec( # noqa: S102 -- exact digest-bound source-only FD
603 compile(source, namespace["__file__"], "exec"), namespace
604 )
605 grant = namespace.pop("_RA8_SUPERVISOR_CASES_VERSION", None)
606 if grant != 1 or "_RA8_SUPERVISOR_CASES_VERSION" in namespace:
607 message = "supervisor cases load grant was not consumed"
608 _refuse_entry(message)
609 if hashlib.sha256(_read_cases_source(descriptor)).hexdigest() != digest:
610 message = "supervisor cases changed while loading"
611 _refuse_entry(message)
612 dispatch = namespace.get("dispatch_supervisor_cases")
613 if not callable(dispatch):
614 message = "supervisor cases dispatcher is absent"
615 _refuse_entry(message)
616 if dispatch.__globals__ is not namespace:
617 message = "supervisor cases dispatcher escaped its private namespace"
618 _refuse_entry(message)
619 return dispatch
620 finally:
621 os.close(descriptor)
622
623
624def _load_process_api(descriptor: int) -> tuple[object, ...]:
625 """Load the authenticated source-only process primitives from a bound FD."""
626 module_name = "_ra8_supervisor_process"
627 module = None
628 try:
629 source = _read_process_source(descriptor)
630 digest = hashlib.sha256(source).hexdigest()
631 if digest != PROCESS_RAW_SHA256:
632 message = "supervisor process digest drifted"
633 _refuse_entry(message)
634 if module_name in sys.modules:
635 message = "supervisor process module name is already occupied"
636 _refuse_entry(message)
637 module = types.ModuleType(module_name)
638 namespace = module.__dict__
639 namespace["__file__"] = f"/proc/self/fd/{descriptor}"
640 namespace["_RA8_SUPERVISOR_PROCESS_VERSION"] = 1
641 sys.modules[module_name] = module
642 exec( # noqa: S102 -- exact digest-bound source-only FD
643 compile(source, namespace["__file__"], "exec"), namespace
644 )
645 grant = namespace.pop("_RA8_SUPERVISOR_PROCESS_VERSION", None)
646 if grant != 1 or "_RA8_SUPERVISOR_PROCESS_VERSION" in namespace:
647 message = "supervisor process load grant was not consumed"
648 _refuse_entry(message)
649 if hashlib.sha256(_read_process_source(descriptor)).hexdigest() != digest:
650 message = "supervisor process changed while loading"
651 _refuse_entry(message)
652 return _validate_process_api(module_name, namespace, module)
653 finally:
654 if module is not None and sys.modules.get(module_name) is module:
655 del sys.modules[module_name]
656 os.close(descriptor)
657
658
659def _validate_process_api(
660 module_name: str, namespace: dict[str, object], module: types.ModuleType
661) -> tuple[object, ...]:
662 """Validate the exact process API and its digest-bound namespace."""
663 names = (
664 "ProcessIdentity",
665 "BoundGroup",
666 "_stat_group",
667 "_bind_process",
668 "_group_members",
669 "_direct_children",
670 "_child_table_is_empty",
671 "_enable_child_subreaper",
672 )
673 api = tuple(namespace.get(name) for name in names)
674 classes, functions = api[:2], api[2:]
675 if not all(isinstance(value, type) for value in classes) or not all(
676 isinstance(value, types.FunctionType) for value in functions
677 ):
678 _refuse_entry("supervisor process API is incomplete")
679 methods = tuple(
680 value for value in vars(api[1]).values() if isinstance(value, types.FunctionType)
681 )
682 escaped = any(value.__module__ != namespace["__name__"] for value in classes)
683 escaped = escaped or any(value.__globals__ is not namespace for value in functions)
684 escaped = escaped or any(value.__globals__ is not namespace for value in methods)
685 escaped = escaped or sys.modules.get(module_name) is not module
686 if escaped:
687 _refuse_entry("supervisor process API escaped its private namespace")
688 return api
689
690
691def _install_process_api(api: tuple[object, ...]) -> None:
692 """Install the exact authenticated process API before any case dispatch."""
693 if "_ra8_supervisor_process" in sys.modules:
694 message = "supervisor process module residue remained after loading"
695 _refuse_entry(message)
696 global ProcessIdentity
697 global BoundGroup
698 global _bind_process, _child_table_is_empty, _direct_children
699 global _enable_child_subreaper, _group_members, _stat_group
700 (
701 ProcessIdentity,
702 BoundGroup,
703 _stat_group,
704 _bind_process,
705 _group_members,
706 _direct_children,
707 _child_table_is_empty,
708 _enable_child_subreaper,
709 ) = api
710
711
712def _wait_group_populated(group: int) -> bool:
713 """Wait for a controller and its payload to share one bound group."""
714 deadline = time.monotonic() + 2
715 while time.monotonic() < deadline:
716 members = _group_members(group)
717 if members is not None and len(members) >= MIN_POPULATED_GROUP_MEMBERS:
718 return True
719 time.sleep(POLL_SECONDS)
720 return False
721
722
723def _wait_group_gone(group: int) -> bool:
724 """Wait a bounded interval for one controller group to disappear."""
725 deadline = time.monotonic() + DEADLINE_SECONDS
726 while time.monotonic() < deadline:
727 if _group_members(group) == set():
728 return True
729 time.sleep(POLL_SECONDS)
730 return False
731
732
733def _cleanup_retry_authorities() -> tuple[dict[str, object], object, object, object, object] | None:
734 """Bind process cleanup globals and the separate supervisor namespace."""
735 cleanup_method = vars(BoundGroup).get("cleanup")
736 if not isinstance(cleanup_method, types.FunctionType):
737 return None
738 cleanup_globals = cleanup_method.__globals__
739 main_globals = globals()
740 if cleanup_globals is main_globals:
741 return None
742 values = (
743 cleanup_globals.get("_group_members"),
744 cleanup_globals.get("DEADLINE_SECONDS"),
745 main_globals.get("_group_members"),
746 main_globals.get("DEADLINE_SECONDS"),
747 )
748 if values[0] is not _group_members or values[1] != DEADLINE_SECONDS:
749 return None
750 if values[2] is not _group_members or values[3] != DEADLINE_SECONDS:
751 return None
752 return (cleanup_globals, *values)
753
754
755def _census_retry_probe(
756 supervisor: BoundGroup,
757 authorities: tuple[dict[str, object], object, object, object, object],
758 main_globals: dict[str, object],
759 original_killpg: Callable[[int, int], None],
760) -> bool:
761 """Inject one census failure and verify that cleanup retains authority."""
762 cleanup_globals, original_members, original_deadline, _, _ = authorities
763 skipped_signal = False
764
765 def fail_bound_census(group: int) -> set[int] | None:
766 return None if group == supervisor.pid else original_members(group)
767
768 def skip_first_group_kill(group: int, sent_signal: int) -> None:
769 nonlocal skipped_signal
770 if group == supervisor.pid and sent_signal == signal.SIGKILL and not skipped_signal:
771 skipped_signal = True
772 return
773 original_killpg(group, sent_signal)
774
775 main_globals["_group_members"], main_globals["DEADLINE_SECONDS"] = (
776 fail_bound_census,
777 POLL_SECONDS * 2,
778 )
779 wrong_namespace_ignored = (
780 cleanup_globals["_group_members"] is original_members
781 and cleanup_globals["DEADLINE_SECONDS"] == original_deadline
782 )
783 cleanup_globals["_group_members"], cleanup_globals["DEADLINE_SECONDS"] = (
784 fail_bound_census,
785 POLL_SECONDS * 2,
786 )
787 os.killpg = skip_first_group_kill
788 first_cleanup = supervisor.cleanup()
789 retained_members = original_members(supervisor.pid)
790 return (
791 not first_cleanup
792 and wrong_namespace_ignored
793 and not supervisor.reaped
794 and skipped_signal
795 and supervisor.entry_descriptor is not None
796 and retained_members is not None
797 and len(retained_members) >= MIN_POPULATED_GROUP_MEMBERS
798 )
799
800
801def _census_cleanup_retry_selftest(entry: str, root: Path) -> bool:
802 """Retain authority after failed census, then retry cleanup to completion."""
803 supervisor = BoundGroup()
804 held_death = None
805 authorities = _cleanup_retry_authorities()
806 if authorities is None:
807 return False
808 cleanup_globals, original_members, original_deadline, main_members, main_deadline = authorities
809 main_globals = globals()
810 original_killpg = os.killpg
811 try:
812 if not supervisor.enable_subreaper():
813 return False
814 launch = ControllerLaunch(
815 entry, root / "supervisor-cleanup-retry.status", SELFTEST_WATCHDOG_TIMEOUT_SECONDS
816 )
817 supervisor.spawn(int(Path(__file__).name), launch)
818 if supervisor.pid is None or supervisor.death_write is None:
819 return False
820 held_death = os.dup(supervisor.death_write)
821 if not _wait_group_populated(supervisor.pid):
822 return False
823 retained = _census_retry_probe(
824 supervisor,
825 authorities,
826 main_globals,
827 original_killpg,
828 )
829 finally:
830 cleanup_globals["_group_members"], cleanup_globals["DEADLINE_SECONDS"] = (
831 original_members,
832 original_deadline,
833 )
834 main_globals["_group_members"], main_globals["DEADLINE_SECONDS"] = (
835 main_members,
836 main_deadline,
837 )
838 os.killpg = original_killpg
839 if held_death is not None:
840 os.close(held_death)
841 cleaned = supervisor.contain()
842 group_absent = supervisor.pid is None or _wait_group_gone(supervisor.pid)
843 return retained and cleaned and supervisor.entry_descriptor is None and group_absent
844
845
846def _public_supervision(request_argv: list[str]) -> int:
847 """Validate and run one public request after cases authentication."""
848 if len(request_argv) != SUPERVISOR_ARG_COUNT or request_argv[5] not in (
849 "normal",
850 "signal-pre-bind",
851 ):
852 return 64
853 if not Path("/proc/self/stat").is_file():
854 return 78
855 request = SupervisorRequest(
856 request_argv[1],
857 Path(request_argv[2]),
858 Path(request_argv[3]),
859 Path(request_argv[4]),
860 request_argv[5],
861 )
862 root = request.bound.parent
863 receipts = (request.bound, request.outer, request.status)
864 if any(receipt.parent != root or receipt.name in ("", ".", "..") for receipt in receipts):
865 return PUBLIC_REFUSAL_STATUS
866 try:
867 descriptor, identity = _open_suite_root_authority(root)
868 except (OSError, RuntimeError):
869 return PUBLIC_REFUSAL_STATUS
870 if f"{identity[0]}:{identity[1]}" != request_argv[6]:
871 _close_suite_root_authority(descriptor, root, identity)
872 return PUBLIC_REFUSAL_STATUS
873 anchored = _anchored_root_path(descriptor)
874 anchored_request = SupervisorRequest(
875 request.entry,
876 anchored / request.bound.name,
877 anchored / request.outer.name,
878 anchored / request.status.name,
879 request.mode,
880 )
881 try:
882 result = _supervise(anchored_request)
883 finally:
884 root_integrity = _close_suite_root_authority(descriptor, root, identity)
885 return result if root_integrity else INTEGRITY_REFUSAL_STATUS
886
887
888def _dispatch_authorized(
889 process_descriptor: int, cases_descriptor: int, request_argv: list[str]
890) -> int:
891 """Dispatch one hidden or public mode after authenticating its cases FD."""
892 try:
893 process_api = _load_process_api(process_descriptor)
894 _install_process_api(process_api)
895 cases_dispatch = _load_cases_dispatch(cases_descriptor)
896 except (OSError, RuntimeError, SyntaxError, UnicodeDecodeError):
897 return PUBLIC_REFUSAL_STATUS
898 hidden_status = _dispatch_controller(request_argv)
899 cases_status = cases_dispatch(request_argv)
900 if hidden_status is None:
901 hidden_status = cases_status
902 if hidden_status is not None:
903 return hidden_status
904 return _public_supervision(request_argv)
905
906
907def main(argv: list[str]) -> int:
908 """Dispatch private controllers or requests with an authenticated cases FD."""
909 controller_status = _dispatch_controller(argv)
910 if controller_status is not None:
911 return controller_status
912 valid = (
913 len(argv) >= ROOT_SELFTEST_ARG_COUNT + 2
914 and argv[1] == PROCESS_ARG
915 and argv[2].isdigit()
916 and argv[3] == CASES_ARG
917 and argv[4].isdigit()
918 )
919 if not valid:
920 return 64
921 return _dispatch_authorized(int(argv[2]), int(argv[4]), [argv[0], *argv[5:]])
922
923
924if __name__ == "__main__":
925 raise SystemExit(main(sys.argv))
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.
Definition xz_config.h:157