Move linear memory faulted guard page tracking into Memory.

This commit moves the tracking for faulted guard pages in a linear memory into
`Memory`.
This commit is contained in:
Peter Huene
2021-03-08 09:23:17 -08:00
parent 7a93132ffa
commit 5fa0f8d469
5 changed files with 76 additions and 57 deletions

View File

@@ -69,11 +69,6 @@ pub(crate) struct Instance {
/// Hosts can store arbitrary per-instance information here. /// Hosts can store arbitrary per-instance information here.
host_state: Box<dyn Any>, host_state: Box<dyn Any>,
/// Stores linear memory guard page faults for the pooling allocator with uffd enabled.
/// These pages need to be reset after the signal handler generates the out-of-bounds trap.
#[cfg(all(feature = "uffd", target_os = "linux"))]
guard_page_faults: RefCell<Vec<(*mut u8, usize, fn(*mut u8, usize) -> anyhow::Result<()>)>>,
/// Additional context used by compiled wasm code. This field is last, and /// Additional context used by compiled wasm code. This field is last, and
/// represents a dynamically-sized array that extends beyond the nominal /// represents a dynamically-sized array that extends beyond the nominal
/// end of the struct (similar to a flexible array member). /// end of the struct (similar to a flexible array member).
@@ -383,14 +378,6 @@ impl Instance {
/// Returns `None` if memory can't be grown by the specified amount /// Returns `None` if memory can't be grown by the specified amount
/// of pages. /// of pages.
pub(crate) fn memory_grow(&self, memory_index: DefinedMemoryIndex, delta: u32) -> Option<u32> { pub(crate) fn memory_grow(&self, memory_index: DefinedMemoryIndex, delta: u32) -> Option<u32> {
// Reset all guard pages before growing any memory when using the uffd feature.
// The uffd feature induces a trap when a fault on a linear memory page is determined to be out-of-bounds.
// It does this by temporarily setting the protection level to `NONE` to cause the kernel to signal SIGBUS.
// Because instances might still be used after a trap, this resets the page back to the expected protection
// level (READ_WRITE) for the uffd implementation.
#[cfg(all(feature = "uffd", target_os = "linux"))]
self.reset_guard_pages().ok()?;
let result = self let result = self
.memories .memories
.get(memory_index) .get(memory_index)
@@ -822,36 +809,6 @@ impl Instance {
(foreign_table_index, foreign_instance) (foreign_table_index, foreign_instance)
} }
} }
/// Records a faulted guard page.
///
/// This is used to track faulted guard pages that need to be reset.
#[cfg(all(feature = "uffd", target_os = "linux"))]
pub(crate) fn record_guard_page_fault(
&self,
page_addr: *mut u8,
size: usize,
reset: fn(*mut u8, usize) -> anyhow::Result<()>,
) {
self.guard_page_faults
.borrow_mut()
.push((page_addr, size, reset));
}
/// Resets previously faulted guard pages.
///
/// This is used to reset the protection of any guard pages that were previously faulted.
///
/// Resetting the guard pages is required before growing memory.
#[cfg(all(feature = "uffd", target_os = "linux"))]
pub(crate) fn reset_guard_pages(&self) -> anyhow::Result<()> {
let mut faults = self.guard_page_faults.borrow_mut();
for (addr, len, reset) in faults.drain(..) {
reset(addr, len)?;
}
Ok(())
}
} }
/// A handle holding an `Instance` of a WebAssembly module. /// A handle holding an `Instance` of a WebAssembly module.

View File

@@ -602,8 +602,6 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
)), )),
dropped_data: RefCell::new(EntitySet::with_capacity(req.module.passive_data.len())), dropped_data: RefCell::new(EntitySet::with_capacity(req.module.passive_data.len())),
host_state, host_state,
#[cfg(all(feature = "uffd", target_os = "linux"))]
guard_page_faults: RefCell::new(Vec::new()),
vmctx: VMContext {}, vmctx: VMContext {},
}; };
let layout = instance.alloc_layout(); let layout = instance.alloc_layout();

View File

@@ -367,8 +367,6 @@ impl InstancePool {
dropped_elements: RefCell::new(EntitySet::new()), dropped_elements: RefCell::new(EntitySet::new()),
dropped_data: RefCell::new(EntitySet::new()), dropped_data: RefCell::new(EntitySet::new()),
host_state: Box::new(()), host_state: Box::new(()),
#[cfg(all(feature = "uffd", target_os = "linux"))]
guard_page_faults: RefCell::new(Vec::new()),
vmctx: VMContext {}, vmctx: VMContext {},
}, },
); );
@@ -431,9 +429,15 @@ impl InstancePool {
let memory = mem::take(memory); let memory = mem::take(memory);
debug_assert!(memory.is_static()); debug_assert!(memory.is_static());
// Reset any faulted guard pages as the physical memory may be reused for another instance in the future
#[cfg(all(feature = "uffd", target_os = "linux"))]
memory
.reset_guard_pages()
.expect("failed to reset guard pages");
let size = (memory.size() * WASM_PAGE_SIZE) as usize; let size = (memory.size() * WASM_PAGE_SIZE) as usize;
drop(memory); drop(memory);
decommit_memory_pages(base, size).unwrap(); decommit_memory_pages(base, size).expect("failed to decommit linear memory pages");
} }
instance.memories.clear(); instance.memories.clear();
@@ -450,7 +454,7 @@ impl InstancePool {
); );
drop(table); drop(table);
decommit_table_pages(base, size).unwrap(); decommit_table_pages(base, size).expect("failed to decommit table pages");
} }
instance.tables.clear(); instance.tables.clear();
@@ -469,12 +473,6 @@ impl InstancePool {
) -> Result<(), InstantiationError> { ) -> Result<(), InstantiationError> {
let module = instance.module.as_ref(); let module = instance.module.as_ref();
// Reset all guard pages before reusing the instance
#[cfg(all(feature = "uffd", target_os = "linux"))]
instance
.reset_guard_pages()
.map_err(|e| InstantiationError::Resource(e.to_string()))?;
debug_assert!(instance.memories.is_empty()); debug_assert!(instance.memories.is_empty());
for plan in for plan in

View File

@@ -313,8 +313,12 @@ unsafe fn handle_page_fault(
None => { None => {
log::trace!("out of bounds memory access at {:p}", addr); log::trace!("out of bounds memory access at {:p}", addr);
// Record the guard page fault with the instance so it can be reset later. // Record the guard page fault so the page protection level can be reset later
instance.record_guard_page_fault(page_addr, len, reset_guard_page); instance.memories[memory_index].record_guard_page_fault(
page_addr,
len,
reset_guard_page,
);
wake_guard_page_access(&uffd, page_addr, len)?; wake_guard_page_access(&uffd, page_addr, len)?;
} }
} }

View File

@@ -180,6 +180,10 @@ enum MemoryStorage {
size: Cell<u32>, size: Cell<u32>,
maximum: u32, maximum: u32,
make_accessible: fn(*mut u8, usize) -> Result<()>, make_accessible: fn(*mut u8, usize) -> Result<()>,
/// Stores the pages in the linear memory that have faulted as guard pages when using the `uffd` feature.
/// These pages need their protection level reset before the memory can grow.
#[cfg(all(feature = "uffd", target_os = "linux"))]
guard_page_faults: RefCell<Vec<(*mut u8, usize, fn(*mut u8, usize) -> Result<()>)>>,
}, },
Dynamic(Box<dyn RuntimeLinearMemory>), Dynamic(Box<dyn RuntimeLinearMemory>),
} }
@@ -209,6 +213,8 @@ impl Memory {
size: Cell::new(plan.memory.minimum), size: Cell::new(plan.memory.minimum),
maximum: min(plan.memory.maximum.unwrap_or(maximum), maximum), maximum: min(plan.memory.maximum.unwrap_or(maximum), maximum),
make_accessible, make_accessible,
#[cfg(all(feature = "uffd", target_os = "linux"))]
guard_page_faults: RefCell::new(Vec::new()),
})) }))
} }
@@ -242,6 +248,10 @@ impl Memory {
make_accessible, make_accessible,
.. ..
} => { } => {
// Reset any faulted guard pages before growing the memory.
#[cfg(all(feature = "uffd", target_os = "linux"))]
self.reset_guard_pages().ok()?;
let old_size = size.get(); let old_size = size.get();
if delta == 0 { if delta == 0 {
return Some(old_size); return Some(old_size);
@@ -276,6 +286,56 @@ impl Memory {
MemoryStorage::Dynamic(mem) => mem.vmmemory(), MemoryStorage::Dynamic(mem) => mem.vmmemory(),
} }
} }
/// Records a faulted guard page in a static memory.
///
/// This is used to track faulted guard pages that need to be reset for the uffd feature.
///
/// This function will panic if called on a dynamic memory.
#[cfg(all(feature = "uffd", target_os = "linux"))]
pub(crate) fn record_guard_page_fault(
&self,
page_addr: *mut u8,
size: usize,
reset: fn(*mut u8, usize) -> Result<()>,
) {
match &self.0 {
MemoryStorage::Static {
guard_page_faults, ..
} => {
guard_page_faults
.borrow_mut()
.push((page_addr, size, reset));
}
MemoryStorage::Dynamic(_) => {
unreachable!("dynamic memories should not have guard page faults")
}
}
}
/// Resets the previously faulted guard pages of a static memory.
///
/// This is used to reset the protection of any guard pages that were previously faulted.
///
/// This function will panic if called on a dynamic memory.
#[cfg(all(feature = "uffd", target_os = "linux"))]
pub(crate) fn reset_guard_pages(&self) -> Result<()> {
match &self.0 {
MemoryStorage::Static {
guard_page_faults, ..
} => {
let mut faults = guard_page_faults.borrow_mut();
for (addr, len, reset) in faults.drain(..) {
reset(addr, len)?;
}
}
MemoryStorage::Dynamic(_) => {
unreachable!("dynamic memories should not have guard page faults")
}
}
Ok(())
}
} }
// The default memory representation is an empty memory that cannot grow. // The default memory representation is an empty memory that cannot grow.
@@ -290,6 +350,8 @@ impl Default for Memory {
size: Cell::new(0), size: Cell::new(0),
maximum: 0, maximum: 0,
make_accessible, make_accessible,
#[cfg(all(feature = "uffd", target_os = "linux"))]
guard_page_faults: RefCell::new(Vec::new()),
}) })
} }
} }