From d6c0b7bc82492634b992301aff9908b40450adb2 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 4 Oct 2026 22:03:28 +0200 Subject: [PATCH] ci(native-arch): verify the Go tarball before running the compiler out of it Both remote scripts did ( cd "$workdir" && curl -fsSL "https://go.dev/dl/${tarball}" -o "$tarball" && tar -xzf "$tarball" ) and then put that directory on PATH and executed `go` from it. HTTPS and nothing else: no integrity check between downloading a 64 MB archive and running a compiler out of it, on the GCC Compile Farm and a LinuxONE host -- machines shared under an acceptable-use policy, where we are a guest. Now it downloads, verifies SHA256 against a sum pinned in this file, and only then extracts. The sums are pinned HERE rather than fetched alongside the tarball on purpose: a checksum served from the same origin as the file it describes cannot attest to that file. Fetching go.dev's JSON index at run time would protect against a truncated download and nothing else, which is the weaker thing wearing the stronger thing's clothes. The cost is that bumping GO_VERSION means updating three lines. The `*)` arm is what makes that cost visible instead of silent: an unpinned version or architecture exits 1 naming what to add, rather than skipping the check. A guard that quietly does nothing when it does not recognise its input is the failure mode worth designing against here. Sums taken from https://go.dev/dl/?mode=json for go1.26.4, and cross-checked programmatically against that index after being written into the file, so a transcription slip cannot survive: ppc64le 53f49b8c...bc22e riscv64 2b9ba137...40555 s390x 1e12a231...b3ca1 The compare logic was exercised locally in all four states before pushing -- matching sum passes; a mismatch exits 1 printing both values; an unpinned GOVER and an unpinned GOARCH each exit 1 with the "refusing to run an unverified toolchain" message. The local run used `shasum -a 256` since this is a mac; the workflow uses `sha256sum`, which is what the Linux hosts have. Applied at both sites. The cfarm job covers ppc64le and riscv64, the s390x job covers s390x, and they are near-duplicate blocks 100 lines apart -- fixing one would have left the other running an unverified toolchain. Deferred from #778, which fixed the other defect in these same two blocks. Found in the build/CI audit for #768. Co-Authored-By: Claude Opus 5 --- .github/workflows/native-arch.yml | 60 +++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/.github/workflows/native-arch.yml b/.github/workflows/native-arch.yml index 630431c0..8fc31675 100644 --- a/.github/workflows/native-arch.yml +++ b/.github/workflows/native-arch.yml @@ -193,7 +193,35 @@ jobs: workdir="$(mktemp -d)" tarball="go${GOVER}.linux-${GOARCH}.tar.gz" echo "== fetching $tarball" - ( cd "$workdir" && curl -fsSL "https://go.dev/dl/${tarball}" -o "$tarball" && tar -xzf "$tarball" ) + curl -fsSL "https://go.dev/dl/${tarball}" -o "$workdir/$tarball" + + # Verify before executing anything out of it. These sums are the ones + # published at https://go.dev/dl/?mode=json for this GO_VERSION, and + # they live HERE, in the repository, rather than being fetched next to + # the tarball: a checksum served from the same origin as the file it + # describes cannot attest to that file. Bumping GO_VERSION means + # updating these, and the `*)` arm below is what says so out loud + # instead of silently skipping the check. + case "${GOVER}/${GOARCH}" in + 1.26.4/ppc64le) want=53f49b8c7eace2d30389327b4a516b13321f90377fdf5929a6b63174609bc22e ;; + 1.26.4/riscv64) want=2b9ba137baaa3031fd74330cb36ab54c5abe380867ca9fbab7c552f3db740555 ;; + 1.26.4/s390x) want=1e12a2318f3dd4c57027c865f6cb51f5d1e36b02d10f3e6316ce7b61a27b3ca1 ;; + *) + echo "== no pinned SHA256 for go${GOVER} on ${GOARCH}" + echo "== add it from https://go.dev/dl/?mode=json; refusing to run an unverified toolchain" + exit 1 + ;; + esac + got="$(sha256sum "$workdir/$tarball" | cut -d' ' -f1)" + if [ "$got" != "$want" ]; then + echo "== SHA256 mismatch for $tarball" + echo "== want $want" + echo "== got $got" + exit 1 + fi + echo "== $tarball sha256 verified" + + ( cd "$workdir" && tar -xzf "$tarball" ) export PATH="$workdir/go/bin:$PATH" export GOROOT="$workdir/go" fi @@ -269,7 +297,35 @@ jobs: workdir="$(mktemp -d)" tarball="go${GOVER}.linux-${GOARCH}.tar.gz" echo "== fetching $tarball" - ( cd "$workdir" && curl -fsSL "https://go.dev/dl/${tarball}" -o "$tarball" && tar -xzf "$tarball" ) + curl -fsSL "https://go.dev/dl/${tarball}" -o "$workdir/$tarball" + + # Verify before executing anything out of it. These sums are the ones + # published at https://go.dev/dl/?mode=json for this GO_VERSION, and + # they live HERE, in the repository, rather than being fetched next to + # the tarball: a checksum served from the same origin as the file it + # describes cannot attest to that file. Bumping GO_VERSION means + # updating these, and the `*)` arm below is what says so out loud + # instead of silently skipping the check. + case "${GOVER}/${GOARCH}" in + 1.26.4/ppc64le) want=53f49b8c7eace2d30389327b4a516b13321f90377fdf5929a6b63174609bc22e ;; + 1.26.4/riscv64) want=2b9ba137baaa3031fd74330cb36ab54c5abe380867ca9fbab7c552f3db740555 ;; + 1.26.4/s390x) want=1e12a2318f3dd4c57027c865f6cb51f5d1e36b02d10f3e6316ce7b61a27b3ca1 ;; + *) + echo "== no pinned SHA256 for go${GOVER} on ${GOARCH}" + echo "== add it from https://go.dev/dl/?mode=json; refusing to run an unverified toolchain" + exit 1 + ;; + esac + got="$(sha256sum "$workdir/$tarball" | cut -d' ' -f1)" + if [ "$got" != "$want" ]; then + echo "== SHA256 mismatch for $tarball" + echo "== want $want" + echo "== got $got" + exit 1 + fi + echo "== $tarball sha256 verified" + + ( cd "$workdir" && tar -xzf "$tarball" ) export PATH="$workdir/go/bin:$PATH" export GOROOT="$workdir/go" fi