From d18e89c7aaaa0e731cfd1e1ce10f7b343b3d0ff1 Mon Sep 17 00:00:00 2001 From: chen21019 <19357113+chen21019@users.noreply.github.com> Date: Sun, 25 Oct 2026 17:44:15 +0800 Subject: [PATCH] Pin public Brotli headers for the final-image linkage probe --- .github/workflows/publish-current-server.yml | 24 +++++++++++++++++++ .../test-server-backport-consumer-contract.py | 3 +++ 2 files changed, 27 insertions(+) diff --git a/.github/workflows/publish-current-server.yml b/.github/workflows/publish-current-server.yml index 2c164ddc94..a19b728639 100644 --- a/.github/workflows/publish-current-server.yml +++ b/.github/workflows/publish-current-server.yml @@ -221,7 +221,31 @@ jobs: run: | set -Eeuo pipefail evidence="$RUNNER_TEMP/server-evidence" + # Compile against pinned public declarations, without installing a + # decoder or relying on runner-provided development packages. + python3 - "$evidence/brotli-headers" <<'PY' + import hashlib, io, json, pathlib, re, sys, tarfile, urllib.request + lock = json.loads(pathlib.Path('source/server/security/ubuntu-backports/brotli/brotli.lock.json').read_text()) + item, = (x for x in lock['sourceFiles'] if x['name'] == 'brotli_1.2.0.orig.tar.gz') + with urllib.request.urlopen(item['url'], timeout=60) as response: + data = response.read(item['size'] + 1) + assert len(data) == item['size'] and hashlib.sha256(data).hexdigest() == item['sha256'] + output = pathlib.Path(sys.argv[1]) / 'brotli' + output.mkdir(parents=True) + with tarfile.open(fileobj=io.BytesIO(data)) as archive: + names = set() + for member in archive.getmembers(): + match = re.fullmatch(r'google-brotli-028fb5a/c/include/brotli/([a-z_]+\.h)', member.name) + if not match: + continue + assert member.isfile() and match[1] not in names + names.add(match[1]) + (output / match[1]).write_bytes(archive.extractfile(member).read()) + assert {'decode.h', 'encode.h', 'types.h', 'port.h'} <= names + assert hashlib.sha256((output / 'decode.h').read_bytes()).hexdigest() == lock['baselineSha256']['c/include/brotli/decode.h'] + PY cc -O2 -Wall -Wextra -Werror \ + -I"$evidence/brotli-headers" \ source/server/security/ubuntu-backports/bundle/installer/testdata/consumer-brotli-probe.c \ -ldl -o "$evidence/brotli-runtime-probe" docker run --rm --network none \ diff --git a/scripts/test-server-backport-consumer-contract.py b/scripts/test-server-backport-consumer-contract.py index d5c2e2d7bd..62a6be7798 100644 --- a/scripts/test-server-backport-consumer-contract.py +++ b/scripts/test-server-backport-consumer-contract.py @@ -65,6 +65,8 @@ def verify(files): and 'test -z "$brotli_integrity"' in source for source in (runtime, build)), 'Brotli dpkg verification failure must propagate before its empty-output check') for marker in ['consumer-brotli-probe.c', '--entrypoint /tmp/brotli-runtime-probe "$CANDIDATE_IMAGE"', + 'brotli/brotli.lock.json', '-I"$evidence/brotli-headers"', + "hashlib.sha256(data).hexdigest() == item['sha256']", 'PASS: three installed Brotli SONAMEs RTLD_NOW, ordinary compression/decompression']: require(marker in publisher, 'Missing actual final-image ordinary Brotli probe: ' + marker) require(' brotli-runtime.txt' in publisher and '"$evidence/brotli-runtime.txt"' in publisher, @@ -135,6 +137,7 @@ def test_missing_brotli_runtime_or_published_proof_is_rejected(self): (BUILD, 'brotli_linkage="$(ldd -r '), (DOCKER, 'test -z "$brotli_integrity"'), (BUILD, 'test -z "$brotli_integrity"'), + (PUBLISH, '-I"$evidence/brotli-headers"'), (PUBLISH, '--entrypoint /tmp/brotli-runtime-probe "$CANDIDATE_IMAGE"'), (PUBLISH, ' brotli-runtime.txt')]: with self.subTest(name=name), self.assertRaises(AssertionError):