diff --git a/backend/tests/test_delivery.py b/backend/tests/test_delivery.py index 130f8fc..6f95a95 100644 --- a/backend/tests/test_delivery.py +++ b/backend/tests/test_delivery.py @@ -11,6 +11,7 @@ ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) policy = importlib.import_module("scripts.release.policy") manifest = importlib.import_module("scripts.release.manifest") +builder = importlib.import_module("scripts.release.build") architecture = importlib.import_module("scripts.check_architecture") @@ -90,3 +91,23 @@ def test_delivery_may_use_public_wire_contracts(tmp_path, monkeypatch): (tmp_path / "public.py").write_text("from kf_oj.contracts.judging_v2 import NodeAdmission\n") monkeypatch.setattr(architecture, "RELEASE", tmp_path) assert architecture.release_violations() == [] + + +def test_signed_guest_library_alias_uses_guest_root(tmp_path): + root = tmp_path / "runtime" + library = root / "services/usr/lib/library.so.1" + library.parent.mkdir(parents=True) + library.write_bytes(b"public-library-data") + alias = library.parent / "library.so" + alias.symlink_to("/usr/lib/library.so.1") + records = { + "services/usr/lib/library.so": {"link": "/usr/lib/library.so.1"}, + "services/usr/lib/library.so.1": { + "bytes": library.stat().st_size, + "sha256": policy.digest(library), + }, + } + target = tmp_path / "payload/lib/library.so" + builder.curated(root, records, "services/usr/lib/library.so", target) + assert target.read_bytes() == library.read_bytes() + assert not target.is_symlink() diff --git a/scripts/release/build.py b/scripts/release/build.py index 825bc6b..01c402d 100644 --- a/scripts/release/build.py +++ b/scripts/release/build.py @@ -107,8 +107,15 @@ def curated(root: Path, records: dict, name: str, target: Path) -> None: if not current.is_symlink() or os.readlink(current) != entry["link"]: raise DeliveryError("runtime input link differs") if entry["link"].startswith("/"): - raise DeliveryError("absolute runtime input link refused") - current = Path(os.path.normpath(current.parent / entry["link"])) + # Public Arch libraries may contain /usr/lib aliases. Interpret + # them within the fingerprinted guest tree, never the build host. + if not relative.startswith("services/usr/lib/") or not entry["link"].startswith( + "/usr/lib/" + ): + raise DeliveryError("absolute runtime input link refused") + current = Path(os.path.normpath(root / "services" / entry["link"].lstrip("/"))) + else: + current = Path(os.path.normpath(current.parent / entry["link"])) if not current.is_relative_to(root): raise DeliveryError("runtime input escapes trusted root") raise DeliveryError("runtime input link depth limit")