fix(build): pin the module-signing key to a fixed in-tree path #35
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/in-tree-signing-key-path"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Every kernel we've shipped publishes a runner temp-directory name
Confirmed by reading it off a running machine, not inferred:
configure-kernel.shappended a per-buildmktemp -dpath to the tree's.configafter the fidelity assertion, andbuild-debs.shnormalized it back to the placeholder only in the separately capturedfinal.config. Butbindeb-pkgships the tree's.configas/boot/config-<rel>, embeds it intovmlinuxviaCONFIG_IKCONFIG(=/proc/config.gz), and packages it in the headers.deb.build-debs.sh:215's own comment says as much: "make clean… preserves .config. The merged .config from configure-kernel.sh is what bindeb-pkg uses."Three consequences, ascending:
caton that path fails on a user's machine, as it must).mktempdiffers per run — directly contradictingbuild-debs.sh:99-105.CONFIG_MODULE_SIG_ALL=yand the headers package carries that.config, so an out-of-tree build going through the kernel's ownmodules_installsigning path reaches for a key file that doesn't exist.docs/users/dkms.md:25already concedes that path is unvalidated against a published release, so this is an open interaction, not a known-good one. See Verification for how to settle it.The fix
Make
CONFIG_MODULE_SIG_KEYa constant in-tree path, so the value the fidelity assertion checks is the value that ships. Nothing to rewrite, nothing to normalize, no ordering requirement against the assertion.certs/unredacted-signing.pem, deliberately not the stockcerts/signing_key.pem. Upstreamcerts/Makefileguards aFORCE+if_changed,gen_keyrule withifeq ($(CONFIG_MODULE_SIG_KEY),certs/signing_key.pem)— under the default name kbuild would regenerate our pre-placed key with a random one and ship a kernel trusting a key nobody holds. That name is also onMRPROPER_FILES. With a non-default name no generator rule exists, so a missing key fails loudly.That kbuild behaviour is the load-bearing assumption here, and it can't be tested without a real build — so
build-debs.shnow asserts the outcome instead of trusting it. Three hard failures, all post-make, so tripping one costs a rebuild and never a bad release:certs/signing_key.x509, which it links into.builtin_trusted_keys(the trust root for every module signature this kernel accepts), must equal our PEM's certificate. This is what converts a wrong kbuild assumption into an aborted build rather than a silently mis-keyed kernel..deb— we now place a key inside a treebindeb-pkgpackages from.mkdebian's file list shouldn't matchcerts/*.pem, but the cost of being wrong is publishing a signing key./boot/config-*inside the image.debmust be byte-identical to the capturedfinal.config. That divergence is this bug; now it can't recur silently.Also: the CI run number leaves the shipped bytes
It reached
KBUILD_BUILD_VERSION→UTS_VERSIONinsidevmlinux, while being recorded nowhere and hard-coded to1inrepro-check.sh— three independent reasons a rebuild could never match.KBUILD_BUILD_VERSIONnow derives from the tag (PACKAGING_REVISION, default 1); traceability moved tomanifest.ci_run_number(#33).repro-check.shalso now: passesPACKAGING_REVISIONparsed from the tag, which makes the five.Preleases structurally verifiable for the first time (the rebuild used to emit...-hardened1.debsand reportMISSINGfor every file — reading like tampering rather than a missing input); cross-checks the manifest against the tag it's filed under; and generates a throwaway signing key, since without one the rebuild would now fail atmake.Byte-identity on the cert-embedding artifacts (
vmlinuz,System.map,-dbg) still needs the real private key — handing a verifier the published.dercannot help, because signing needs the private half — soREPRO_SIGNING_KEY_PATHis honoured for operators who hold it. Properly localizing that divergence is follow-up work on the comparison itself.Verification
Ran the real
configure-kernel.shagainst a stubmake+merge_config.shharness:MODULE_SIG_KEY_PATHset)0600, zero/tmp/occurrences in.config, exactly oneMODULE_SIG_KEYline.configbyte-identical (42b7bec5…both times) — the direct regression test for this bugintent.config/.configdesyncCert-identity assertion (pure
openssl, so testable locally): matching cert → passes; a foreign cert → fails, which is exactly the kbuild-regenerated-key case it exists to catch; absent.x509→ the actionable-message branch, not a silent skip. Leak-guard grep verified both directions.Tag parsing checked against all 12 release directories: correct
PACKAGING_REVISIONfor the 5.Preleases, and the twonull-version releases (v7.0.10-hardened1,…1.2) now exit 2 with an explanation instead of proceeding with"null"as a version string.shellcheck,bash -n,py_compile,yamllint -d relaxed,intent-matches-policyall clean. Confirmed noREVISION=remains anywhere andci_run_numberrecording is intact.Not testable on this host (no
dpkg-deb): the config-parity and leak-guard assertions need Linux or the pinned container. They are the two that run against real.debs, and both fail closed.Draft gate before dispatching publish for v7.1.5
This is what makes landing it pre-tag safe — nothing is published until you dispatch
publish.yml:Expect
certs/unredacted-signing.pemand no/tmpstring. Then confirm thefinal.configasset is byte-identical to that,sbverify --cert unredacted-hardened.derthe vmlinuz, and on a scratch VM boot it andmodprobea signed in-tree module to confirmMODULE_SIG_FORCEaccepts it. If anything is wrong, delete the tag and re-cut.Worth doing against v7.1.4 first: build any DKMS module on a VM with the current kernel + headers and watch for a
sign-file/No such file or directoryerror naming the/tmp/…path. If v7.1.4 already fails that way, this PR is a user-facing bug fix and belongs in the release notes as one. If it passes, the interaction is benign and this stays hygiene + reproducibility.🤖 Generated with Claude Code
CHANGES SHIPPED BYTES. Verify against the draft release before publishing. Every kernel this project has shipped publishes a runner temp-directory name. Confirmed by reading it off a running machine, not inferred: # zcat /proc/config.gz | grep MODULE_SIG_KEY CONFIG_MODULE_SIG_KEY="/tmp/tmp.TliL5BYWSL/signing.pem" Mechanism: configure-kernel.sh appended a per-build `mktemp -d` path to the TREE's .config after the fidelity assertion, and build-debs.sh normalized that value back to the placeholder only in the separately-captured final.config. But bindeb-pkg ships the tree's .config as /boot/config-<rel>, embeds it into vmlinux via CONFIG_IKCONFIG (= /proc/config.gz), and packages it in the headers .deb. Three consequences, ascending: 1. An information leak: the runner's temp-dir name is in every kernel. The private key itself was never shipped. 2. Same-tag rebuilds were never bit-identical, because mktemp differs per run -- directly contradicting build-debs.sh's own comment. 3. Possibly broken DKMS, untested either way: MODULE_SIG_ALL=y and the headers package carries that .config, so an out-of-tree build going through the kernel's own modules_install signing path reaches for a key file that does not exist. docs/users/dkms.md already concedes that path is unvalidated against a published release, so this is an open interaction. The Verification section below says how to settle it. Fix: make CONFIG_MODULE_SIG_KEY a CONSTANT in-tree path, so the value the fidelity assertion checks is the value that ships. Nothing to rewrite, nothing to normalize, no ordering requirement against the assertion. certs/unredacted-signing.pem, deliberately NOT the stock certs/signing_key.pem: upstream certs/Makefile guards a FORCE + if_changed,gen_key rule with `ifeq ($(CONFIG_MODULE_SIG_KEY),certs/signing_key.pem)`, so under the default name kbuild would REGENERATE our pre-placed key with a random one and ship a kernel trusting a key nobody holds. That name is also on MRPROPER_FILES. With a non-default name no generator rule exists, so a missing key fails loudly. That kbuild behaviour is the load-bearing assumption here and cannot be tested without a real build, so build-debs.sh now asserts the outcome instead of trusting it. Three new hard failures, all post-`make`, so the cost of tripping one is a rebuild and never a bad release: (a) cert identity: the DER kbuild extracted into certs/signing_key.x509 -- which it links into .builtin_trusted_keys, the trust root for every module signature this kernel accepts -- must equal our PEM's certificate. This is what turns a wrong kbuild assumption into an aborted build rather than a silently mis-keyed kernel. (b) no private key in any .deb. We now put a key inside a tree bindeb-pkg packages from; mkdebian's file list should not match certs/*.pem, but the cost of being wrong is publishing a signing key, so assert it. (c) config parity: the /boot/config-* inside the image .deb must be byte-identical to the captured final.config. That divergence IS this bug, undetected for twelve releases; now it cannot recur silently. Also removes the CI run number from the shipped bytes. It reached KBUILD_BUILD_VERSION and so UTS_VERSION inside vmlinux while being recorded nowhere and hard-coded to 1 in repro-check.sh -- three independent reasons a rebuild could never match. KBUILD_BUILD_VERSION now derives from the tag (PACKAGING_REVISION, default 1); traceability moved to manifest.ci_run_number. repro-check.sh: passes PACKAGING_REVISION parsed from the tag, which makes the five .P releases structurally verifiable for the first time (previously the rebuild emitted ...-hardened1 .debs and every file reported MISSING, reading like tampering rather than a missing input); cross-checks the manifest's kernel_version/hardened_revision against the tag; and generates a throwaway signing key, since without one the rebuild would now fail at `make` rather than silently generating a random one. Byte-identity on the cert-embedding artifacts (vmlinuz, System.map, -dbg) still needs the real private key -- handing over the published .der cannot help, because signing needs the private half -- so REPRO_SIGNING_KEY_PATH is honoured for operators who hold it. Localizing that divergence properly is follow-up work on the comparison itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>