NSM entropy audit and correction
Audit date: 2026-09-09. The correction in a10323d has now passed a newly
measured, nondebug Nitro diagnostic (only iterations changed to eight).
Production a10323d is running and warming after two builds reproduced its PCRs. This audit found a
requirement gap, not a demonstrated parent attack or an uninitialized-urandom
vulnerability.
Evidence for the previous image
The application generated its TLS identity before opening the NSM attester.
Its signing key used rand::random, and puzzle generation used OsRng for the
epoch key and all seven private seeds. These sources eventually use Linux's
CRNG, rather than explicitly requesting NSM randomness.
The exact deployed EIF (5d3fb3dd5ef083142dd0f8fb09ae5cca0f88b4af546ae16de7952e9fdd16394a)
was parsed read-only according to the AWS EIF format.
Its kernel and first ramdisk contain:
| Component | SHA256 |
|---|---|
| Kernel Image | 03808bbf031a6f27178b35657e6b4b8cdf11820c349396407995ceeec82ec9b9 |
| init | a056a462b30ff749e536e4ec5846388a4048abf10c65d4623333d52c8f724c88 |
| nsm.ko | 86154e77eeeea24b7835787ea0b0bc20758293703ffca15f346bf78b5788ef7b |
The kernel Image also byte-matches the CLI v1.5.0 GitHub release blob.
These match the installed signed Amazon Linux RPM
aws-nitro-enclaves-cli-devel-1.5.0-0.amzn2023.aarch64. The kernel identifies
itself as 4.14.256-209.484.amzn2.aarch64, built 2022-01-11. Its associated
configuration has CONFIG_HW_RANDOM=y, CONFIG_ARCH_RANDOM=y,
CONFIG_NUMA=y, and CONFIG_HZ=250.
The RPM's init and NSM ELF hashes differ from the
CLI v1.5.0 tag,
but their executable .text sections and .data are byte-identical; build IDs
also match. Thus the hash difference is not evidence of different executable
instructions. NSM ELF symbols/relocations show nsm_rng_read and
hwrng_register; its struct hwrng data has quality 1000.
The AWS NSM driver implements GetRandom and registers an entropy-producing hwrng. The bootstrap init loads this driver before launching the application. However registration does not establish a fail-closed NSM entropy barrier. In the matching Amazon kernel hwrng core, registration attempts an early 16-byte read, ignores an unsuccessful read, and starts an asynchronous entropy-feeding thread.
The pinned getrandom 0.2.17 uses the Linux syscall with flags zero; AWS-LC
0.45.0's Linux entropy implementation also waits for initialized randomness.
The matching kernel random.c
blocks such calls until CRNG readiness. This supports normal secure OS entropy
behavior and NSM feeding, but does not identify which source caused readiness
or prove a successful NSM request preceded each application's secret. Neither
a live attestation nor the existing startup logs record that causal fact.
The literal requirement to source epoch keys directly from NSM was unmet.
Minimal measured correction
Attester::seed_os_rng() now runs before installing the TLS provider or
creating any TLS/signing identity. Production obtains fresh bytes directly
from NSM, credits them through RNDADDENTROPY, verifies sufficient credited
entropy, and calls RNDRESEEDCRNG. Both ioctls require enclave-root privileges;
errors abort startup. The exact pinned kernel implements RNDRESEEDCRNG at
random.c:2048; this is not an assumption based on a newer kernel.
The kernel also has per-NUMA CRNGs and propagates forced reseeding with a jiffies timestamp. Two fresh NSM injection/reseed rounds, separated by 20 ms (more than one tick at the pinned HZ=250), ensure that a secondary state last used in the first reseed's tick becomes older than the second reseed marker. No TLS provider runs between these rounds. Readiness is never inferred merely from a nonzero entropy count; that count guards the kernel's otherwise possible no-op reseed with insufficient input, after trusted bytes were added.
The signing seed is taken directly from NSM. The additive
relay_timelock::generate_with_rng API obtains the dataset key, seven private
seeds, epoch key, and wrap nonces from a fallible caller-supplied source before
starting expensive work. The enclave supplies NSM; the standalone CLI's
existing generate API supplies OS randomness. Production has no OS fallback
when NSM fails.
The direct NSM GetRandom ioctl path owns a zeroizing raw response buffer and borrows the decoded byte string before copying to zeroizing output storage. This avoids the upstream NSM helper's unwiped raw response stack buffer. Response sizes, caller size, and number of fetches are bounded. Errors clear partially filled caller buffers. These measures concern application-owned secret buffers; they do not claim erasure of all copies inside the NSM kernel driver or hardware.
Focused tests cover failed/empty/oversized/short suppliers, finite fetch budgets, cleared failure outputs, kernel-reseed failures, and a real RandomX generation/serial solve with all entropy supplied by a test source. A fault injected at each of the 16 entropy draws prevents generation. All five focused attester tests passed natively on Linux aarch64; the genuine supplied-entropy RandomX generate/solve test also passed on Linux aarch64 and Mac ARM. Linux wire-decoder tests additionally cover genuine NSM response encoding, NSM error variants, truncation and invalid lengths. Real Nitro validation of the corrected privileged bootstrap subsequently passed in the measured diagnostic. Startup executed both injection/reseed rounds before TLS; fresh attestation and hardware checks passed. A real request was recovered as canonical CBOR, and independent Hetzner recovery matched the exact response bytes. These short-work results do not establish production-duration operation.