stage1: exec stage2 from a sealed memfd, config on stdin#13
Merged
Conversation
Replace the write-to-/tmp-then-exec path with an in-memory, pathless launch: - Load the payload into an anonymous memfd, seal it (F_SEAL_WRITE plus SHRINK/GROW/SEAL), and execveat() it directly. The bytes measured into PCR 14 are now immutable and are exactly what runs; there is no named, writable inode to swap between measurement and exec, which closes the measure/exec TOCTOU. MFD_EXEC is requested where supported, with an EINVAL fallback for pre-6.3 kernels. - Deliver the config (the raw user-data JSON) on stdin from a second memfd instead of /tmp/stage2-config.json. stdin needs no extra-fd convention (which trips up runtimes like Bun/Node single-file executables) and, being an in-memory file, has no pipe-size limit. No stage2 artifact is left on any named path. example-stage2 now reads its config from stdin; README documents the contract. Config stays unmeasured by design (code-only PCR 14). Verified: full chain boots and powers off in both sha256 and ed25519 modes, with the config JSON received on stdin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Follows the boot-path security review. Removes the last on-disk artifacts from the stage2 launch and tightens the "measured == executed" invariant.
What changes
Sealed, pathless exec. The payload is loaded into an anonymous
memfd, sealed withF_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL, and launched withexecveat(fd, "", AT_EMPTY_PATH). Previously we wrote it to/tmp/stage2.exeand exec'd that path, so the measured bytes and the executed bytes were two different objects with a window in between. Now the thing measured into PCR 14, the thing sealed immutable, and the thing executed are one kernel object.MFD_EXECis requested where supported (Linux 6.3+ hardened kernels default new memfds to non-executable), with anEINVALfallback for older kernels.Config on stdin. The raw user-data JSON is delivered on stdin from a second
memfd(dup2'd to fd 0), replacing/tmp/stage2-config.json. stdin is a universal channel that needs no extra-fd convention (which trips up runtimes like Bun/Node single-file executables), and as an in-memory file it has no pipe-size limit / no deadlock. No stage2 artifact is left on any named path.example-stage2now reads its config from stdin; the README documents the new contract. Config remains unmeasured by design (PCR 14 is code-only), consistent with the existing measurement model.Verification
Full chain (
stage0 -> UKI -> stage1 -> example-stage2) boots and powers off cleanly in both sha256 and ed25519 modes; the payload runs from the sealed memfd (argv[0]=stage2) and prints the config JSON received on stdin. Workspace tests pass; no new warnings.🤖 Generated with Claude Code