3"""Bind privileged image-lock shell surfaces to exact reviewed raw bytes."""
5from __future__
import annotations
12from collections.abc
import Callable, Mapping
13from dataclasses
import dataclass
14from pathlib
import Path, PurePosixPath
16import hil_convergence_safety_raw_digest_controls
as raw_digest_controls
22DEVCONTAINER_IMAGE_PATH =
"scripts/ci/devcontainer_image.sh"
23DEVCONTAINER_IMAGE_RAW_SHA256 =
"6cc453374569d45c31351bac2ffd1b99b4dc36af45f5464979cd59ac5e7c5d5f"
24DEVCONTAINER_IMAGE_LOCK_RECEIPTS_PATH =
"scripts/ci/devcontainer_image_lock_receipts.bash"
25DEVCONTAINER_IMAGE_LOCK_RECEIPTS_RAW_SHA256 = (
26 "854cfd1163d3d49eda0b05d8a14c5b32385b4b96e7d08de20b03da5cbd1ee727"
28DEVCONTAINER_IMAGE_LOCK_SELFTEST_PATH =
"scripts/ci/devcontainer_image_lock_selftest.bash"
29DEVCONTAINER_IMAGE_LOCK_SELFTEST_RAW_SHA256 = (
30 "ecd07f920afd67996299c55a824a951a336e00f5e7ad4522c5a8cd99b885f3f5"
32DEVCONTAINER_IMAGE_SELFTEST_PATH =
"scripts/ci/devcontainer_image_selftest.bash"
33DEVCONTAINER_IMAGE_SELFTEST_RAW_SHA256 = (
34 "1d84ebe964ea5085a4145db610cb1d37ad61c02ea6d1e3220f8befdb2de1fcd7"
36DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_PATH = (
37 "scripts/ci/devcontainer_image_bound_exit_selftest.bash"
39DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_RAW_SHA256 = (
40 "79a1a39638b961ecb2daededdd363f04a7f765991b860a629cfbdc589df8de4d"
42DEVCONTAINER_IMAGE_SELFTEST_CASES_PATH =
"scripts/ci/devcontainer_image_selftest_cases.bash"
43DEVCONTAINER_IMAGE_SELFTEST_CASES_RAW_SHA256 = (
44 "77284974e5886b4e7796999275145e229d42a6c4fc38c45d1786cba1c0878395"
46DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_PATH =
"scripts/ci/devcontainer_image_signal_selftest.bash"
47DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_RAW_SHA256 = (
48 "37890007bdfe343848b8d42f2f58367e6018b0503b3117df6011ad599754ea21"
50DEVCONTAINER_IMAGE_SELFTEST_PROCESS_PATH =
"scripts/ci/devcontainer_image_selftest_process.py"
51DEVCONTAINER_IMAGE_SELFTEST_PROCESS_RAW_SHA256 = (
52 "7dbb7b6fa4c477d8baea6ed43f2f1bb87a1555015dda5984fed1efb614b182f8"
54DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_PATH =
"scripts/ci/devcontainer_image_selftest_supervisor.py"
55DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_RAW_SHA256 = (
56 "2e52c9d4a2a1f0d1adad083a00611445ad9eff5015d765caef244317846c7990"
58DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_PATH = (
59 "scripts/ci/devcontainer_image_selftest_supervisor_cases.py"
61DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_RAW_SHA256 = (
62 "897a5be60eec486f9f9615fead84db22f8526dba189df305f561bc1c7b5e49e7"
64RAW_DIGEST_CONTROLS_PATH =
"scripts/checks/hil_convergence_safety_raw_digest_controls.py"
65RAW_DIGEST_CONTROLS_RAW_SHA256 =
"62787d69cb8d4069facfcfb6b97aa1a0d7268d7609ec929f8f38d46fb2057a8b"
67_DIRECTORY_MODE = 0o755
71_ROOT_MODE_REQUIRED = 0o500
72_ROOT_MODE_ALLOWED = 0o755
73_EXECUTABLE_MODE = 0o755
75_MAX_AUTHORITY_BYTES = 1_048_576
76_READ_STEP_BYTES = 65_536
77_FORBIDDEN_RELATIVE_PARTS = frozenset({
"",
".",
".."})
79 "DEVCONTAINER_IMAGE_RAW_SHA256": DEVCONTAINER_IMAGE_PATH,
80 "DEVCONTAINER_IMAGE_LOCK_RECEIPTS_RAW_SHA256": DEVCONTAINER_IMAGE_LOCK_RECEIPTS_PATH,
81 "DEVCONTAINER_IMAGE_LOCK_SELFTEST_RAW_SHA256": DEVCONTAINER_IMAGE_LOCK_SELFTEST_PATH,
82 "DEVCONTAINER_IMAGE_SELFTEST_RAW_SHA256": DEVCONTAINER_IMAGE_SELFTEST_PATH,
83 "DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_RAW_SHA256": (
84 DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_PATH
86 "DEVCONTAINER_IMAGE_SELFTEST_CASES_RAW_SHA256": DEVCONTAINER_IMAGE_SELFTEST_CASES_PATH,
87 "DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_RAW_SHA256": DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_PATH,
88 "DEVCONTAINER_IMAGE_SELFTEST_PROCESS_RAW_SHA256": DEVCONTAINER_IMAGE_SELFTEST_PROCESS_PATH,
89 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_RAW_SHA256": (
90 DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_PATH
92 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_RAW_SHA256": (
93 DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_PATH
95 "RAW_DIGEST_CONTROLS_RAW_SHA256": RAW_DIGEST_CONTROLS_PATH,
98Target = tuple[str, str, str, int]
99Identity = tuple[int, int, int, int, int, int, int, int, int, int]
102@dataclass(frozen=True)
103class RawReadAuthority:
104 """Portable numeric ownership derived from the checked repository root.
106 Hosted CI, containers, and worktrees use different IDs, so no numeric ID is
107 pinned in source. The retained root descriptor establishes both IDs and all
108 audited directories and files must match. Explicit fields exist so the
109 production owner/group branches have unprivileged two-sided tests.
118@dataclass(frozen=True)
119class RawReadTestHooks:
120 """Deterministic race and failure hooks used by the two-sided selftest."""
122 missing_capability: str |
None =
None
123 allow_alternate_fixed_paths: bool =
False
124 after_root_open: Callable[[Path, int],
None] |
None =
None
125 after_component_open: Callable[[str, int, int],
None] |
None =
None
126 after_file_open: Callable[[str, int],
None] |
None =
None
127 after_read: Callable[[str, int],
None] |
None =
None
128 before_post_rewalk: Callable[[str],
None] |
None =
None
129 before_root_postcheck: Callable[[Path, int],
None] |
None =
None
130 after_close_fd: Callable[[int],
None] |
None =
None
133@dataclass(frozen=True)
135 """Shared authority, flags, and test hooks for one retained-root audit."""
137 authority: RawReadAuthority
138 flags: Mapping[str, int]
139 hooks: RawReadTestHooks |
None
142@dataclass(frozen=True)
143class _MetadataPolicy:
144 """Exact metadata policy for one directory or regular file."""
154@dataclass(frozen=True)
155class _DirectoryRequest:
156 """One component-wise openat request."""
166 """Descriptor-backed state retained across one bounded file read."""
169 before: os.stat_result
170 directory_identities: tuple[Identity, ...]
171 ledger: _DescriptorLedger
174def _shell_targets(authority: Mapping[str, object]) -> tuple[Target, ...]:
175 """Return each raw-bound privileged shell surface."""
178 str(authority.get(
"DEVCONTAINER_IMAGE_PATH",
"")),
179 "DEVCONTAINER_IMAGE_RAW_SHA256",
180 "devcontainer image authority",
184 str(authority.get(
"DEVCONTAINER_IMAGE_LOCK_RECEIPTS_PATH",
"")),
185 "DEVCONTAINER_IMAGE_LOCK_RECEIPTS_RAW_SHA256",
186 "devcontainer image-lock receipt authority",
190 str(authority.get(
"DEVCONTAINER_IMAGE_LOCK_SELFTEST_PATH",
"")),
191 "DEVCONTAINER_IMAGE_LOCK_SELFTEST_RAW_SHA256",
192 "devcontainer image-lock selftest authority",
196 str(authority.get(
"DEVCONTAINER_IMAGE_SELFTEST_PATH",
"")),
197 "DEVCONTAINER_IMAGE_SELFTEST_RAW_SHA256",
198 "devcontainer image selftest lifecycle authority",
202 str(authority.get(
"DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_PATH",
"")),
203 "DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_RAW_SHA256",
204 "devcontainer image bound-exit selftest authority",
208 str(authority.get(
"DEVCONTAINER_IMAGE_SELFTEST_CASES_PATH",
"")),
209 "DEVCONTAINER_IMAGE_SELFTEST_CASES_RAW_SHA256",
210 "devcontainer image selftest cases authority",
214 str(authority.get(
"DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_PATH",
"")),
215 "DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_RAW_SHA256",
216 "devcontainer image signal selftest authority",
222def _python_targets(authority: Mapping[str, object]) -> tuple[Target, ...]:
223 """Return each raw-bound Python process and control surface."""
226 str(authority.get(
"DEVCONTAINER_IMAGE_SELFTEST_PROCESS_PATH",
"")),
227 "DEVCONTAINER_IMAGE_SELFTEST_PROCESS_RAW_SHA256",
228 "devcontainer image selftest process authority",
232 str(authority.get(
"DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_PATH",
"")),
233 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_RAW_SHA256",
234 "devcontainer image selftest supervisor authority",
238 str(authority.get(
"DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_PATH",
"")),
239 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_RAW_SHA256",
240 "devcontainer image selftest supervisor cases authority",
244 str(authority.get(
"RAW_DIGEST_CONTROLS_PATH",
"")),
245 "RAW_DIGEST_CONTROLS_RAW_SHA256",
246 "raw digest implementation-control authority",
252def _targets(values: Mapping[str, object] |
None =
None) -> tuple[Target, ...]:
253 """Return path, pin, label, and exact file mode for each bound surface."""
254 authority = globals()
if values
is None else values
255 return _shell_targets(authority) + _python_targets(authority)
258def pin_names() -> tuple[str, ...]:
259 """Return the exact mutable names used by the production wrong-pin tests."""
260 return tuple(pin_name
for _path, pin_name, _label, _mode
in _targets())
263def _source_names() -> frozenset[str]:
264 """Return every direct source authority name parsed without execution."""
267 "DEVCONTAINER_IMAGE_PATH",
268 "DEVCONTAINER_IMAGE_RAW_SHA256",
269 "DEVCONTAINER_IMAGE_LOCK_RECEIPTS_PATH",
270 "DEVCONTAINER_IMAGE_LOCK_RECEIPTS_RAW_SHA256",
271 "DEVCONTAINER_IMAGE_LOCK_SELFTEST_PATH",
272 "DEVCONTAINER_IMAGE_LOCK_SELFTEST_RAW_SHA256",
273 "DEVCONTAINER_IMAGE_SELFTEST_PATH",
274 "DEVCONTAINER_IMAGE_SELFTEST_RAW_SHA256",
275 "DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_PATH",
276 "DEVCONTAINER_IMAGE_BOUND_EXIT_SELFTEST_RAW_SHA256",
277 "DEVCONTAINER_IMAGE_SELFTEST_CASES_PATH",
278 "DEVCONTAINER_IMAGE_SELFTEST_CASES_RAW_SHA256",
279 "DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_PATH",
280 "DEVCONTAINER_IMAGE_SIGNAL_SELFTEST_RAW_SHA256",
281 "DEVCONTAINER_IMAGE_SELFTEST_PROCESS_PATH",
282 "DEVCONTAINER_IMAGE_SELFTEST_PROCESS_RAW_SHA256",
283 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_PATH",
284 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_RAW_SHA256",
285 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_PATH",
286 "DEVCONTAINER_IMAGE_SELFTEST_SUPERVISOR_CASES_RAW_SHA256",
287 "RAW_DIGEST_CONTROLS_PATH",
288 "RAW_DIGEST_CONTROLS_RAW_SHA256",
293def authority_values() -> dict[str, object]:
294 """Return a detached copy of the direct path and digest authorities."""
295 return {name: globals()[name]
for name
in _source_names()}
298def target_specs(values: Mapping[str, object] |
None =
None) -> tuple[Target, ...]:
299 """Return immutable target specifications for the runtime selftest."""
300 return _targets(values)
303def maximum_authority_bytes() -> int:
304 """Return the exact bounded-read ceiling exercised by the selftest."""
305 return _MAX_AUTHORITY_BYTES
309 pin_name: str, label: str, values: Mapping[str, object] |
None =
None
310) -> tuple[str |
None, str |
None]:
311 """Return one exact pin or one diagnostic for that authority."""
312 authority = globals()
if values
is None else values
313 expected = authority.get(pin_name)
314 if not isinstance(expected, str)
or re.fullmatch(
r"[0-9a-f]{64}", expected)
is None:
315 return None, f
"{label}: exact raw SHA-256 pin is missing or malformed"
316 return expected,
None
319def _digest_error(payload: bytes, expected: str, label: str) -> str |
None:
320 """Return one raw-byte mismatch diagnostic, if any."""
321 if hashlib.sha256(payload).hexdigest() != expected:
322 return f
"{label}: raw bytes differ from exact audited digest"
326def _raw_target_error(
328 files: Mapping[str, bytes],
329 values: Mapping[str, object] |
None,
331 """Return one trusted local path, pin, presence, or digest error."""
332 path, pin_name, label, _mode = target
333 path_error = _target_path_error(path, pin_name, label)
334 if path_error
is not None:
336 expected, error = _expected_digest(pin_name, label, values)
337 if error
is not None:
339 payload = files.get(path)
341 return f
"{label}: bound file is missing"
342 return _digest_error(payload, expected, label)
345def raw_errors(files: Mapping[str, bytes], values: Mapping[str, object] |
None =
None) -> list[str]:
346 """Return errors for missing pins/files or raw-byte digest mismatches."""
349 for target
in _targets(values)
350 if (error := _raw_target_error(target, files, values))
is not None
354def _control_target(values: Mapping[str, object]) -> Target:
355 """Return the one raw-bound implementation-control policy target."""
357 target
for target
in _targets(values)
if target[1] ==
"RAW_DIGEST_CONTROLS_RAW_SHA256"
359 if len(matches) != 1:
360 return "",
"RAW_DIGEST_CONTROLS_RAW_SHA256",
"raw digest control target", _SOURCE_MODE
364def _source_values(source: str) -> dict[str, object]:
365 """Parse the direct module authorities without executing candidate code."""
367 tree = ast.parse(source)
370 values: dict[str, object] = {}
371 for node
in tree.body:
372 if not isinstance(node, ast.Assign)
or len(node.targets) != 1:
374 target = node.targets[0]
375 if isinstance(target, ast.Name)
and target.id
in _source_names():
377 values[target.id] = ast.literal_eval(node.value)
378 except (ValueError, TypeError):
384 sources: tuple[str, ...],
385 authority_source: str,
387 """Apply the production digest path to mutation-selftest source bytes."""
388 values = _source_values(authority_source)
390 path: source.encode(
"utf-8")
391 for (path, _pin, _label, _mode), source
in zip(_targets(values), sources, strict=
True)
393 control_error = _raw_target_error(_control_target(values), files, values)
394 if control_error
is not None:
395 return [control_error]
396 errors = implementation_errors(authority_source)
397 return errors + raw_errors(files, values)
400def implementation_errors(source: str) -> list[str]:
401 """Reject removal or duplication of every load-bearing reader control."""
402 return raw_digest_controls.implementation_errors(source)
405def implementation_controls() -> tuple[raw_digest_controls.ImplementationControl, ...]:
406 """Return every exact reader control from its focused policy module."""
407 return raw_digest_controls.controls()
410def implementation_mutations(source: str) -> tuple[tuple[str, str, str], ...]:
411 """Return exact one-control mutants and their required diagnostics."""
412 return raw_digest_controls.implementation_mutations(source)
415def _identity(value: os.stat_result) -> Identity:
416 """Return every security-relevant field required to remain exact."""
427 stat.S_IFMT(value.st_mode),
431def _fixed_relative_parts(path: str) -> tuple[str, ...] |
None:
432 """Return canonical POSIX components, rejecting absolute or parent paths."""
433 if re.fullmatch(
r"[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)*", path)
is None:
435 pure = PurePosixPath(path)
436 if pure.is_absolute()
or any(part
in _FORBIDDEN_RELATIVE_PARTS
for part
in pure.parts):
438 if pure.as_posix() != path:
443def _target_path_error(path: str, pin_name: str, label: str) -> str |
None:
444 """Require one normalized path and its immutable repository location."""
445 if _fixed_relative_parts(path)
is None:
446 return f
"{label}: bound path must be a fixed normalized relative path"
447 if path != _FIXED_PATH_BY_PIN[pin_name]:
448 return f
"{label}: bound path differs from fixed repository authority"
452def _required_flags(hooks: RawReadTestHooks |
None) -> tuple[dict[str, int] |
None, str |
None]:
453 """Resolve every required no-follow/nonblocking platform capability."""
454 required = (
"O_DIRECTORY",
"O_NOFOLLOW",
"O_CLOEXEC",
"O_NONBLOCK")
455 flags: dict[str, int] = {}
456 for name
in required:
457 value = getattr(os, name,
None)
458 if hooks
is not None and hooks.missing_capability == name:
460 if not isinstance(value, int)
or value == 0:
462 f
"raw digest authority: required platform capability is unavailable: {name}"
466 (
"open_dir_fd", os.open
in os.supports_dir_fd),
467 (
"stat_dir_fd", os.stat
in os.supports_dir_fd),
468 (
"stat_follow_symlinks", os.stat
in os.supports_follow_symlinks),
469 (
"pread", callable(getattr(os,
"pread",
None))),
471 for name, available
in predicates:
472 is_available = available
and not (hooks
is not None and hooks.missing_capability == name)
475 f
"raw digest authority: required platform capability is unavailable: {name}"
480def _metadata_error(value: os.stat_result, policy: _MetadataPolicy) -> str |
None:
481 """Return an exact type, owner, group, mode, or link diagnostic."""
483 stat.S_ISDIR(value.st_mode)
if policy.kind ==
"directory" else stat.S_ISREG(value.st_mode)
485 if not kind_ok
or (policy.single_link
and value.st_nlink != 1):
486 return f
"{policy.label}: bound path is absent, linked, or non-regular"
487 if value.st_uid != policy.uid:
488 return f
"{policy.label}: bound path owner differs from repository authority"
489 if value.st_gid != policy.gid:
490 return f
"{policy.label}: bound path group differs from repository authority"
491 if stat.S_IMODE(value.st_mode) != policy.mode:
492 return f
"{policy.label}: bound path mode differs from exact audited mode"
496class _DescriptorLedger:
497 """Own descriptors until their one exhaustive reverse-order close."""
499 def __init__(self, hooks: RawReadTestHooks |
None) ->
None:
500 self._fds: list[int] = []
503 def retain(self, fd: int) -> int:
504 """Record and return one newly opened descriptor."""
508 def close_all(self, label: str) -> list[str]:
509 """Release ownership, close once, then invoke an after-close hook."""
513 errors.extend(_close_released_descriptor(fd, label, self._hooks))
517def _close_released_descriptor(
520 hooks: RawReadTestHooks |
None,
522 """Close one released number once, then notify an untrusted test hook."""
525 except OSError
as exc:
526 return [f
"{label}: cannot close audited descriptor safely: {exc.errno}"]
527 hook =
None if hooks
is None else hooks.after_close_fd
531 except (OSError, RuntimeError):
532 return [f
"{label}: descriptor-close hook failed"]
537 request: _DirectoryRequest,
538 context: _AuditContext,
539 ledger: _DescriptorLedger,
540) -> tuple[int |
None, Identity |
None, str |
None]:
541 """No-follow open and bind one exact path component."""
542 label = f
"raw digest authority directory: {request.relative}"
544 before = os.stat(request.component, dir_fd=request.parent_fd, follow_symlinks=
False)
545 error = _metadata_error(
549 uid=context.authority.root_uid,
550 gid=context.authority.root_gid,
551 mode=_DIRECTORY_MODE,
556 if error
is not None:
557 return None,
None, error
558 descriptor = ledger.retain(
562 | context.flags[
"O_DIRECTORY"]
563 | context.flags[
"O_NOFOLLOW"]
564 | context.flags[
"O_CLOEXEC"]
565 | context.flags[
"O_NONBLOCK"],
566 dir_fd=request.parent_fd,
569 opened = os.fstat(descriptor)
570 if _identity(before) != _identity(opened):
571 return None,
None, f
"{label}: component changed before its no-follow open"
572 hooks = context.hooks
573 if hooks
is not None and hooks.after_component_open
is not None:
574 hooks.after_component_open(request.relative, request.index, descriptor)
575 except OSError
as exc:
576 return None,
None, f
"{label}: cannot inspect or open component safely: {exc.errno}"
577 return descriptor, _identity(opened),
None
580def _read_exact_payload(
581 fd: int, before: os.stat_result, relative: str, hooks: RawReadTestHooks |
None
582) -> tuple[bytes |
None, str |
None]:
583 """Use bounded positional reads and prove exact EOF at the pre-open size."""
584 label = f
"raw digest authority file: {relative}"
585 if before.st_size > _MAX_AUTHORITY_BYTES:
586 return None, f
"{label}: bound file exceeds maximum audited size"
590 while offset < before.st_size:
591 requested =
min(_READ_STEP_BYTES, before.st_size - offset)
592 chunk = os.pread(fd, requested, offset)
593 if len(chunk) != requested:
594 return None, f
"{label}: bound file shrank during bounded raw-byte read"
597 if os.pread(fd, 1, before.st_size) != b
"" or os.pread(fd, 1, before.st_size + 1) != b
"":
598 return None, f
"{label}: bound file has bytes beyond its audited pre-read size"
599 if hooks
is not None and hooks.after_read
is not None:
600 hooks.after_read(relative, fd)
601 except OSError
as exc:
602 return None, f
"{label}: cannot read bound bytes safely: {exc.errno}"
603 return b
"".join(chunks),
None
608 parts: tuple[str, ...],
609 expected_directories: tuple[Identity, ...],
610 expected_file: Identity,
611 context: _AuditContext,
613 """Rewalk every component from the retained root and compare identities."""
614 relative =
"/".join(parts)
615 ledger = _DescriptorLedger(context.hooks)
619 for index, component
in enumerate(parts[:-1]):
620 request = _DirectoryRequest(parent_fd, component, relative, index)
621 descriptor, identity, error = _open_directory(
623 _AuditContext(context.authority, context.flags,
None),
626 if error
is not None:
628 if identity != expected_directories[index]:
630 f
"raw digest authority file: {relative}: parent changed during raw-byte read"
633 parent_fd = descriptor
635 after_path = os.stat(parts[-1], dir_fd=parent_fd, follow_symlinks=
False)
636 if _identity(after_path) != expected_file:
637 error = f
"raw digest authority file: {relative}: path changed during raw-byte read"
638 except OSError
as exc:
640 f
"raw digest authority file: {relative}: cannot rewalk bound path safely: {exc.errno}"
642 close_errors = ledger.close_all(f
"raw digest authority file: {relative}")
643 return error
if error
is not None else (close_errors[0]
if close_errors
else None)
649 context: _AuditContext,
650) -> tuple[_OpenedTarget |
None, tuple[str, ...] |
None, str |
None]:
651 """Open and bind one target and every directory leading to it."""
652 path, _pin, label, _expected_mode = target
653 parts = _fixed_relative_parts(path)
655 return None,
None, f
"{label}: bound path must be a fixed normalized relative path"
656 hooks = context.hooks
657 if path != _FIXED_PATH_BY_PIN[target[1]]
and (
658 hooks
is None or not hooks.allow_alternate_fixed_paths
660 return None,
None, f
"{label}: bound path differs from fixed repository authority"
661 ledger = _DescriptorLedger(context.hooks)
663 directory_identities: list[Identity] = []
666 close_errors: list[str] = []
668 for index, component
in enumerate(parts[:-1]):
669 request = _DirectoryRequest(parent_fd, component, path, index)
670 parent_fd, identity, error = _open_directory(
675 if error
is not None:
677 directory_identities.append(identity)
679 fd, before, error = _open_final(parent_fd, parts, target, context, ledger)
680 completed = error
is None
681 except (OSError, RuntimeError)
as exc:
682 detail = exc.errno
if isinstance(exc, OSError)
else "hook"
683 error = f
"{label}: cannot inspect or open bound bytes safely: {detail}"
686 close_errors = ledger.close_all(label)
687 if error
is not None:
688 return None,
None, error
if not close_errors
else close_errors[0]
689 state = _OpenedTarget(fd, before, tuple(directory_identities), ledger)
690 return state, parts,
None
695 parts: tuple[str, ...],
697 context: _AuditContext,
698 ledger: _DescriptorLedger,
699) -> tuple[int |
None, os.stat_result |
None, str |
None]:
700 """No-follow open and bind the single-link regular final component."""
701 path, _pin, label, expected_mode = target
702 before = os.stat(parts[-1], dir_fd=parent_fd, follow_symlinks=
False)
703 error = _metadata_error(
707 uid=context.authority.file_uid,
708 gid=context.authority.file_gid,
714 if error
is not None:
715 return None,
None, error
720 | context.flags[
"O_NOFOLLOW"]
721 | context.flags[
"O_CLOEXEC"]
722 | context.flags[
"O_NONBLOCK"],
726 opened = os.fstat(fd)
727 if _identity(before) != _identity(opened):
728 return None,
None, f
"{label}: bound path changed before its no-follow open"
729 hooks = context.hooks
730 if hooks
is not None and hooks.after_file_open
is not None:
731 hooks.after_file_open(path, fd)
732 return fd, before,
None
739 context: _AuditContext,
741 """Read, bind, and digest one fixed target below the retained root."""
742 path, _pin, label, _expected_mode = target
743 state, parts, error = _open_target(root_fd, target, context)
744 if error
is not None:
747 errors: list[str] = []
749 hooks = context.hooks
751 payload, error = _read_exact_payload(state.fd, state.before, path, hooks)
753 after_fd = os.fstat(state.fd)
754 if _identity(after_fd) != _identity(state.before):
755 error = f
"{label}: bound file metadata changed during bounded raw-byte read"
756 if error
is None and hooks
is not None and hooks.before_post_rewalk
is not None:
757 hooks.before_post_rewalk(path)
759 error = _post_rewalk(
762 state.directory_identities,
763 _identity(state.before),
767 error = _digest_error(payload, expected, label)
768 except (OSError, RuntimeError)
as exc:
769 detail = exc.errno
if isinstance(exc, OSError)
else "hook"
770 error = f
"{label}: cannot inspect or open bound bytes safely: {detail}"
772 errors.extend(state.ledger.close_all(label))
773 if error
is not None:
774 errors.insert(0, error)
778def _root_metadata_error(value: os.stat_result, authority: RawReadAuthority |
None) -> str |
None:
779 """Validate repository-root type, ownership, and safe portable mode."""
780 label =
"raw digest authority root"
781 if not stat.S_ISDIR(value.st_mode):
782 return f
"{label}: repository root is not a directory"
783 mode = stat.S_IMODE(value.st_mode)
784 if (mode & _ROOT_MODE_REQUIRED) != _ROOT_MODE_REQUIRED
or mode & ~_ROOT_MODE_ALLOWED:
785 return f
"{label}: repository root mode is not safe for the audited authority"
786 if authority
is not None and value.st_uid != authority.root_uid:
787 return f
"{label}: repository root owner differs from configured authority"
788 if authority
is not None and value.st_gid != authority.root_gid:
789 return f
"{label}: repository root group differs from configured authority"
793def _audit_all_targets(
795 values: Mapping[str, object],
796 context: _AuditContext,
798 """Audit all fixed targets below one retained repository root."""
800 for target
in _targets(values):
801 expected, error = _expected_digest(target[1], target[2], values)
803 errors.extend(_audit_target(root_fd, target, expected, context))
809def audit_live_errors(
811 values: Mapping[str, object],
813 authority: RawReadAuthority |
None =
None,
814 hooks: RawReadTestHooks |
None =
None,
816 """Audit exact live bytes through one retained no-follow root descriptor."""
817 flags, error = _required_flags(hooks)
818 if error
is not None:
820 ledger = _DescriptorLedger(hooks)
821 root_path = Path(root)
824 before = os.lstat(root_path)
825 error = _root_metadata_error(before, authority)
827 root_fd = ledger.retain(
831 | flags[
"O_DIRECTORY"]
832 | flags[
"O_NOFOLLOW"]
834 | flags[
"O_NONBLOCK"],
837 opened = os.fstat(root_fd)
838 if _identity(before) != _identity(opened):
839 error =
"raw digest authority root: path changed before its no-follow open"
841 configured = authority
or RawReadAuthority(
842 root_uid=opened.st_uid,
843 root_gid=opened.st_gid,
844 file_uid=opened.st_uid,
845 file_gid=opened.st_gid,
847 context = _AuditContext(configured, flags, hooks)
848 if hooks
is not None and hooks.after_root_open
is not None:
849 hooks.after_root_open(root_path, root_fd)
850 errors.extend(_audit_all_targets(root_fd, values, context))
851 if hooks
is not None and hooks.before_root_postcheck
is not None:
852 hooks.before_root_postcheck(root_path, root_fd)
853 after_fd = os.fstat(root_fd)
854 after_path = os.lstat(root_path)
855 if _identity(before) != _identity(after_fd)
or _identity(before) != _identity(
858 errors.append(
"raw digest authority root: path changed during retained-root audit")
861 except (OSError, RuntimeError)
as exc:
862 detail = exc.errno
if isinstance(exc, OSError)
else "hook"
863 errors.append(f
"raw digest authority root: cannot inspect or open safely: {detail}")
865 errors.extend(ledger.close_all(
"raw digest authority root"))
869def live_errors(root: Path) -> list[str]:
870 """Read and validate each exact authority with fail-closed diagnostics."""
871 return audit_live_errors(root, authority_values())
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.