This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user