Skip to content

Commit ac7f44f

Browse files
authored
Fix pooling allocator predicate to reset VM permissions (#13008)
* Fix pooling allocator predicate to reset VM permissions This commit fixes a mistake that was introduced in #9583 where the logic to reset a linear memory slot in the pooling allocator used the wrong predicate. Specifically VM permissions must be reset if virtual memory can be relied on at all, and the preexisting predicate of `can_elide_bounds_check` was an inaccurate representation of this. The correct predicate to check is `can_use_virtual_memory`. * Fix failure on 32-bit
1 parent 38c2e2a commit ac7f44f

3 files changed

Lines changed: 164 additions & 97 deletions

File tree

crates/wasmtime/src/runtime/vm/cow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ impl MemoryImageSlot {
461461
let host_page_size_log2 = u8::try_from(host_page_size().ilog2()).unwrap();
462462
if initial_size_bytes_page_aligned < self.accessible
463463
&& (tunables.memory_guard_size > 0
464-
|| ty.can_elide_bounds_check(tunables, host_page_size_log2))
464+
|| ty.can_use_virtual_memory(tunables, host_page_size_log2))
465465
{
466466
self.set_protection(initial_size_bytes_page_aligned..self.accessible, false)?;
467467
self.accessible = initial_size_bytes_page_aligned;

tests/all/pooling_allocator.rs

Lines changed: 147 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -670,103 +670,111 @@ fn instance_too_large() -> Result<()> {
670670
#[cfg_attr(miri, ignore)]
671671
fn dynamic_memory_pooling_allocator() -> Result<()> {
672672
for guard_size in [0, 1 << 16] {
673-
let max_size = 128 << 20;
674-
let mut pool = crate::small_pool_config();
675-
pool.max_memory_size(max_size as usize);
676-
let mut config = Config::new();
677-
config.memory_reservation(max_size);
678-
config.memory_guard_size(guard_size);
679-
config.allocation_strategy(pool);
680-
681-
let engine = Engine::new(&config)?;
682-
683-
let module = Module::new(
684-
&engine,
685-
r#"
686-
(module
687-
(memory (export "memory") 1)
688-
689-
(func (export "grow") (param i32) (result i32)
690-
local.get 0
691-
memory.grow)
692-
693-
(func (export "size") (result i32)
694-
memory.size)
695-
696-
(func (export "i32.load") (param i32) (result i32)
697-
local.get 0
698-
i32.load)
699-
700-
(func (export "i32.store") (param i32 i32)
701-
local.get 0
702-
local.get 1
703-
i32.store)
704-
705-
(data (i32.const 100) "x")
706-
)
707-
"#,
708-
)?;
709-
710-
let mut store = Store::new(&engine, ());
711-
let instance = Instance::new(&mut store, &module, &[])?;
673+
for signals_based_traps in [false, true] {
674+
let max_size = 128 << 20;
675+
let mut pool = crate::small_pool_config();
676+
pool.max_memory_size(max_size as usize);
677+
let mut config = Config::new();
678+
config.memory_reservation(max_size);
679+
config.memory_guard_size(guard_size);
680+
config.allocation_strategy(pool);
681+
config.signals_based_traps(signals_based_traps);
682+
683+
let engine = Engine::new(&config)?;
684+
685+
let Ok(module) = Module::new(
686+
&engine,
687+
r#"
688+
(module
689+
(memory (export "memory") 1)
690+
691+
(func (export "grow") (param i32) (result i32)
692+
local.get 0
693+
memory.grow)
694+
695+
(func (export "size") (result i32)
696+
memory.size)
697+
698+
(func (export "i32.load") (param i32) (result i32)
699+
local.get 0
700+
i32.load)
701+
702+
(func (export "i32.store") (param i32 i32)
703+
local.get 0
704+
local.get 1
705+
i32.store)
706+
707+
(data (i32.const 100) "x")
708+
)
709+
"#,
710+
) else {
711+
// Ignore invalid configurations on 32-bit which can't run with
712+
// signals-based-traps.
713+
assert!(cfg!(target_pointer_width = "32") && signals_based_traps);
714+
continue;
715+
};
712716

713-
let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;
714-
let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;
715-
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
716-
let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;
717-
let memory = instance.get_memory(&mut store, "memory").unwrap();
718-
719-
// basic length 1 tests
720-
// assert_eq!(memory.grow(&mut store, 1)?, 0);
721-
assert_eq!(memory.size(&store), 1);
722-
assert_eq!(size.call(&mut store, ())?, 1);
723-
assert_eq!(i32_load.call(&mut store, 0)?, 0);
724-
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'x'));
725-
i32_store.call(&mut store, (0, 0))?;
726-
i32_store.call(&mut store, (100, i32::from(b'y')))?;
727-
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'y'));
728-
729-
// basic length 2 tests
730-
let page = 64 * 1024;
731-
assert_eq!(grow.call(&mut store, 1)?, 1);
732-
assert_eq!(memory.size(&store), 2);
733-
assert_eq!(size.call(&mut store, ())?, 2);
734-
i32_store.call(&mut store, (page, 200))?;
735-
assert_eq!(i32_load.call(&mut store, page)?, 200);
736-
737-
// test writes are visible
738-
i32_store.call(&mut store, (2, 100))?;
739-
assert_eq!(i32_load.call(&mut store, 2)?, 100);
740-
741-
// test growth can't exceed maximum
742-
let too_many = max_size / (64 * 1024);
743-
assert_eq!(grow.call(&mut store, too_many as u32)?, -1);
744-
assert!(memory.grow(&mut store, too_many).is_err());
745-
746-
assert_eq!(memory.data(&store)[page as usize], 200);
747-
748-
// Re-instantiate in another store.
749-
store = Store::new(&engine, ());
750-
let instance = Instance::new(&mut store, &module, &[])?;
751-
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
752-
let memory = instance.get_memory(&mut store, "memory").unwrap();
753-
754-
// This is out of bounds...
755-
assert!(i32_load.call(&mut store, page).is_err());
756-
assert_eq!(memory.data_size(&store), page as usize);
757-
758-
// ... but implementation-wise it should still be mapped memory from
759-
// before if we don't have any guard pages.
760-
//
761-
// Note though that prior writes should all appear as zeros and we can't see
762-
// data from the prior instance.
763-
//
764-
// Note that this part is only implemented on Linux which has
765-
// `MADV_DONTNEED`.
766-
if cfg!(target_os = "linux") && guard_size == 0 {
767-
unsafe {
768-
let ptr = memory.data_ptr(&store);
769-
assert_eq!(*ptr.offset(page as isize), 0);
717+
let mut store = Store::new(&engine, ());
718+
let instance = Instance::new(&mut store, &module, &[])?;
719+
720+
let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;
721+
let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;
722+
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
723+
let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;
724+
let memory = instance.get_memory(&mut store, "memory").unwrap();
725+
726+
// basic length 1 tests
727+
// assert_eq!(memory.grow(&mut store, 1)?, 0);
728+
assert_eq!(memory.size(&store), 1);
729+
assert_eq!(size.call(&mut store, ())?, 1);
730+
assert_eq!(i32_load.call(&mut store, 0)?, 0);
731+
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'x'));
732+
i32_store.call(&mut store, (0, 0))?;
733+
i32_store.call(&mut store, (100, i32::from(b'y')))?;
734+
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'y'));
735+
736+
// basic length 2 tests
737+
let page = 64 * 1024;
738+
assert_eq!(grow.call(&mut store, 1)?, 1);
739+
assert_eq!(memory.size(&store), 2);
740+
assert_eq!(size.call(&mut store, ())?, 2);
741+
i32_store.call(&mut store, (page, 200))?;
742+
assert_eq!(i32_load.call(&mut store, page)?, 200);
743+
744+
// test writes are visible
745+
i32_store.call(&mut store, (2, 100))?;
746+
assert_eq!(i32_load.call(&mut store, 2)?, 100);
747+
748+
// test growth can't exceed maximum
749+
let too_many = max_size / (64 * 1024);
750+
assert_eq!(grow.call(&mut store, too_many as u32)?, -1);
751+
assert!(memory.grow(&mut store, too_many).is_err());
752+
753+
assert_eq!(memory.data(&store)[page as usize], 200);
754+
755+
// Re-instantiate in another store.
756+
store = Store::new(&engine, ());
757+
let instance = Instance::new(&mut store, &module, &[])?;
758+
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
759+
let memory = instance.get_memory(&mut store, "memory").unwrap();
760+
761+
// This is out of bounds...
762+
assert!(i32_load.call(&mut store, page).is_err());
763+
assert_eq!(memory.data_size(&store), page as usize);
764+
765+
// ... but implementation-wise it should still be mapped memory from
766+
// before if we don't have any guard pages.
767+
//
768+
// Note though that prior writes should all appear as zeros and we can't see
769+
// data from the prior instance.
770+
//
771+
// Note that this part is only implemented on Linux which has
772+
// `MADV_DONTNEED`.
773+
if cfg!(target_os = "linux") && guard_size == 0 && !signals_based_traps {
774+
unsafe {
775+
let ptr = memory.data_ptr(&store);
776+
assert_eq!(*ptr.offset(page as isize), 0);
777+
}
770778
}
771779
}
772780
}
@@ -1400,6 +1408,49 @@ fn pagemap_scan_enabled_or_disabled() -> Result<()> {
14001408
Ok(())
14011409
}
14021410

1411+
#[test]
1412+
#[cfg_attr(miri, ignore)]
1413+
fn pooling_reuse_resets() -> Result<()> {
1414+
let mut config = Config::new();
1415+
let mut cfg = crate::small_pool_config();
1416+
cfg.total_memories(1);
1417+
cfg.max_memory_size(0x2000000);
1418+
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
1419+
config.memory_guard_size(0);
1420+
config.memory_reservation(0x2000000);
1421+
let engine = Engine::new(&config)?;
1422+
1423+
let a = Module::new(&engine, r#"(module (memory (export "m") 10 100))"#)?;
1424+
let b = Module::new(
1425+
&engine,
1426+
r#"
1427+
(module $B
1428+
(memory 5 100)
1429+
(func (export "read_oob") (result i32)
1430+
(i32.load (i32.const 983040))
1431+
)
1432+
)
1433+
"#,
1434+
)?;
1435+
1436+
{
1437+
let mut store = Store::new(&engine, ());
1438+
let instance = Instance::new(&mut store, &a, &[])?;
1439+
let memory = instance.get_memory(&mut store, "m").unwrap();
1440+
memory.grow(&mut store, 10)?;
1441+
}
1442+
1443+
{
1444+
let mut store = Store::new(&engine, ());
1445+
let instance = Instance::new(&mut store, &b, &[])?;
1446+
let read_oob = instance.get_typed_func::<(), i32>(&mut store, "read_oob")?;
1447+
let trap: Trap = read_oob.call(&mut store, ()).unwrap_err().downcast()?;
1448+
assert_eq!(trap, Trap::MemoryOutOfBounds);
1449+
}
1450+
1451+
Ok(())
1452+
}
1453+
14031454
// This test instantiates a memory with an image into a slot in the pooling
14041455
// allocator in a way that maps the image into the allocator but fails
14051456
// instantiation. Failure here is injected with `ResourceLimiter`. Afterwards
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(module)
2+
3+
(thread $t1
4+
(module $A
5+
(memory 10 100)
6+
(func (export "grow")
7+
(drop (memory.grow (i32.const 10)))))
8+
(invoke "grow")
9+
)
10+
(wait $t1)
11+
12+
(module $B
13+
(memory 5 100)
14+
(func (export "read_oob") (result i32)
15+
(i32.load (i32.const 983040))))
16+
(assert_trap (invoke "read_oob") "out of bounds memory access")

0 commit comments

Comments
 (0)