mirror of
https://github.com/hexagonal-sun/moss-kernel.git
synced 2026-05-24 08:55:20 -04:00
libkernel: pg_walk: use saturating arithmetic to avoid overflow at last page-table entry
The generic RecursiveWalker computed the coverage region for each
page-table entry as:
VirtMemoryRegion::new(entry_va, table_coverage).intersection(region)
For the last entry at the PML4 level (PML4[511]) in the kernel
high-half, entry_va + table_coverage wraps past usize::MAX, causing an
arithmetic overflow panic inside end_address().
Fix this in walk.rs by computing the intersection bounds with
saturating_add/min/max directly.
Add regression tests for all levels, ensuring last-element walking is
correct.
This commit is contained in:
@@ -390,6 +390,101 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_last_pml4_entry() {
|
||||
// The last PML4 entry (index 511) covers [0xffff_ff80_0000_0000, END).
|
||||
// Computing `entry_va + coverage` used to overflow usize for this entry.
|
||||
let mut harness = TestHarness::new(4);
|
||||
|
||||
let va = VA::from_value(0xffffffff80000000);
|
||||
let pa = 0x8_0000;
|
||||
|
||||
harness
|
||||
.map_4k_pages(pa, va.value(), 1, PtePermissions::ro(false))
|
||||
.unwrap();
|
||||
harness
|
||||
.map_4k_pages(
|
||||
pa + PAGE_SIZE,
|
||||
va.value() + PAGE_SIZE,
|
||||
1,
|
||||
PtePermissions::ro(false),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut was_called = false;
|
||||
walk_and_modify_region(
|
||||
harness.inner.root_table,
|
||||
VirtMemoryRegion::new(va.add_pages(1), PAGE_SIZE),
|
||||
&mut harness.inner.create_walk_ctx(),
|
||||
&mut |_va, desc: PTE| {
|
||||
was_called = true;
|
||||
assert_eq!(desc.mapped_address().unwrap().value(), pa + PAGE_SIZE);
|
||||
desc
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(was_called);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_last_pdpt_entry() {
|
||||
// The last PDPT entry (index 511 within PML4[511]) covers the last 1 GiB:
|
||||
// [0xffff_ffff_c000_0000, END). `entry_va + coverage` overflowed usize here.
|
||||
let mut harness = TestHarness::new(4);
|
||||
|
||||
let va = VA::from_value(0xffffffff_c0000000);
|
||||
let pa = 0x9_0000;
|
||||
|
||||
harness
|
||||
.map_4k_pages(pa, va.value(), 1, PtePermissions::ro(false))
|
||||
.unwrap();
|
||||
|
||||
let mut was_called = false;
|
||||
walk_and_modify_region(
|
||||
harness.inner.root_table,
|
||||
VirtMemoryRegion::new(va, PAGE_SIZE),
|
||||
&mut harness.inner.create_walk_ctx(),
|
||||
&mut |_va, desc: PTE| {
|
||||
was_called = true;
|
||||
assert_eq!(desc.mapped_address().unwrap().value(), pa);
|
||||
desc
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(was_called);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_last_pd_entry() {
|
||||
// The last PD entry (index 511 within PDPT[511]/PML4[511]) covers the last 2 MiB:
|
||||
// [0xffff_ffff_ffe0_0000, END). `entry_va + coverage` overflowed usize here.
|
||||
let mut harness = TestHarness::new(4);
|
||||
|
||||
let va = VA::from_value(0xffffffff_ffe00000);
|
||||
let pa = 0xa_0000;
|
||||
|
||||
harness
|
||||
.map_4k_pages(pa, va.value(), 1, PtePermissions::ro(false))
|
||||
.unwrap();
|
||||
|
||||
let mut was_called = false;
|
||||
walk_and_modify_region(
|
||||
harness.inner.root_table,
|
||||
VirtMemoryRegion::new(va, PAGE_SIZE),
|
||||
&mut harness.inner.create_walk_ctx(),
|
||||
&mut |_va, desc: PTE| {
|
||||
was_called = true;
|
||||
assert_eq!(desc.mapped_address().unwrap().value(), pa);
|
||||
desc
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(was_called);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_at_canonical_kernel_va() {
|
||||
let mut harness = TestHarness::new(4);
|
||||
|
||||
@@ -70,9 +70,17 @@ where
|
||||
};
|
||||
|
||||
if let Some(next_desc) = desc.next_table_address() {
|
||||
let sub_region = VirtMemoryRegion::new(entry_va, table_coverage)
|
||||
.intersection(region)
|
||||
.expect("Sub region should overlap with parent region");
|
||||
// `entry_va + table_coverage` can overflow for the last entry
|
||||
// at each level (e.g. PML4 entry 511 on x86_64). Compute the
|
||||
// intersection with saturating arithmetic instead of
|
||||
// constructing an unrepresentable VirtMemoryRegion.
|
||||
let entry_end = entry_va.value().saturating_add(table_coverage);
|
||||
let sub_start = region.start_address().value().max(entry_va.value());
|
||||
let sub_end = region.end_address().value().min(entry_end);
|
||||
let sub_region = VirtMemoryRegion::from_start_end_address(
|
||||
VA::from_value(sub_start),
|
||||
VA::from_value(sub_end),
|
||||
);
|
||||
|
||||
<T::Descriptor as TableMapper>::NextLevel::walk(
|
||||
next_desc, sub_region, ctx, modifier,
|
||||
|
||||
Reference in New Issue
Block a user