#!/bin/sh set -eu gate_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) root=$(CDPATH= cd -- "$gate_dir/../../../.." && pwd -P) input=$gate_dir/P15-052-input.json freebsd_src=${FREEBSD_SRC:-/work/build/freebsd-src} base= output= while test "$#" -gt 0; do case "$1" in --base) test "$#" -ge 2 || { printf '%s\n' '--base requires a commit' >&2; exit 2; } base=$2 shift 2 ;; --output) test "$#" -ge 2 || { printf '%s\n' '--output requires a directory' >&2; exit 2; } output=$2 shift 2 ;; *) printf 'unknown argument: %s\n' "$1" >&2 exit 2 ;; esac done test -n "$base" || { printf '%s\n' '--base is required' >&2; exit 2; } test -n "$output" || { printf '%s\n' '--output is required' >&2; exit 2; } test -f "$input" || { printf 'missing input: %s\n' "$input" >&2; exit 2; } test -d "$freebsd_src/sys" || { printf 'missing FreeBSD source tree: %s\n' "$freebsd_src" >&2 exit 2 } for tool in git python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || { printf 'missing required host tool: %s\n' "$tool" >&2 exit 2 } done case "$output" in /*) ;; *) output=$PWD/$output ;; esac test ! -e "$output" || { printf 'refusing existing output: %s\n' "$output" >&2; exit 2; } mkdir -p "$output" python3 - "$root" "$input" "$base" "$output" "$freebsd_src" <<'PY' from __future__ import annotations import hashlib import json from pathlib import Path import re import subprocess import sys from typing import Any ROOT = Path(sys.argv[1]) INPUT = Path(sys.argv[2]) REQUESTED_BASE = sys.argv[3] OUTPUT = Path(sys.argv[4]) FREEBSD_SRC = Path(sys.argv[5]) SPEC = json.loads(INPUT.read_text(encoding="ascii")) U32_MAX = (1 << 32) - 1 U48_MAX = (1 << 48) - 1 U64_MAX = (1 << 64) - 1 I64_MAX = (1 << 63) - 1 def git(*args: str) -> str: return subprocess.check_output( ["git", "-C", str(ROOT), *args], text=True ).strip() def sha256(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def source_at(commit: str, path: str) -> str: completed = subprocess.run( ["git", "-C", str(ROOT), "show", f"{commit}:{path}"], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if completed.returncode != 0: raise SystemExit(f"cannot read {path} at {commit}: {completed.stderr}") return completed.stdout def extract_function(source: str, name: str) -> str: match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE) if match is None: raise SystemExit(f"function not found: {name}") start = source.rfind("\n\n", 0, match.start()) + 2 brace = source.find("{", match.end()) if brace < 0: raise SystemExit(f"function body not found: {name}") depth = 0 state = "code" index = brace while index < len(source): char = source[index] following = source[index + 1] if index + 1 < len(source) else "" if state == "code": if char == "/" and following == "*": state = "block" index += 2 continue if char == "/" and following == "/": state = "line" index += 2 continue if char == '"': state = "string" elif char == "'": state = "character" elif char == "{": depth += 1 elif char == "}": depth -= 1 if depth == 0: return source[start : index + 1] + "\n" elif state == "block" and char == "*" and following == "/": state = "code" index += 2 continue elif state == "line" and char == "\n": state = "code" elif state in {"string", "character"}: if char == "\\": index += 2 continue if (state == "string" and char == '"') or ( state == "character" and char == "'" ): state = "code" index += 1 raise SystemExit(f"unterminated function: {name}") def line_number(source: str, needle: str) -> int: if source.count(needle) != 1: raise SystemExit( f"source needle must occur exactly once ({source.count(needle)}): {needle}" ) return source.count("\n", 0, source.index(needle)) + 1 if SPEC.get("schema") != 1 or SPEC.get("candidate") != "P15-052": raise SystemExit("invalid P15-052 gate input") resolved = git("rev-parse", f"{REQUESTED_BASE}^{{commit}}") if resolved != SPEC["required_base"]: raise SystemExit( f"P15-052 must replay {SPEC['required_base']}, got {resolved}" ) sources = { path: source_at(resolved, path) for path in SPEC["source_sha256"] } source_hashes = { path: sha256(text.encode("utf-8")) for path, text in sources.items() } for path, expected in SPEC["source_sha256"].items(): if source_hashes[path] != expected: raise SystemExit(f"frozen source identity mismatch: {path}") function_bodies: dict[str, str] = {} for key, counts in SPEC["function_errno_counts"].items(): path, function = key.rsplit(":", 1) body = extract_function(sources[path], function) function_bodies[key] = body for errno_name, expected_count in counts.items(): actual_count = body.count(f"return ({errno_name});") if actual_count != expected_count: raise SystemExit( f"{key} {errno_name} count changed: {actual_count} != {expected_count}" ) if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", body): raise SystemExit(f"negative errno entered FreeBSD function: {key}") param_path = FREEBSD_SRC / "sys/amd64/include/param.h" types_path = FREEBSD_SRC / "sys/sys/_types.h" geom_path = FREEBSD_SRC / "sys/geom/geom.h" for path in (param_path, types_path, geom_path): if not path.is_file(): raise SystemExit(f"missing FreeBSD contract source: {path}") param_source = param_path.read_text(encoding="utf-8") types_source = types_path.read_text(encoding="utf-8") geom_source = geom_path.read_text(encoding="utf-8") page_match = re.search(r"^#define\s+PAGE_SHIFT\s+(\d+)\b", param_source, re.MULTILINE) if page_match is None: raise SystemExit("PAGE_SHIFT is absent from amd64 param.h") page_shift = int(page_match.group(1)) if page_shift != 12: raise SystemExit(f"unexpected audited amd64 PAGE_SHIFT: {page_shift}") if "typedef\t__int64_t\t__off_t;" not in types_source: raise SystemExit("FreeBSD off_t source is not the audited signed 64-bit type") if "off_t\t\t\tmediasize;" not in geom_source: raise SystemExit("GEOM mediasize is not the audited off_t field") freebsd_head = subprocess.check_output( ["git", "-C", str(FREEBSD_SRC), "rev-parse", "HEAD"], text=True ).strip() freebsd_contract = { "head": freebsd_head, "page_shift": page_shift, "off_t": "signed-64", "geom_mediasize": "off_t", "files": { str(path.relative_to(FREEBSD_SRC)): sha256(path.read_bytes()) for path in (param_path, types_path, geom_path) }, } # These are maxima after the current super/inode decode and mount-time media # checks, not unconstrained C-structure values. inode_slot_min = 32 inode_isize_max = 64 xattr_isize_max = 4 + 4 * ((1 << 16) - 2) inode_off_max = I64_MAX - inode_slot_min idx_base_max = inode_off_max + inode_isize_max + xattr_isize_max entry_size_max = 8 chunkbits_min = 9 chunkbits_max = page_shift + 31 chunk_idx_max = I64_MAX >> chunkbits_min blkaddr_max = U48_MAX physical_max = blkaddr_max << page_shift chunk_off_max = (1 << chunkbits_max) - 1 prefix_record_max = 256 prefix_off_max = U32_MAX * 4 + 255 * (3 + 2 + prefix_record_max) xattr_base_max = U32_MAX << page_shift xattr_relative_max = U32_MAX * 4 reachability: dict[str, tuple[bool, str, dict[str, int]]] = { "data.chunk.inode_plus_isize": ( inode_off_max > U64_MAX - inode_isize_max, "inode_off is bounded by mounted backing size minus one compact inode", {"inode_off_max": inode_off_max, "inode_isize_max": inode_isize_max}, ), "data.chunk.isize_plus_xattr": ( inode_off_max + inode_isize_max > U64_MAX - xattr_isize_max, "inode offset plus maximum 64-byte inode and 16-bit xattr body stays below UINT64_MAX", {"metadata_base_max": inode_off_max + inode_isize_max, "xattr_isize_max": xattr_isize_max}, ), "data.chunk.align": ( idx_base_max > U64_MAX - (entry_size_max - 1), "validated backing and bounded inode/xattr sizes leave alignment headroom", {"idx_base_max": idx_base_max, "alignment_slack": entry_size_max - 1}, ), "data.chunk.index_multiply": ( chunk_idx_max > (U64_MAX - idx_base_max) // entry_size_max, "size <= OFF_MAX and chunkbits >= 9 bound the index product", {"idx_base_max": idx_base_max, "chunk_idx_max": chunk_idx_max, "entry_size_max": entry_size_max}, ), "data.chunk.image_size_shift": ( I64_MAX > U64_MAX, "mount validates blocks << blkszbits <= GEOM off_t mediasize", {"validated_image_bytes_max": I64_MAX}, ), "data.chunk.physical_shift": ( blkaddr_max > (U64_MAX >> page_shift), "decoded chunk block address is at most 48 bits and blkszbits is at most PAGE_SHIFT", {"blkaddr_max": blkaddr_max, "blkszbits_max": page_shift}, ), "data.chunk.physical_plus_offset": ( chunk_off_max > U64_MAX - physical_max, "48-bit block address and maximum validated chunkbits leave addition headroom", {"physical_max": physical_max, "chunk_off_max": chunk_off_max}, ), "xattr.backing_size.shift": ( I64_MAX > U64_MAX, "mounted primary backing bytes are already bounded by GEOM off_t mediasize", {"validated_backing_bytes_max": I64_MAX}, ), "xattr.metadata.align": ( prefix_off_max > U64_MAX - 3, "32-bit prefix start, 8-bit count, and bounded records cannot approach UINT64_MAX", {"prefix_off_max": prefix_off_max}, ), "xattr.metadata.header_add": ( False, "the preceding off <= UINT64_MAX-3 check and 4-byte roundup imply aligned off <= UINT64_MAX-3, so adding uint16_t cannot overflow", {"accepted_input_max": U64_MAX - 3, "aligned_off_max": U64_MAX - 3, "raw_len_size": 2}, ), "xattr.shared.base_shift": ( U32_MAX > (U64_MAX >> page_shift), "xattr_blkaddr is 32-bit and blkszbits is at most PAGE_SHIFT", {"xattr_blkaddr_max": U32_MAX, "blkszbits_max": page_shift}, ), "xattr.shared.base_plus_relative": ( xattr_relative_max > U64_MAX - xattr_base_max, "32-bit xattr block address and 32-bit shared ID cannot overflow the 64-bit sum", {"xattr_base_max": xattr_base_max, "relative_max": xattr_relative_max}, ), } ledger = [] for target in SPEC["targets"]: target_id = target["id"] if target_id not in reachability: raise SystemExit(f"target has no reachability proof: {target_id}") body = function_bodies[f"{target['path']}:{target['function']}"] if body.count(target["needle"]) != 1: raise SystemExit(f"target source is absent or ambiguous: {target_id}") reachable, reason, bounds = reachability[target_id] ledger.append( { **target, "line": line_number(sources[target["path"]], target["needle"]), "source_unique": True, "target_marker_reached": reachable, "current_freebsd_return": "+EOVERFLOW", "candidate_freebsd_return": "+EINTEGRITY", "linux_semantic_return": "-EFSCORRUPTED (no direct checked counterpart)", "source_class": "on-disk contradiction", "replay_observation": reason, "provenance_bounds": bounds, } ) data_source = sources["repo-pre-15/src/data.c"] xattr_source = sources["repo-pre-15/src/xattr.c"] linux_xattr = sources["src-linux/xattr.c"] control_anchors = ( (xattr_source, "if (off > backing_size || (uint64_t)len > backing_size - off)\n\t\treturn (EINTEGRITY);"), (xattr_source, "if (off > INT64_MAX)\n\t\t\treturn (EOVERFLOW);"), (xattr_source, "error = EOPNOTSUPP;"), (data_source, "return (ENXIO);"), (data_source, "return (EIO);"), (data_source, "if (len == 0) {\n\t\treturn (0);\n\t}"), (linux_xattr, "ret = -EOPNOTSUPP;"), (linux_xattr, "ret = -ENOMEM;"), (linux_xattr, "return PTR_ERR(it->kaddr);"), (linux_xattr, "return -EFSCORRUPTED;"), ) for source, anchor in control_anchors: if anchor not in source: raise SystemExit(f"errno preservation anchor is absent: {anchor}") abi_reachable = False abi_reason = ( "erofs_xattr_read_backing checks off <= backing_size before off > INT64_MAX; " "for a mounted primary backing, backing_size <= GEOM signed off_t mediasize" ) controls = [] for control in SPEC["preservation_controls"]: record: dict[str, Any] = dict(control) record["source_anchors_verified"] = True if control["id"] == "abi.off_gt_int64": record["target_marker_reached"] = abi_reachable record["replay_observation"] = abi_reason controls.append(record) unreachable = [record["id"] for record in ledger if not record["target_marker_reached"]] status = "GO" if not unreachable else "STOP" stop_reasons = [ { "id": target_id, "reason": next( record["replay_observation"] for record in ledger if record["id"] == target_id ), } for target_id in unreachable ] ledger_document = { "schema": 1, "gate": "G06", "candidate": "P15-052", "status": status, "resolved_base": resolved, "targets": ledger, "preservation_controls": controls, "freebsd_contract": freebsd_contract, "prototype_identity": SPEC["prototype_identity"], "prototype_disposition": ( "READY arithmetic vectors inject unconstrained internal or local values; " "they are not independently reachable on-disk fixtures" ), } (OUTPUT / "branch-ledger.json").write_text( json.dumps(ledger_document, indent=2, sort_keys=True) + "\n", encoding="ascii" ) tsv = [ "id\tfunction\tfield\tfreebsd_now\tfreebsd_candidate\tlinux_semantic\tmarker\tobservation" ] for record in ledger: tsv.append( "\t".join( ( record["id"], record["function"], record["field"], record["current_freebsd_return"], record["candidate_freebsd_return"], record["linux_semantic_return"], "reached" if record["target_marker_reached"] else "not-reached", record["replay_observation"], ) ) ) (OUTPUT / "branch-ledger.tsv").write_text("\n".join(tsv) + "\n", encoding="ascii") control_tsv = ["id\tfreebsd\tlinux\tresult\tmarker"] for record in controls: control_tsv.append( "\t".join( ( record["id"], record["freebsd"], record["linux"], record["result"], ( "not-reached" if record.get("target_marker_reached") is False else "preserved" ), ) ) ) (OUTPUT / "preservation-ledger.tsv").write_text( "\n".join(control_tsv) + "\n", encoding="ascii" ) (OUTPUT / "source-sha256.json").write_text( json.dumps(source_hashes, indent=2, sort_keys=True) + "\n", encoding="ascii" ) (OUTPUT / "freebsd-contract.json").write_text( json.dumps(freebsd_contract, indent=2, sort_keys=True) + "\n", encoding="ascii" ) result = { "schema": 1, "gate": "G06", "candidate": "P15-052", "status": status, "requested_base": REQUESTED_BASE, "resolved_base": resolved, "target_count": len(ledger), "target_markers_reached": len(ledger) - len(unreachable), "target_markers_not_reached": len(unreachable), "source_unique_count": sum(record["source_unique"] for record in ledger), "preservation_control_count": len(controls), "abi_marker_reached": abi_reachable, "stop_reasons": stop_reasons, "b20": "STOP-NO-SOURCE", "b21": "NOT_RUN", "qemu": "NOT_RUN", "qemu_reason": "host-proven target reachability failure", "full_feature_suite": "NOT_RUN", } (OUTPUT / "result.json").write_text( json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii" ) hash_lines = [] for path in sorted(OUTPUT.iterdir()): if path.is_file() and path.name != "SHA256SUMS": hash_lines.append(f"{sha256(path.read_bytes())} {path.name}") (OUTPUT / "SHA256SUMS").write_text("\n".join(hash_lines) + "\n", encoding="ascii") print(json.dumps(result, sort_keys=True)) if status != "GO": raise SystemExit(1) PY