Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ pub fn derive_kek(
KdfId::Argon2id => {
let p = Params::new(params.m_cost, params.t_cost, params.p_cost, Some(32))
.map_err(|_| CryptoError::Kdf)?;
// Version::V0x13 and the 32-byte output length (`Some(32)` above) are
// pinned to the on-disk format, exactly like KEK_INFO on the HKDF path:
// changing either re-derives a different KEK, so every existing Argon2id
// slot would stop unwrapping (silent data loss). Bump the format version
// deliberately if this ever changes; the argon2id KAT test pins it.
let a2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, p);
a2.hash_password_into(ikm, salt, okm.as_mut())
.map_err(|_| CryptoError::Kdf)?;
Expand Down
6 changes: 6 additions & 0 deletions src/page_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,12 @@ impl PageCache {
/// A checksum mismatch is a fatal corruption error per ARCHITECTURE.md —
/// `ChecksumMismatch` signals the database is broken, not merely that
/// the operation failed.
///
/// The encrypted cold-load path has the same severity: a `DecryptionFailed`
/// from `PageCipher::open` (AEAD tag rejection) is `is_fatal()` — it poisons
/// the manager exactly like `ChecksumMismatch`, not an operational error the
/// caller can retry. The XXH3 checksum and the AEAD tag are peer integrity
/// checks: either failing means the persisted bytes are untrustworthy.
fn load_page(&mut self, page_id: u64) -> Result<()> {
self.maybe_evict()?;

Expand Down
4 changes: 4 additions & 0 deletions src/page_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ impl PageIo {
pub fn set_stride(&mut self, stride: usize) {
self.stride = stride;
let len = match &mut self.backing {
// Infallible by signature (the encrypted-open bootstrap has no Result
// to thread into): a seek failure means a broken fd, already fatal under
// the single-writer flock. Falling back to 0 makes reads fail closed
// (InvalidPageId) instead of computing offsets against a stale count.
Backing::File { file } => file.seek(SeekFrom::End(0)).unwrap_or(0),
Backing::Memory { bytes } => bytes.len() as u64,
};
Expand Down
5 changes: 5 additions & 0 deletions src/spillway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
// process and unconditionally discarded.
// spill page_id allocates a slot (or overwrites its existing
// one), bytes + per-slot checksum are written.
// Writes are deliberately NOT fsynced: spillway content never
// crosses a transaction boundary (rebuilt on demand, discarded at
// `truncate` on commit/rollback and as crash garbage at `open`), so
// a durability barrier would be wasted I/O. The per-slot XXH3 still
// guards a torn write; durability is intentionally omitted.
// rehydrate slot is read, checksum verified, bytes returned.
// truncate file shrunk to zero, resident-set index cleared. Called
// at commit, rollback, and defrag.
Expand Down
6 changes: 6 additions & 0 deletions src/superblock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ impl Superblock {
/// this superblock's plaintext identity. The four bootstrap fields that stay
/// cleartext in both encrypted and plaintext DBs are included; this prevents
/// transplanting a sealed body from a different DB or a different txn_counter.
///
/// These four MUST stay cleartext even in an encrypted DB precisely because
/// they are the AAD: slot selection (`max_by_key` on `txn_counter`) and this
/// AAD derivation both run BEFORE any DEK is available, so the fields cannot
/// live in the DEK-sealed body — the engine must read them to pick the live
/// slot and rebuild the AAD before it can open that body.
pub fn sb_identity_aad(&self) -> [u8; 24] {
let mut a = [0u8; 24];
a[0..4].copy_from_slice(&self.magic.to_le_bytes());
Expand Down
Loading