Repository navigation
sealed: hold the pre-key between guard pages (guarded-block); 0.9.0-rc.15 - #268
Merged
Merged
Conversation
…9.0-rc.15 The pre-key moves off the heap into a mapping of its own: a no-access guard page, its pages (read-only once filled, locked), and a no-access guard page, marked MADV_DONTDUMP on Linux. A 64 KiB over-read from a live C malloc buffer captured the whole heap-held pre-key in 60/60 runs on Windows and on Linux; guarded, in none, and with readable memory placed directly against it the read faults before reading a byte. The requested split into four heap pages was measured first and still fully captured in 27/60 runs per platform. The unsafe page protection needs lives in a new crate in this repository, guarded-block 0.1.0, so secure-gate keeps #![forbid(unsafe_code)]; `sealed` depends on it in place of region. - Prekey::new_idle_noaccess(): also no-access between key derivations. On Windows that keeps the pre-key out of ReadProcessMemory and full-memory dumps taken while idle, but PAGE_NOACCESS drops a VirtualLock (measured), so it does not lock there and lock_status() says so. Prekey::new() stays the default. - LockError: page_too_large removed (no per-target alignment table is needed any more); idle_noaccess() and forked() added. In a fork() child lock_status() now reports the lock as not inherited. - Re-seals derive the cipher before copying plaintext into the ciphertext buffer, so a failed derivation (idle mode) cannot leave plaintext there. - The derivation is unchanged, pinned to a vector computed outside Rust. - CI: guarded-block and the sealed tests on Ubuntu, Windows and macOS. Probe and raw output: docs/design/receipts/guarded-prekey/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jd6eJgv7urGn4rvMcVASo2
- guarded-block: size the mapping with checked arithmetic and return MapError instead of wrapping. Not reachable today (rustc rejects [u8; N] above isize::MAX, and no N up to that can overflow), but fill's soundness no longer rests on that. - guarded-block: document that a refused return to no-access leaves an idle block readable until a later access closes it; edition 2024. - Numbers the receipts support: the split was fully captured 26/60 on Windows and 27/60 on Linux (not 27 on each); the Windows idle-mode overhead is mostly re-faulting the pages into the working set, the protection changes alone being 0.65-0.97 us; MADV_DONTDUMP was checked as a flag, no core dump taken; the guard pages are evidenced by the neighbor test, not the C-buffer runs, which an unguarded mapping also passes. - Sealed docs: Prekey and LockError examples (doctests), the sealed types in the crate root's "which type" table, and guarded-block in SECURITY.md's table of crates that lock correctly (memsec's row now says its API is unsafe fns). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jd6eJgv7urGn4rvMcVASo2
A cfg_attr row: a real intra-doc link where `sealed` is enabled (docs.rs), plain text where it is not, so the `--features=full` doc gate has no broken link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jd6eJgv7urGn4rvMcVASo2
…tates them The refused return to no-access, and MADV_DONTDUMP as the kernel's documented behavior rather than a dump we took, now read the same on Prekey, new_idle_noaccess, the sealed module, GuardedBlock::new_with_idle_noaccess and guarded-block's README; the Windows idle cost is attributed to working-set re-faults there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jd6eJgv7urGn4rvMcVASo2
Every push to main failed Miri from the sealed merge (rc.13) on: the first Prekey test reached region's integer-to-pointer cast, which -Zmiri-strict-provenance rejects, and Miri cannot emulate mlock / mprotect / VirtualLock in any case. full,std is exactly what --all-features was before sealed existed (and is the same set on release/0.8); the sealed and guarded-block tests run natively on three OSes in ci.yml instead. Checked with CI's exact command and flags on this branch under WSL: 446 tests in 24 binaries passed, no errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jd6eJgv7urGn4rvMcVASo2
Slurp9187
force-pushed
the
guarded-prekey
branch
from
October 21, 2026 02:08
c3747fb to
3ae7cc7
Compare
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 up the sealed-memory work tracked in #56. EFV asked for the pre-key to be split into four separately allocated, aligned and locked 4 KiB pages, against a Heartbleed-style over-read by C code in the same process (it links SQLCipher and vendored OpenSSL). That was measured before anything was built, beside three alternatives. This PR builds the one the numbers support.
What the probe measured
Real 64 KiB
memcpyover-reads from live Cmallocbuffers, 20 fresh processes per case, Windows 11 and Linux (WSL2, glibc 2.43). Probe and raw output:docs/design/receipts/guarded-prekey/.The locked quota is unchanged (11 pre-keys under Windows' default), as is the per-access cost (5.95 µs). The derivation is also unchanged, pinned to a vector computed in Python.
What changes
guarded-block0.1.0 (guarded-block/, its own workspace likeresidue-probe). It's a safe API overregion's page protection: a mapping of its own, no-access guard pages, read-only once filled, locked,MADV_DONTDUMPon Linux, wiped on drop, and fork-awarelock_status. All theunsafeis there, so secure-gate keeps#![forbid(unsafe_code)].sealeddepends on it instead ofregion.Prekeynow sits on top of it.Prekey::new_idle_noaccess()(opt-in) also keeps the pre-key no-access between key derivations. On Windows that keeps it out ofReadProcessMemoryand full-memory crash dumps taken while idle (measured). ButPAGE_NOACCESSdrops aVirtualLock(measured: error 158 on unlock, and pages leave the working set), so this mode doesn't lock there, andlock_status()says so.Prekey::new()stays the default.LockError:page_too_largeremoved (no per-target alignment table is needed any more). Addedidle_noaccess(), andforked(), since afork()child no longer repeats the parent's lock result.guarded-blockand the sealed tests on Ubuntu, Windows and macOS, at the 1.85 MSRV and on stable. Every other job is Ubuntu-only.Verification
Locally, on the final tree, 59 of 60 gates pass:
-D warnings;docsrs);guarded-blockon stable, 1.85 and release, and its rustdoc;guarded-blockpublish dry-run.The one expected failure is secure-gate's publish dry-run:
guarded-blockis not on crates.io yet. It passes onceguarded-block0.1.0 is published, which has to happen first.guarded-block's tests also pass on WSL2. Every new or changed test was checked to fail with the code it guards removed.Not published, not tagged. The merge waits for an external review.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jd6eJgv7urGn4rvMcVASo2