#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Capability-free exact-progress copy and credential restoration model.""" from __future__ import annotations from dataclasses import dataclass MAX_COPY_SIZE = 128 * 1024 * 1024 MAX_CHUNKS = 4096 MAX_EVENTS = MAX_CHUNKS + 16 MAX_TICKS = 8192 MAX_U64 = (1 << 64) - 1 BACKUP_AUTHID = "BACKUP_AUTHID" BACKUP_CAPS = "BACKUP_CAPS" SET_PRIV_AUTHID = "SET_PRIV_AUTHID" SET_PRIV_CAPS = "SET_PRIV_CAPS" COPY_CHUNK = "COPY_CHUNK" RESTORE_CAPS = "RESTORE_CAPS" RESTORE_AUTHID = "RESTORE_AUTHID" KILL_AND_REAP_CHILD = "KILL_AND_REAP_CHILD" TERMINATE_SERVICE = "TERMINATE_SERVICE" OK = "OK" ERROR = "ERROR" MORE = "MORE" COMPLETE = "COMPLETE" OPERATIONS = { BACKUP_AUTHID, BACKUP_CAPS, SET_PRIV_AUTHID, SET_PRIV_CAPS, COPY_CHUNK, RESTORE_CAPS, RESTORE_AUTHID, KILL_AND_REAP_CHILD, TERMINATE_SERVICE, } class CopyModelError(RuntimeError): """The synthetic copy contract cannot reach a bounded safe state.""" @dataclass(frozen=True) class CopyPlan: source: int destination: int length: int def __post_init__(self) -> None: for value in (self.source, self.destination, self.length): if not isinstance(value, int) or isinstance(value, bool) or value < 0: raise CopyModelError("copy plan value is invalid") if not 1 <= self.length <= MAX_COPY_SIZE: raise CopyModelError("copy length is invalid") if self.source > MAX_U64 - self.length \ or self.destination > MAX_U64 - self.length: raise CopyModelError("copy range overflows") @dataclass(frozen=True) class FakeCopyEvent: operation: str result: str progress: int = 0 remote_status: str = "" ticks: int = 1 def __post_init__(self) -> None: if self.operation not in OPERATIONS or self.result not in {OK, ERROR}: raise CopyModelError("copy event operation/result is invalid") if not isinstance(self.progress, int) or isinstance(self.progress, bool) \ or self.progress < 0: raise CopyModelError("copy progress is invalid") if self.operation == COPY_CHUNK: if self.remote_status not in {MORE, COMPLETE}: raise CopyModelError("copy status is invalid") elif self.progress or self.remote_status: raise CopyModelError("non-copy event carries copy result") if not isinstance(self.ticks, int) or isinstance(self.ticks, bool) \ or not 1 <= self.ticks <= MAX_TICKS: raise CopyModelError("copy event ticks are invalid") class FakeCopyFacade: """Exact fake script with no credential, process, clock or memory access.""" def __init__(self, events: tuple[FakeCopyEvent, ...]) -> None: if not isinstance(events, tuple) or not 1 <= len(events) <= MAX_EVENTS \ or any(type(item) is not FakeCopyEvent for item in events): raise CopyModelError("copy event script is invalid") self._events = list(events) self.ticks = 0 def invoke(self, operation: str, cleanup: bool = False) -> FakeCopyEvent: if not self._events: raise CopyModelError("copy event script is exhausted") event = self._events.pop(0) if event.operation != operation: raise CopyModelError("copy event ordering differs") if not cleanup and self.ticks + event.ticks > MAX_TICKS: raise CopyModelError("copy deadline precedes operation") self.ticks += event.ticks return event @property def remaining(self) -> int: return len(self._events) @dataclass(frozen=True) class CopyOutcome: classification: str success: bool copied: int restore_failure_bits: int child_alive: bool service_available: bool target_copy_performed: bool = False firmware_behavior_proven: bool = False def run_copy(plan: CopyPlan, facade: FakeCopyFacade) -> CopyOutcome: """Run one bounded synthetic copy and attempt every required restore.""" if type(plan) is not CopyPlan or type(facade) is not FakeCopyFacade: raise CopyModelError("copy model boundary is invalid") auth_changed = caps_changed = False copied = chunks = restore_failures = 0 failed = False try: if facade.invoke(BACKUP_AUTHID).result != OK: failed = True elif facade.invoke(BACKUP_CAPS).result != OK: failed = True elif facade.invoke(SET_PRIV_AUTHID).result != OK: failed = True else: auth_changed = True if facade.invoke(SET_PRIV_CAPS).result != OK: failed = True else: caps_changed = True while copied < plan.length: if chunks >= MAX_CHUNKS: failed = True break event = facade.invoke(COPY_CHUNK) chunks += 1 remaining = plan.length - copied if event.result != OK or event.progress == 0 \ or event.progress > remaining: failed = True break copied += event.progress if event.remote_status == COMPLETE: if copied != plan.length: failed = True break if copied == plan.length: failed = True break if copied != plan.length: failed = True except CopyModelError: failed = True if caps_changed: if facade.invoke(RESTORE_CAPS, cleanup=True).result != OK: restore_failures |= 1 caps_changed = False if auth_changed: if facade.invoke(RESTORE_AUTHID, cleanup=True).result != OK: restore_failures |= 2 auth_changed = False child_alive = True service_available = True if (failed and copied) or restore_failures: if facade.invoke(KILL_AND_REAP_CHILD, cleanup=True).result != OK: raise CopyModelError("child termination failed") child_alive = False if restore_failures: if facade.invoke(TERMINATE_SERVICE, cleanup=True).result != OK: raise CopyModelError("compromised service termination failed") service_available = False if facade.remaining: raise CopyModelError("copy event script has unused operations") success = not failed and copied == plan.length and restore_failures == 0 if success: return CopyOutcome("OFFLINE_EXACT_COPY_COMPLETE", True, copied, 0, True, True) if restore_failures: classification = "OFFLINE_RESTORE_FAILURE_CONTAINED" elif copied: classification = "OFFLINE_PARTIAL_COPY_CONTAINED" else: classification = "OFFLINE_COPY_REJECTED_BEFORE_MUTATION" return CopyOutcome(classification, False, copied, restore_failures, child_alive, service_available)