Conversation
Booting a kernel that links a locally built FIPS canister panics early: FIPS(fips_integrity_init): canister 6.12 found (based on 6.12.109-4.ph5) FIPS(fips_integrity_init): processing 8 sections, 687696 bytes Kernel panic - not syncing: FIPS canister verification failed! gen_canister_relocs assigns every measured section an "ondx", the index fips_integrity_init() uses to address its si[] array when it reverses a relocation. si[] is built from canister_sections[], which contains only the sections that carry both a begin and an end marker. .bss carries a begin marker only - it is deliberately not measured - yet it still consumed an ondx. Every section laid out after .bss therefore received an index one too high, and its relocations were reversed against the wrong section's buffer, so the reconstructed image no longer matched the HMAC recorded at canister build time. The bug was latent for as long as .bss happened to be emitted after all measured sections, which is what gcc 12.2 did. gcc 12.5 (5fce7ef) emits .bss in the middle: gcc 12.2 .text .init.text .exit.text .rodata .smp_locks .data .init.data .bss .init.rodata gcc 12.5 .rodata .text .data .bss .init.text .exit.text .smp_locks .init.data .init.rodata With that layout .init.text, .exit.text, .smp_locks and .init.data - 568 relocations - are all misdirected, and ondx reaches 8 on an eight entry si[] array. Only sections that are actually measured may consume an index. The measured set, the generated linker script and therefore the canister HMAC itself are unchanged by this; only the numbering handed to the interpreter changes. Verified from the build artefacts: the canister that last passed verification reports GCC 12.2.0 in .comment and the first that failed reports GCC 12.5.0; recomputing the build-time HMAC from each shipped fips_canister.o reproduces the injected digest exactly, and every byte that differs between the canister object and the linked vmlinux is covered by a relocation. The defect is confined to the reverse relocation step. Change-Id: Ia09d27e079e0dd52f6ed3e47bf44e8a43ed6ae53 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JW73JTCUGRcaNTUEQcAMtf
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A kernel that links a locally built FIPS canister panics at 0.64 s:
The canister that ships with the distribution (
6.12.60-18.2) still verifies, so FIPS boot as such is fine. Only canisters built from current sources fail — which is whatcanister_build=1produces.Root cause
gen_canister_relocsgives each section anondx, the indexfips_integrity_init()uses to address itssi[]array when reversing a relocation:si[]is built fromcanister_sections[], which by construction holds only sections carrying both markers:.bsscarries a begin marker only — it is deliberately not measured, it exists in the list purely so relocations can resolve against it — yet it still consumed anondx. Every section laid out after.bsswas therefore numbered one too high, and its relocations were reversed against the wrong section's buffer. The reconstructed image then no longer matched the HMAC recorded at build time.This stayed invisible for as long as
.bsshappened to be emitted after every measured section. gcc 12.2 did exactly that; gcc 12.5 (5fce7ef, 92: core-toolchain: upgrade gcc to 12.5 and binutils to 2.46.1) does not:ondx0…).text .init.text .exit.text .rodata .smp_locks .data .init.data.bss.init.rodata.rodata .text .data.bss.init.text .exit.text .smp_locks .init.data .init.rodataUnder the new layout
.init.text,.exit.text,.smp_locksand.init.data— 568 relocations — are all misdirected, andondxreaches 8 on an eight-entrysi[]..init.rodatawas already numbered one too high under gcc 12.2 as well. It never mattered because it carries zero relocations, in both the passing and the failing canister.Fix
Only a section that is actually measured may consume an index.
.bsscannot hold relocations (it isNOBITS), so the sentinel is never dereferenced.The measured set, the generated linker script and therefore the canister HMAC are all unchanged — running the old and the new generator over the same object produces a byte-identical
canister_markers.ldsand an identicalcanister_sections_size. Only the numbering handed to the interpreter changes.Evidence
Established from build artefacts, without rebuilding:
GCC: (GNU) 12.2.0in.comment; the first that failed reportsGCC: (GNU) 12.5.0. Same kernel source version on both sides of the boundary, so the kernel version is not the variable.fips_canister.o—objcopy --only-sectionper section in linker-script order, concatenated, HMAC-SHA256 — reproduces the injected digest exactly, for the passing and the failing canister alike.vmlinuxis covered by a relocation in that section; there are zero uncovered differences across all eight measured sections. Nothing beyond relocation application touches the canister.That leaves the reverse-relocation step, and the numbering above accounts for it.
.text.init.text.exit.text.rodata.smp_locks.data.init.data.init.rodataTesting
check_spec.pypasses; the generator compiles clean with-Wall.End-to-end confirmation requires a canister build plus a relinked kernel booted with
fips=1, which is running separately; this PR is posted on the strength of the artefact analysis above rather than waiting on it.Note on certification
A canister built this way is functionally correct but not CMVP-validated — the published
6.12.60-18.2cannot certify a 6.12.109 kernel, and nothing built locally certifies anything. This fix is about a locally built canister being correct, which is what the boot-time integrity check demands regardless of certification.🤖 Generated with Claude Code