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