Reject stale native runtime build provenance before release staging

This commit is contained in:
karl committed 2026-10-13 13:04:58 +02:00
1 parent ea2c3f4691
commit 07bbcb76e8
12 files changed
+204 -9

No files matched your search

+9 -3
View File
@@ -9,10 +9,11 @@ from pathlib import Path
import shutil
import subprocess
import sys
from runtime_provenance import fingerprint, UPSTREAM_COMMIT
ROOT = Path(__file__).resolve().parents[2]
TAG = "b11461"
COMMIT = "4d756bc72bf00a4aacf410ae15a2d315f3db400d"
COMMIT = UPSTREAM_COMMIT
WINDOWS_CRT_FILES = (
"concrt140.dll", "msvcp140.dll", "msvcp140_1.dll", "msvcp140_2.dll",
"msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", "vccorlib140.dll",
@@ -40,7 +41,7 @@ def elf_dynamic(path):
return values
def bundle_linux_libraries(build):
def bundle_linux_libraries(build, provenance=None):
"""Flatten approved SONAME files beside the worker and record dependency closure.
System libc/C++ ABI remains a declared distro compatibility gate. Vulkan's
@@ -94,6 +95,7 @@ def bundle_linux_libraries(build):
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
inventory = {"platform": "linux", "worker_sha256": checksum(worker),
"build_provenance": provenance if provenance is not None else fingerprint(ROOT),
"libraries": [{"path": path.name, "bytes": path.stat().st_size, "sha256": checksum(path)}
for _, path, _ in sorted(records)]}
temporary = build / "runtime-libraries.json.partial"
@@ -188,10 +190,13 @@ def main():
except ImportError as error:
raise RuntimeError("Install CMake first (python -m pip install cmake)") from error
worker_source = ROOT / "crates/searchi-semantic/native"
provenance = fingerprint(ROOT)
run([cmake_exe, "-S", worker_source, "-B", build,
"-DCMAKE_BUILD_TYPE=Release", f"-DLLAMA_CPP_DIR={source}",
f"-DSEARCHI_ACCELERATION={args.backend}"])
run([cmake_exe, "--build", build, "--config", "Release", "--parallel", args.jobs])
if fingerprint(ROOT) != provenance:
raise RuntimeError("Native build inputs changed during compilation; rerun the build")
if vulkan_loader:
for name in ["vulkan-1.dll", "VULKAN-LICENSE.txt"]:
shutil.copy2(vulkan_loader / name, build / "Release" / name)
@@ -202,13 +207,14 @@ def main():
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
inventory = {"worker_sha256": checksum(directory / "searchi-gemma-worker.exe"),
"build_provenance": provenance,
"libraries": [{"path": path.name, "bytes": path.stat().st_size, "sha256": checksum(path)}
for path in sorted(directory.glob("*.dll"))]}
temporary = directory / "runtime-libraries.json.partial"
temporary.write_text(json.dumps(inventory, indent=2), encoding="utf-8")
os.replace(temporary, directory / "runtime-libraries.json")
if linux_dispatch(args.backend):
bundle_linux_libraries(build)
bundle_linux_libraries(build, provenance)
print(f"Runtime build complete: {build}")
@@ -0,0 +1,34 @@
"""Build-input identity for detecting stale, otherwise checksum-valid runtimes."""
import hashlib
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
UPSTREAM_COMMIT = "4d756bc72bf00a4aacf410ae15a2d315f3db400d"
INPUTS = (
"crates/searchi-semantic/native/worker.cpp",
"crates/searchi-semantic/native/CMakeLists.txt",
"tools/embeddinggemma2/build_runtime.py",
"tools/embeddinggemma2/no-logits.patch",
"tools/embeddinggemma2/cpu-dispatch.patch",
"tools/embeddinggemma2/cpu-only-loader.patch",
)
def fingerprint(root=ROOT):
hashes = {}
for name in INPUTS:
path = Path(root) / name
with path.open("rb") as source:
data = source.read(1024 * 1024 + 1)
if not data or len(data) > 1024 * 1024:
raise ValueError("Native build input is empty or oversized")
# Only build-source line endings are canonicalized; indexed user bytes
# and extraction provenance are never processed by this helper.
hashes[name] = hashlib.sha256(data.replace(b"\r\n", b"\n")).hexdigest()
return {"schema": 1, "upstream_commit": UPSTREAM_COMMIT,
"normalization": "CRLF-to-LF build inputs only", "inputs": hashes}
def verify(manifest, root=ROOT):
if not isinstance(manifest.get("build_provenance"), dict) or manifest["build_provenance"] != fingerprint(root):
raise ValueError("Runtime build provenance missing or stale; rebuild the normal native runtime")
+6
View File
@@ -10,6 +10,7 @@ import shutil
import stat
import time
import uuid
from runtime_provenance import verify as verify_provenance
ROOT = Path(__file__).resolve().parents[2]
CRT_LIBRARIES = {"concrt140.dll", "msvcp140.dll", "msvcp140_1.dll", "msvcp140_2.dll", "msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", "vccorlib140.dll", "vcruntime140.dll", "vcruntime140_1.dll", "vcruntime140_threads.dll"}
@@ -339,6 +340,11 @@ def verify_runtime(source, required=False):
manifest = json.loads(inventory.read_text(encoding="utf-8"))
if not isinstance(manifest, dict):
raise RuntimeError("Invalid runtime dependency inventory")
if required or "build_provenance" in manifest:
try:
verify_provenance(manifest, ROOT)
except ValueError as error:
raise RuntimeError(str(error)) from error
with source.open("rb") as stream:
if hashlib.file_digest(stream, "sha256").hexdigest() != manifest.get("worker_sha256"):
raise RuntimeError("Runtime worker checksum mismatch")
@@ -0,0 +1,39 @@
from pathlib import Path
import tempfile
import unittest
import runtime_provenance as provenance
class ProvenanceTests(unittest.TestCase):
def test_line_endings_equivalent_but_changed_source_rejected(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name in provenance.INPUTS:
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"first\nsecond\n")
manifest = {"build_provenance": provenance.fingerprint(root)}
for name in provenance.INPUTS:
(root / name).write_bytes(b"first\r\nsecond\r\n")
provenance.verify(manifest, root)
(root / provenance.INPUTS[0]).write_bytes(b"different source\n")
with self.assertRaisesRegex(ValueError, "stale"):
provenance.verify(manifest, root)
def test_missing_old_inventory_rejected_and_inputs_bounded(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name in provenance.INPUTS:
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"source\n")
with self.assertRaisesRegex(ValueError, "missing or stale"):
provenance.verify({"worker_sha256": "old-valid-hash"}, root)
(root / provenance.INPUTS[0]).write_bytes(b"x" * (1024 * 1024 + 1))
with self.assertRaisesRegex(ValueError, "oversized"):
provenance.fingerprint(root)
if __name__ == "__main__":
unittest.main()
+7 -2
View File
@@ -9,6 +9,7 @@ import unittest
from unittest.mock import patch
import stage_runtime
import runtime_provenance
class StagingTests(unittest.TestCase):
@@ -28,7 +29,7 @@ class StagingTests(unittest.TestCase):
records.append({"path": name, "bytes": path.stat().st_size,
"sha256": hashlib.sha256(path.read_bytes()).hexdigest()})
inventory = runtime / "runtime-libraries.json"
inventory.write_text(json.dumps({"platform": "linux", "worker_sha256": hashlib.sha256(worker.read_bytes()).hexdigest(), "libraries": records}))
inventory.write_text(json.dumps({"platform": "linux", "worker_sha256": hashlib.sha256(worker.read_bytes()).hexdigest(), "build_provenance": runtime_provenance.fingerprint(), "libraries": records}))
dependencies = stage_runtime.verify_runtime(worker, True)
destination = root / "resources"
destination.mkdir()
@@ -419,6 +420,10 @@ class StagingTests(unittest.TestCase):
def test_missing_crt_notice_preserves_existing_staged_worker(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name in runtime_provenance.INPUTS:
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes((runtime_provenance.ROOT / name).read_bytes())
runtime = root / ".searchi/runtime/auto/Release"
runtime.mkdir(parents=True)
worker = runtime / "searchi-gemma-worker.exe"
@@ -429,7 +434,7 @@ class StagingTests(unittest.TestCase):
data = name.encode()
(runtime / name).write_bytes(data)
records.append({"path": name, "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()})
(runtime / "runtime-libraries.json").write_text(json.dumps({"worker_sha256": hashlib.sha256(worker.read_bytes()).hexdigest(), "libraries": records}))
(runtime / "runtime-libraries.json").write_text(json.dumps({"worker_sha256": hashlib.sha256(worker.read_bytes()).hexdigest(), "build_provenance": runtime_provenance.fingerprint(root), "libraries": records}))
license_path = root / ".searchi/references/llama.cpp/LICENSE"
license_path.parent.mkdir(parents=True)
license_path.write_text("license")