#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Host-only transactional mapping/protection model for admitted ELF loads.""" from __future__ import annotations from dataclasses import dataclass from phase10ag_bounded_elf import LoadSegment, PF_R, PF_W, PF_X PAGE_SIZE = 16 * 1024 MAX_SEGMENTS = 8 MAX_RELATIVE_RELOCATIONS = 4096 MAX_EVENTS = 64 MAX_MAIN_TICKS = 128 MAX_REGION_SIZE = 128 * 1024 * 1024 RESERVE_CHILD = "RESERVE_CHILD" CREATE_MIRROR = "CREATE_MIRROR" COPY_FILE_BYTES = "COPY_FILE_BYTES" ZERO_BSS = "ZERO_BSS" APPLY_RELATIVE = "APPLY_RELATIVE" COPY_MIRROR_TO_CHILD = "COPY_MIRROR_TO_CHILD" SET_FINAL_PROTECTION = "SET_FINAL_PROTECTION" SYNC_IMAGE = "SYNC_IMAGE" RELEASE_MIRROR = "RELEASE_MIRROR" UNMAP_CHILD = "UNMAP_CHILD" OK = "OK" ERROR = "ERROR" OPERATIONS = { RESERVE_CHILD, CREATE_MIRROR, COPY_FILE_BYTES, ZERO_BSS, APPLY_RELATIVE, COPY_MIRROR_TO_CHILD, SET_FINAL_PROTECTION, SYNC_IMAGE, RELEASE_MIRROR, UNMAP_CHILD, } class MappingModelError(RuntimeError): """The mapping transaction or its rollback is incomplete.""" def _round_down(value: int) -> int: return value & ~(PAGE_SIZE - 1) def _round_up(value: int) -> int: if value > (1 << 64) - PAGE_SIZE: raise MappingModelError("mapping address cannot be rounded") return (value + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1) @dataclass(frozen=True) class MappingPlan: segments: tuple[LoadSegment, ...] relative_relocations: int def __post_init__(self) -> None: if not isinstance(self.segments, tuple) or not 1 <= len(self.segments) <= MAX_SEGMENTS \ or any(type(item) is not LoadSegment for item in self.segments): raise MappingModelError("mapping segments are invalid") if tuple(sorted(self.segments, key=lambda item: item.virtual_address)) != self.segments: raise MappingModelError("mapping segments are not ordered") if not isinstance(self.relative_relocations, int) \ or isinstance(self.relative_relocations, bool) \ or not 1 <= self.relative_relocations <= MAX_RELATIVE_RELOCATIONS: raise MappingModelError("relative relocation count is invalid") protected: list[tuple[int, int]] = [] for segment in self.segments: if segment.memory_size <= 0 or segment.file_size < 0 \ or segment.file_size > segment.memory_size \ or segment.flags & ~(PF_R | PF_W | PF_X) \ or segment.flags & PF_W and segment.flags & PF_X: raise MappingModelError("mapping segment is unsafe") start = _round_down(segment.virtual_address) end = _round_up(segment.virtual_address + segment.memory_size) if protected and protected[-1][1] > start: raise MappingModelError("page-rounded protections overlap") protected.append((start, end)) if self.region_size > MAX_REGION_SIZE: raise MappingModelError("mapping region exceeds budget") @property def region_start(self) -> int: return _round_down(self.segments[0].virtual_address) @property def region_size(self) -> int: end = _round_up(self.segments[-1].virtual_address + self.segments[-1].memory_size) return end - self.region_start @dataclass(frozen=True) class FakeMappingEvent: operation: str result: str segment_index: int = -1 value: int = 0 ticks: int = 1 def __post_init__(self) -> None: if self.operation not in OPERATIONS or self.result not in {OK, ERROR}: raise MappingModelError("fake mapping operation/result is invalid") if not isinstance(self.segment_index, int) or isinstance(self.segment_index, bool) \ or self.segment_index < -1: raise MappingModelError("fake segment index is invalid") if not isinstance(self.value, int) or isinstance(self.value, bool) or self.value < 0: raise MappingModelError("fake mapping value is invalid") if not isinstance(self.ticks, int) or isinstance(self.ticks, bool) \ or not 1 <= self.ticks <= MAX_MAIN_TICKS: raise MappingModelError("fake mapping ticks are invalid") indexed = self.operation in {COPY_FILE_BYTES, ZERO_BSS, SET_FINAL_PROTECTION} if indexed != (self.segment_index >= 0): raise MappingModelError("fake mapping segment binding is invalid") class FakeMappingFacade: """Exact scripted facade with no memory or process capability.""" def __init__(self, events: tuple[FakeMappingEvent, ...]) -> None: if not isinstance(events, tuple) or not 1 <= len(events) <= MAX_EVENTS \ or any(type(event) is not FakeMappingEvent for event in events): raise MappingModelError("fake mapping script is invalid") self._events = list(events) self.trace: list[str] = [] self.ticks = 0 def invoke(self, operation: str, segment_index: int = -1, value: int = 0, cleanup: bool = False) -> FakeMappingEvent: if not self._events: raise MappingModelError("fake mapping script is exhausted") event = self._events.pop(0) if event.operation != operation or event.segment_index != segment_index \ or event.value != value: raise MappingModelError("fake mapping ordering or binding differs") if not cleanup and self.ticks + event.ticks > MAX_MAIN_TICKS: self.trace.append(f"DEADLINE_BEFORE:{operation}") raise MappingModelError("mapping main tick budget expired before operation") self.ticks += event.ticks self.trace.append(f"{operation}:{segment_index}:{value}:{event.result}") return event @property def remaining(self) -> int: return len(self._events) @dataclass(frozen=True) class MappingOutcome: classification: str success: bool region_size: int copied_file_bytes: int zeroed_bss_bytes: int relative_relocations: int final_protections: tuple[int, ...] mirror_released: bool child_region_retained: bool child_region_unmapped: bool target_mapping_performed: bool = False device_behavior_proven: bool = False def _ok(facade: FakeMappingFacade, operation: str, segment_index: int = -1, value: int = 0) -> None: if facade.invoke(operation, segment_index, value).result != OK: raise MappingModelError(f"{operation} failed") def run_mapping(plan: MappingPlan, facade: FakeMappingFacade) -> MappingOutcome: """Run one synthetic mapping transaction with full-region rollback.""" if type(plan) is not MappingPlan or type(facade) is not FakeMappingFacade: raise MappingModelError("mapping model boundary is invalid") child_reserved = False mirror_active = False mirror_released = False child_unmapped = False committed = False copied = 0 zeroed = 0 protections: list[int] = [] primary_error: Exception | None = None try: _ok(facade, RESERVE_CHILD, value=plan.region_size) child_reserved = True _ok(facade, CREATE_MIRROR, value=plan.region_size) mirror_active = True for index, segment in enumerate(plan.segments): if segment.file_size: _ok(facade, COPY_FILE_BYTES, index, segment.file_size) copied += segment.file_size bss = segment.memory_size - segment.file_size if bss: _ok(facade, ZERO_BSS, index, bss) zeroed += bss _ok(facade, APPLY_RELATIVE, value=plan.relative_relocations) _ok(facade, COPY_MIRROR_TO_CHILD, value=plan.region_size) for index, segment in enumerate(plan.segments): _ok(facade, SET_FINAL_PROTECTION, index, segment.flags) protections.append(segment.flags) _ok(facade, SYNC_IMAGE, value=plan.region_size) _ok(facade, RELEASE_MIRROR) mirror_active = False mirror_released = True committed = True except Exception as error: primary_error = error finally: try: if mirror_active: if facade.invoke(RELEASE_MIRROR, cleanup=True).result != OK: raise MappingModelError("mirror cleanup failed") mirror_active = False mirror_released = True if child_reserved and not committed: if facade.invoke(UNMAP_CHILD, value=plan.region_size, cleanup=True).result != OK: raise MappingModelError("child mapping rollback failed") child_unmapped = True child_reserved = False except Exception as cleanup_error: raise MappingModelError("mapping rollback is incomplete") from cleanup_error if facade.remaining: raise MappingModelError("fake mapping script has unused operations") if primary_error is not None: return MappingOutcome( "OFFLINE_MAPPING_TRANSACTION_ROLLED_BACK", False, plan.region_size, copied, zeroed, plan.relative_relocations, tuple(protections), mirror_released, False, child_unmapped) if not committed or not mirror_released or child_unmapped \ or tuple(protections) != tuple(item.flags for item in plan.segments): raise MappingModelError("successful mapping invariants failed") return MappingOutcome( "OFFLINE_MAPPING_TRANSACTION_MODEL_COMPLETE", True, plan.region_size, copied, zeroed, plan.relative_relocations, tuple(protections), True, True, False)