Skip to content

Verify the DXVK and VKD3D-Proton downloads against pinned checksums - #17

Merged
BenA-SA merged 1 commit into
masterfrom
build/pin-download-checksums
Sep 20, 2026
Merged

BenA-SA merged 1 commit into
masterfrom
build/pin-download-checksums

Conversation

@BenA-SA

@BenA-SA BenA-SA commented Sep 20, 2026

Copy link
Copy Markdown
Owner

The problem

Containerfile.full piped both graphics translation layers from GitHub straight into tar:

RUN curl -fsSL ".../dxvk-${DXVK_VERSION}.tar.gz"           | tar -xz -C /opt \
 && curl -fsSL ".../vkd3d-proton-${VKD3D_VERSION}.tar.zst" | tar --zstd -x -C /opt

The versions were pinned; the bytes were not. A release tag is mutable — delete and re-push it and that URL serves something else, with nothing in the build to notice. What lands in /opt is DLLs Wine loads into the process driving the trainer. It was the only unverified download left: apt checks the Debian/WineHQ/Mesa packages against their keys, and the base images are digest-addressed.

The pipe also made verification structurally impossible: curl | tar -x extracts as it downloads, so the complete file never exists on disk to be inspected.

What changed

Each checksum sits directly under the version it belongs to, so a bump that forgets the digest shows up in the diff:

ARG DXVK_VERSION=3.1
ARG DXVK_SHA256=30f9cc326874be344285582275446968cfa4c069db31ce56df312d6644179154
ARG VKD3D_VERSION=3.0.1
ARG VKD3D_SHA256=3cf2315522af5e43605ef6d3c41dad91387040bf97199934f3f7ab76caaa2f0c

One RUN downloads both, verifies both, then extracts both — so a bad DXVK tarball can't leave a half-populated /opt — and removes the archives in the same layer. No new packages: curl and zstd were already kept by ADR-0008.

Checksums are ours, computed by downloading the artefacts. Neither project publishes one — each release has exactly one asset, no .sha256/.asc/.sig, and no digest in either release body. The only corroboration available is that both match the size GitHub's API reports for the asset. This is tamper evidence, not provenance, and the ADR says so.

Why not ADD --checksum

It was tested against the real podman 5.8.4 and does work — correct sum builds, wrong sum fails with unexpected response digest. Rejected anyway: ADD from a URL does not extract, so a later RUN must unpack, and that RUN's rm cannot reclaim what ADD already committed. Measured on an identical base:

Variant Image
base (debian:trixie + curl, zstd) 146 MB
ADD --checksum ×2 + RUN tar && rm 228 MB
single RUN curl + verify + tar + rm 205 MB

23 MB of permanently stranded tarball — a third of what ADR-0008 had just clawed back.

Evidence the gate bites

Happy path — a full podman build --no-cache -f Containerfile.full, not just the layer: succeeds in 7m12s, image 6.63 GB (unchanged from ADR-0008, so the rm does reclaim), both archives extracted where build-prefix.sh's /opt/dxvk-* / /opt/vkd3d-proton-* globs find them, and the prefix resolves system32/d3d12.dll -> /opt/vkd3d-proton-3.0.1/x64/d3d12.dll.

Corrupted checksum — DXVK_SHA256 set to deadbe…:

/tmp/dxvk.tar.gz: FAILED
/tmp/vkd3d.tar.zst: OK
sha256sum: WARNING: 1 computed checksum did NOT match
Error: ... while running runtime: exit status 1

--strict turned out to be load-bearing. The first draft used a plain sha256sum -c -, which is quietly broken: a blank or malformed checksum line is only a warning and the build still exits 0. Blanking DXVK_SHA256 proves it:

# sha256sum -c -            -> "WARNING: 1 line is improperly formatted"  ... exit 0, extracted unverified
# sha256sum --strict -c -   -> same warning                                ... exit 1

Without --strict the gate degrades silently to no gate in exactly the case it most needs to hold — a half-finished version bump.

What it costs

  • The checksums are ours, so they prove nothing changed since today, not that today's bytes were honest. If either project starts publishing digests, switch to those.
  • A version bump is now a two-line edit; getting it half-right fails the build. That's the point, but it's friction, and anyone "simplifying" the --strict away reopens the hole.
  • Slightly more local I/O (~23 MB written and read back) instead of stream extraction. Noise against a 385 s build, and the layer caches.
  • The TPV installer is still unverified — entrypoint.sh fetches it at runtime from a vendor URL, and it self-updates, so a pinned digest would break it rather than protect it. The image's largest unverified input stays unverified; that needs its own change.

Linters run locally: shellcheck --severity=warning, ruff 0.11.7 on tools/, hadolint v2.15.1-alpine on both Containerfiles with the repo's ignore set — all clean.

ADR: docs/adr/0009-verify-the-dxvk-and-vkd3d-downloads-against-pinned-checksums.md (Builds on ADR-0008).

Both graphics translation layers were piped straight from a GitHub release
into tar with no integrity check. The versions were ARG-pinned but the bytes
were not, and a release tag is mutable: re-pushing a tag swaps what that URL
serves, and nothing in the build would notice. What lands in /opt is a set of
DLLs Wine loads into the process that drives the trainer, so this was the one
unverified input left in an image whose apt packages and base images are
already checked.

Each archive now carries a sha256 next to its version ARG. The download writes
to /tmp, both files are verified before either is extracted, and the tarballs
are removed in the same layer. Neither upstream publishes a digest or
signature, so the checksums are computed locally and corroborated only against
the asset sizes GitHub's API reports; this is tamper evidence, not provenance.

ADD --checksum was tested against podman 5.8.4 and does work, but ADD from a
URL cannot extract, so both tarballs would be committed to a layer a later rm
cannot reclaim: 228 MB against 205 MB on an identical base, 23 MB of dead
weight in an image ADR-0008 had just trimmed by 60 MB.

sha256sum is run with --strict deliberately. Without it a blank or malformed
checksum line is only a warning and the build still exits 0, which would turn
a half-finished version bump into a silently unverified extract.

A full no-cache build of Containerfile.full succeeds in 7m12s and still
produces a 6.63 GB image, with both archives extracted where build-prefix.sh
expects them.
@BenA-SA
BenA-SA merged commit 4cc3ca2 into master Sep 20, 2026
4 checks passed
@BenA-SA
BenA-SA deleted the build/pin-download-checksums branch September 20, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant