This commit is contained in:
2026-08-18 09:20:44 +02:00
commit b826cd721a
522 changed files with 93730 additions and 0 deletions
+380
View File
@@ -0,0 +1,380 @@
#!/bin/sh
set -eu
BASE=bd5a09054e5cf89efd4db82aadb051f20b06ebf7
FREEBSD_SRC=/work/build/freebsd-src
OUTPUT=
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../../../.." && pwd)
INPUT=$SCRIPT_DIR/P15-032-input.json
usage()
{
printf '%s\n' "usage: $0 --output ABSOLUTE_PATH [--base SHA] [--freebsd-src PATH]" >&2
exit 20
}
while [ "$#" -gt 0 ]; do
case "$1" in
--output)
[ "$#" -ge 2 ] || usage
OUTPUT=$2
shift 2
;;
--base)
[ "$#" -ge 2 ] || usage
BASE=$2
shift 2
;;
--freebsd-src)
[ "$#" -ge 2 ] || usage
FREEBSD_SRC=$2
shift 2
;;
*)
usage
;;
esac
done
[ -n "$OUTPUT" ] || usage
case "$OUTPUT" in
/*) ;;
*) printf '%s\n' "P15-032: --output must be absolute" >&2; exit 20 ;;
esac
if [ -d "$OUTPUT" ] && [ -n "$(find "$OUTPUT" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
printf '%s\n' "P15-032: --output must be empty" >&2
exit 20
fi
mkdir -p "$OUTPUT"
TMP=$(mktemp -d "${TMPDIR:-/tmp}/P15-032.XXXXXX")
cleanup()
{
rm -rf "$TMP"
}
trap cleanup EXIT HUP INT TERM
printf 'argv=%s\n' "$0 --output $OUTPUT --base $BASE --freebsd-src $FREEBSD_SRC" >"$OUTPUT/runner.argv"
printf 'whole_gate_deadline_seconds=120\n' >"$OUTPUT/deadlines.txt"
printf 'command_deadline_seconds=10\n' >>"$OUTPUT/deadlines.txt"
printf 'compile_deadline_seconds=20\n' >>"$OUTPUT/deadlines.txt"
printf 'model_deadline_seconds=10\n' >>"$OUTPUT/deadlines.txt"
set +e
/usr/bin/timeout 120s python3 - "$ROOT" "$FREEBSD_SRC" "$INPUT" "$OUTPUT" "$TMP" "$BASE" >"$OUTPUT/runner.stdout" 2>"$OUTPUT/runner.stderr" <<'PY'
import datetime
import hashlib
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
root = Path(sys.argv[1]).resolve()
freebsd = Path(sys.argv[2]).resolve()
input_path = Path(sys.argv[3]).resolve()
output = Path(sys.argv[4]).resolve()
tmp = Path(sys.argv[5]).resolve()
base = sys.argv[6]
cfg = json.loads(input_path.read_text(encoding="utf-8"))
command_timeout = cfg["timeouts_seconds"]["command"]
commands = []
def run(argv, cwd, timeout=command_timeout, check=True):
started = datetime.datetime.now(datetime.timezone.utc)
cp = subprocess.run(argv, cwd=cwd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=timeout, check=False)
commands.append({
"argv": argv,
"cwd": str(cwd),
"timeout_seconds": timeout,
"started_utc": started.isoformat(),
"exit": cp.returncode,
"stdout": cp.stdout.decode("utf-8", "replace"),
"stderr": cp.stderr.decode("utf-8", "replace"),
})
if check and cp.returncode != 0:
raise RuntimeError(f"command failed ({cp.returncode}): {shlex.join(argv)}")
return cp
def sha256(data):
return hashlib.sha256(data).hexdigest()
def bounded_read(path):
data = path.read_bytes()
if len(data) > 4 * 1024 * 1024:
raise RuntimeError(f"refusing oversized contract file: {path}")
return data
def base_blob(path):
return run(["git", "show", f"{base}:{path}"], root).stdout
def line_of(text, marker):
return text[:text.index(marker)].count("\n") + 1
started = datetime.datetime.now(datetime.timezone.utc)
if base != cfg["frozen_base"]:
raise RuntimeError("requested base does not match frozen base")
head = run(["git", "rev-parse", "HEAD"], root).stdout.decode().strip()
remote = run(["git", "rev-parse", "xdm/main"], root).stdout.decode().strip()
if head != base or remote != base:
raise RuntimeError(f"base mismatch: HEAD={head} xdm/main={remote} expected={base}")
allowed = {
"repo-pre-15/tests/pre15/gates/P15-032.sh",
"repo-pre-15/tests/pre15/gates/P15-032-input.json",
"repo-pre-15/docs/pre15-stage0/P15-032.md",
}
status_raw = run(["git", "status", "--porcelain=v1", "-z"], root).stdout
entries = [entry for entry in status_raw.decode().split("\0") if entry]
paths = set()
for entry in entries:
path = entry[3:]
if " -> " in path:
path = path.split(" -> ", 1)[1]
paths.add(path)
if not paths.issubset(allowed):
raise RuntimeError(f"out-of-scope worktree paths: {sorted(paths - allowed)}")
outer_head = run(["git", "rev-parse", "HEAD"], freebsd).stdout.decode().strip()
if outer_head != cfg["freebsd_source"]["outer_git_head"]:
raise RuntimeError(f"FreeBSD outer source identity mismatch: {outer_head}")
contracts = []
source_hashes = {}
for contract in cfg["contracts"]:
kind = contract["root"]
rel = contract["path"]
if kind == "freebsd":
data = bounded_read(freebsd / rel)
elif kind == "repo_base":
data = base_blob(rel)
elif kind == "linux_reference":
data = base_blob(rel)
else:
raise RuntimeError(f"unknown contract root: {kind}")
actual = sha256(data)
source_hashes[f"{kind}:{rel}"] = actual
if actual != contract["sha256"]:
raise RuntimeError(f"hash mismatch for {kind}:{rel}: {actual}")
text = data.decode("utf-8", "replace")
matches = []
for marker in contract.get("must_contain", []):
if marker not in text:
raise RuntimeError(f"missing contract marker in {rel}: {marker!r}")
matches.append({"marker": marker, "line": line_of(text, marker)})
forbidden = []
for marker in contract.get("must_not_contain", []):
present = marker in text
forbidden.append({"marker": marker, "present": present})
if present:
raise RuntimeError(f"forbidden bridge marker in {rel}: {marker!r}")
contracts.append({"root": kind, "path": rel, "sha256": actual,
"matches": matches, "negative_matches": forbidden})
events = cfg["required_events"]
event_index = {name: index for index, name in enumerate(events)}
required_order = [
("check_self_identity", "first_io"),
("check_namespace_ancestors", "first_io"),
("trace_storage_ancestors", "first_io"),
("crhold_mount_cred", "first_io"),
("vop_set_text", "first_io"),
("register_upper_mount", "first_io"),
("io_complete", "drain_inflight"),
("drain_inflight", "unregister_upper_mount"),
("unregister_upper_mount", "vn_close"),
("vn_close", "crfree_mount_cred"),
("crfree_mount_cred", "release_last_reference"),
]
for before, after in required_order:
if event_index[before] >= event_index[after]:
raise RuntimeError(f"invalid lifecycle order: {before} !< {after}")
nodes = set()
adj = {}
for before, after in cfg["lock_edges"]:
nodes.update((before, after))
adj.setdefault(before, []).append(after)
visiting = set()
visited = set()
def visit(node):
if node in visiting:
raise RuntimeError(f"lock graph cycle at {node}")
if node in visited:
return
visiting.add(node)
for child in adj.get(node, []):
visit(child)
visiting.remove(node)
visited.add(node)
for node in sorted(nodes):
visit(node)
dot = ["digraph P15_032 {", " rankdir=LR;"]
for before, after in cfg["lock_edges"]:
dot.append(f' "{before}" -> "{after}";')
dot.append("}")
(output / "lockgraph.dot").write_text("\n".join(dot) + "\n", encoding="utf-8")
invariants = cfg["required_invariants"]
closeable = [name for name in invariants if name != "storage_ancestor_check_before_io"]
controls = cfg["false_go_controls"]
if set(controls) != set(closeable):
raise RuntimeError("false-GO controls do not exactly cover closeable invariants")
enum_lines = [f" INV_{name.upper()} = 1ULL << {index}," for index, name in enumerate(closeable)]
required_mask = " |\n ".join(f"INV_{name.upper()}" for name in closeable)
control_rows = ",\n".join(
f' {{"{name}", INV_{name.upper()}}}' for name in controls
)
model = f'''#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
enum invariant {{
{os.linesep.join(enum_lines)}
}};
struct scenario {{
uint64_t present;
int storage_ancestry_identity_api;
}};
struct control {{
const char *name;
uint64_t bit;
}};
static const uint64_t required =
{required_mask};
static int
visible_only_oracle(const struct scenario *scenario)
{{
return ((scenario->present & required) == required ? 0 : EINVAL);
}}
static int
rigorous_oracle(const struct scenario *scenario)
{{
int error;
error = visible_only_oracle(scenario);
if (error != 0)
return (error);
if (!scenario->storage_ancestry_identity_api)
return (EOPNOTSUPP);
return (0);
}}
int
main(void)
{{
static const struct control controls[] = {{
{control_rows}
}};
struct scenario candidate = {{ required, 0 }};
struct scenario mutation;
size_t index;
if (visible_only_oracle(&candidate) != 0)
return (1);
if (rigorous_oracle(&candidate) != EOPNOTSUPP)
return (2);
puts("hidden-md-edge: visible-only=GO rigorous=STOP:EOPNOTSUPP");
for (index = 0; index < sizeof(controls) / sizeof(controls[0]); index++) {{
mutation = candidate;
mutation.present &= ~controls[index].bit;
if (visible_only_oracle(&mutation) == 0 || rigorous_oracle(&mutation) == 0)
return (3);
printf("false-go-control:%s=REJECTED\\n", controls[index].name);
}}
puts("lifecycle-model=PASS");
return (0);
}}
'''
model_path = tmp / "P15-032-model.c"
binary_path = tmp / "P15-032-model"
model_path.write_text(model, encoding="utf-8")
compile_cp = run(["/usr/bin/timeout", f"{cfg['timeouts_seconds']['compile']}s",
"/usr/bin/cc", "-std=c11", "-Wall", "-Wextra", "-Werror",
"-O2", "-o", str(binary_path), str(model_path)], tmp,
timeout=cfg["timeouts_seconds"]["compile"] + 2)
model_cp = run(["/usr/bin/timeout", f"{cfg['timeouts_seconds']['model']}s",
str(binary_path)], tmp,
timeout=cfg["timeouts_seconds"]["model"] + 2)
(output / "owned-temp-model.c").write_bytes(model_path.read_bytes())
(output / "owned-temp-model.stdout").write_bytes(model_cp.stdout)
(output / "owned-temp-model.stderr").write_bytes(model_cp.stderr)
false_go = {
"controls": [{"removed_invariant": name, "outcome": "REJECTED"} for name in controls],
"adversarial_hidden_edge": {
"topology": "regular vnode -> filesystem -> md GEOM provider -> private backing vnode",
"visible_only_oracle": "GO",
"rigorous_oracle": "STOP:EOPNOTSUPP",
},
}
(output / "false-go-controls.json").write_text(json.dumps(false_go, indent=2) + "\n")
(output / "contracts.json").write_text(json.dumps(contracts, indent=2) + "\n")
(output / "source-hashes.json").write_text(json.dumps(source_hashes, indent=2, sort_keys=True) + "\n")
(output / "commands.json").write_text(json.dumps(commands, indent=2) + "\n")
state = {
"events": events,
"required_order": required_order,
"local_lifecycle": "PASS",
"storage_ancestor_identity": "UNAVAILABLE",
"typed_errno_if_detectable": cfg["typed_errno"]["cycle"],
"typed_errno_for_unsupported_graph": cfg["typed_errno"]["unsupported_dependency_graph"],
}
(output / "state-model.json").write_text(json.dumps(state, indent=2) + "\n")
result = {
"unit": cfg["unit"],
"batch": cfg["batch"],
"classification": "GATE_STOP",
"decision": "STOP",
"exit": 10,
"base": base,
"head": head,
"xdm_main": remote,
"freebsd_git_head": outer_head,
"freebsd_identity": cfg["freebsd_source"]["identity"],
"started_utc": started.isoformat(),
"finished_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"closeable_invariants": closeable,
"blocking_condition": cfg["blocking_condition"],
"source_changes_permitted": False,
"tests_run": ["contract extraction", "owned-temp C lifecycle model", "adversarial false-GO controls"],
"tests_omitted": ["D", "H", "K", "Q", "TC006", "TC179", "TC184", "full feature", "smoke"],
}
(output / "result.json").write_text(json.dumps(result, indent=2) + "\n")
print("P15-032 STOP: no public identity-preserving VFS/GEOM-to-backing-vnode ancestry API")
sys.exit(10)
PY
RC=$?
set -e
printf 'exit=%s\n' "$RC" >"$OUTPUT/runner.exit"
case "$RC" in
10)
printf '%s\n' GATE_STOP >"$OUTPUT/classification"
;;
124)
printf '%s\n' RUNNER_TIMEOUT >"$OUTPUT/classification"
;;
0)
printf '%s\n' GATE_GO >"$OUTPUT/classification"
;;
*)
printf '%s\n' RUNNER_FAILURE >"$OUTPUT/classification"
;;
esac
(cd "$OUTPUT" && find . -maxdepth 1 -type f ! -name manifest.sha256 -print | LC_ALL=C sort | xargs sha256sum) >"$OUTPUT/manifest.sha256"
exit "$RC"