Repository navigation
Conversation
One static musl binary that does what AWS's init.c and eiffel's Go pid1 did between them: block every signal, bring up devtmpfs and the console, answer the vsock heartbeat nitro-cli waits for, read and unlink /cmd and /env, switch into /rootfs, mount the filesystem set, then spawn chronyd plus the listed programs and reap them from a single waitpid(-1) loop. The first direct child to exit takes the enclave down: SIGTERM to all, two seconds to flush, reboot. Two deliberate differences from init.c: a single cgroup2 mount replaces the per-controller cgroup v1 loop, and children inherit the console fds instead of being piped through a copy thread each. There is no nsm.ko handling at all; the driver is built into the kernel since PR #4. Every failure writes one line to the console before acting on it. The console is the only debugging channel an enclave has. unsafe lives in src/sys.rs alone, which holds the pre_exec hook; the crate denies it everywhere else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The init stage is now a cargo build of eiffel-init for musl instead of a git clone and gcc of AWS's init.c, with --remap-path-prefix so nothing about the build machine reaches PCR1, and the same PT_INTERP assertion the chrony stage makes. /blobs/init keeps its name and path, so the bootstrap layout only loses its rootfs/sbin/pid1 entry. The builder stage grows a COPY of eiffel-init/ because cargo needs every workspace member on disk to resolve the manifest, even to build one. Verified reproducible: two --no-cache builds of the init stage produce the same sha256. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The example's cmd loses its /sbin/pid1 line, which the new init rejects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rust job gains the static musl build with the no-PT_INTERP assertion the image makes, so a change that breaks the enclave target fails before anything spends eight minutes on a kernel. The reproducibility job already hashes /init, which is now eiffel-init. The boot test gets a second EIF of its own, whose only program is a static /app/hello that prints the time, opens /dev/nsm and exits 0, so the run can assert the whole sequence: the heartbeat answered, the program started, its output on the console, and the shutdown. The example EIF could only ever prove the heartbeat. The harness switches to vhost-device-vsock's vsock backend (--forward-cid=1) and a host listener on the vsock loopback. The previous version sent a "LISTEN 9000" line over the UDS backend, which is not part of its protocol: that backend implements Firecracker's hybrid vsock, where a host listener is a socket named <uds-path>_<port> and CONNECT only goes host-to-guest. QEMU's own nitro-enclave documentation describes the arrangement used here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
patrislav
force-pushed
the
feat/rust-eiffel-init
branch
from
November 2, 2026 16:15
b3b1acc to
07cc646
Compare
Brings up the workflow hardening. The only conflict was the boot-test step in enclave.yml, where this branch had changed the EIF it boots and the mount it needs: kept both, with the Fedora digest and the vhost-device-vsock version pin from below. Co-Authored-By: Claude Opus 5 <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.
Inside the enclave two processes do one job. AWS's
init.cmounts the filesystems, answers the parent's heartbeat and switches root, then hands off to our Gopid1, which spawns and supervises the customer's programs. That split means two languages, two build stages and two sets of behaviour to reason about for something that is a few hundred lines of work in total. This replaces both with one program.Second of three stacked pull requests; merge the builder first. chronyd is still spawned as the first supervised child here, and the next pull request removes it.
What lands
eiffel-initcrate, built static for musl, that runs the whole boot sequence: block every signal, mount devtmpfs and take the console, answer the heartbeat the parent waits on, read and unlink the config files, switch root, mount the filesystem set, spawn the programs and supervise them.nitro-cliblocks on it and will report the enclave as failed without it.Migration for customers
The
cmdfile no longer accepts a/sbin/pid1line, and that path no longer exists in the rootfs. List only your own programs. A stalecmdis rejected at first boot with a console message naming the problem rather than failing obscurely. The README carries the note.Behavioural differences from today
Verification
Not verified
Nothing was booted. There is no Nitro hardware, and the machine this was built on cannot run the QEMU machine type that emulates one, so the boot test, the device assertions and the heartbeat plumbing are unrun. No PCR values were computed or compared.
The QEMU harness was reworked here. The arrangement tried first does not match what
vhost-device-vsockactually offers, since its socket backend cannot answer a connection the guest initiates. The harness now follows QEMU's own documented setup. That change is reasoned from the upstream documentation, not from a run.Reading PCRs back from inside the guest was left out rather than guessed at. It needs an attestation client and a request encoding that would have been recalled rather than read.
Impact
PCR0, PCR1 and PCR2 all move again. Re-pin from the PCR output.
🤖 Generated with Claude Code