Stream: git-wasmtime

Topic: wasmtime / issue #13982 MPK does not reset protection key...


view this post on Zulip Wasmtime GitHub notifications bot (Jul 25 2026 at 19:51):

alexcrichton opened issue #13982:

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

view this post on Zulip Wasmtime GitHub notifications bot (Jul 25 2026 at 19:51):

alexcrichton added the bug label to Issue #13982.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 25 2026 at 19:51):

alexcrichton added the wasmtime:pooling-allocator label to Issue #13982.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 30 2026 at 16:56):

alexcrichton commented on issue #13982:

cc @jlb6740 and this may also just be a dupe of https://github.com/bytecodealliance/wasmtime/issues/7942

view this post on Zulip Wasmtime GitHub notifications bot (Jul 31 2026 at 21:51):

jlb6740 commented on issue #13982:

@alexcrichton Thanks for this. I am looking into a fix now and yes, I think this is the same as reported in https://github.com/bytecodealliance/wasmtime/issues/7942. The fix is to simply re-apply the key with after each of the mmap calls that clobber it. I do not think this is expected to happen a lot and the overhead appears to be just one more syscall both of which may be completely acceptable tradeoffs when saving the memory is the primary concern. I have a patch (fix) I'll push for your comment, but first I am going through and making sure I understand all the implications.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 07 2026 at 18:01):

alexcrichton closed issue #13982:

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


Last updated: Aug 30 2026 at 09:07 UTC