Skip to content

Commit a8484dd

Browse files
committed
fix(pager): enforce exact realm isolation for cached pages
1 parent 0ed10a5 commit a8484dd

2 files changed

Lines changed: 64 additions & 8 deletions

File tree

src/pager/cache.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ pub struct Page {
3232
/// Page kind recorded at write time; used by the Pager flush path to
3333
/// reconstruct the correct AAD for each dirty page.
3434
pub kind_byte: u8,
35-
/// Realm that owns this cached plaintext. Used by the Pager read path to
36-
/// reject cross-realm cache hits without a VFS round-trip.
37-
pub realm_id_bytes: [u8; 16],
35+
/// Realm that owns this cached plaintext. `None` marks metadata-less test
36+
/// pages, which the Pager read path must reject rather than treat as a
37+
/// cache hit.
38+
pub realm_id_bytes: Option<[u8; 16]>,
3839
}
3940

4041
impl Page {
@@ -43,7 +44,7 @@ impl Page {
4344
Self {
4445
bytes,
4546
kind_byte: 0,
46-
realm_id_bytes: [0u8; 16],
47+
realm_id_bytes: None,
4748
}
4849
}
4950

@@ -52,7 +53,7 @@ impl Page {
5253
Self {
5354
bytes,
5455
kind_byte,
55-
realm_id_bytes: [0u8; 16],
56+
realm_id_bytes: None,
5657
}
5758
}
5859

@@ -61,7 +62,7 @@ impl Page {
6162
Self {
6263
bytes,
6364
kind_byte,
64-
realm_id_bytes,
65+
realm_id_bytes: Some(realm_id_bytes),
6566
}
6667
}
6768
}
@@ -321,7 +322,11 @@ mod tests {
321322
fn insert_and_get() {
322323
let mut c = PageCache::with_capacity(4);
323324
c.insert((FileKey::Main, 1), page(0));
324-
assert!(c.get((FileKey::Main, 1)).is_some());
325+
assert_eq!(
326+
c.get((FileKey::Main, 1))
327+
.map(|cached| cached.realm_id_bytes),
328+
Some(None)
329+
);
325330
}
326331

327332
#[test]

src/pager/core.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ impl<V: Vfs> Pager<V> {
616616
{
617617
let mut cache = self.inner.cache_for_key(file).lock();
618618
if let Some(page) = cache.get((file, page_id)) {
619-
if page.realm_id_bytes != [0u8; 16] && page.realm_id_bytes != realm_id.0 {
619+
if page.realm_id_bytes != Some(realm_id.0) {
620620
return Err(PagedbError::ChecksumFailure);
621621
}
622622
self.inner.record_hit(file);
@@ -1030,6 +1030,57 @@ mod tests {
10301030
assert!(matches!(err, PagedbError::ChecksumFailure));
10311031
}
10321032

1033+
#[tokio::test(flavor = "current_thread")]
1034+
async fn cached_pages_require_an_exact_realm_match() {
1035+
let pager = mk_pager().await;
1036+
let zero_realm = RealmId([0; 16]);
1037+
let nonzero_realm = RealmId([1; 16]);
1038+
let body = vec![0u8; PAGE - ENVELOPE_OVERHEAD];
1039+
1040+
pager
1041+
.write_main_page(1, zero_realm, PageKind::BTreeLeaf, &body)
1042+
.await
1043+
.unwrap();
1044+
pager
1045+
.write_main_page(2, nonzero_realm, PageKind::BTreeLeaf, &body)
1046+
.await
1047+
.unwrap();
1048+
1049+
{
1050+
let mut cache = pager.inner.buffer_pool.lock();
1051+
assert_eq!(
1052+
cache
1053+
.get((FileKey::Main, 1))
1054+
.map(|page| page.realm_id_bytes),
1055+
Some(Some(zero_realm.0))
1056+
);
1057+
assert_eq!(
1058+
cache
1059+
.get((FileKey::Main, 2))
1060+
.map(|page| page.realm_id_bytes),
1061+
Some(Some(nonzero_realm.0))
1062+
);
1063+
}
1064+
1065+
for (page_id, requested_realm) in [(1, nonzero_realm), (2, zero_realm)] {
1066+
let misses_before = pager.inner.buffer_pool_misses.load(AtomOrd::Relaxed);
1067+
let cache_len_before = pager.inner.buffer_pool.lock().len();
1068+
let err = pager
1069+
.read_main_page(page_id, requested_realm, PageKind::BTreeLeaf)
1070+
.await
1071+
.err()
1072+
.unwrap();
1073+
1074+
assert!(matches!(err, PagedbError::ChecksumFailure));
1075+
assert_eq!(
1076+
pager.inner.buffer_pool_misses.load(AtomOrd::Relaxed),
1077+
misses_before,
1078+
"cached realm mismatch must not fall back to disk"
1079+
);
1080+
assert_eq!(pager.inner.buffer_pool.lock().len(), cache_len_before);
1081+
}
1082+
}
1083+
10331084
#[tokio::test(flavor = "current_thread")]
10341085
async fn rejects_illegal_page_kind_on_main() {
10351086
let pager = mk_pager().await;

0 commit comments

Comments
 (0)