gaynor-anthropic opened issue #12813:
ExtendedOpcode::MAX is computed as the variant count (N), not the max discriminant (N-1). new() checks bytes <= MAX, so new(N) passes validation and reaches transmute(N) on an invalid discriminant . Reachable via Disassembler::disassemble_all on 3 crafted bytes; Miri confirms. Niche optimisation currently masks it (Some(transmute(N)) bit-aliases None), but that's a layout coincidence.
Minimal PoC:
$ cat src/main.rs //! `ExtendedOpcode::MAX` is the variant *count* (310), not the max //! discriminant (309). `new()` checks `<= MAX`, so `new(310)` reaches //! `transmute(310)` — UB. Miri catches it; natively it niche-collapses //! to `None` by coincidence. //! //! cargo +nightly miri run #![forbid(unsafe_code)] use pulley_interpreter::disas::Disassembler; use pulley_interpreter::opcode::{ExtendedOpcode, Opcode}; fn main() { // Safe public disassembler → SafeBytecodeStream → ExtendedOpcode::new(MAX) let bytes = [ Opcode::ExtendedOp as u8, ExtendedOpcode::MAX as u8, (ExtendedOpcode::MAX >> 8) as u8, ]; let _ = Disassembler::disassemble_all(&bytes); } $ cargo miri run Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s Running `/root/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/bin/cargo-miri runner target/miri/x86_64-unknown-linux-gnu/debug/poc_bug_029` error: Undefined Behavior: constructing invalid value at .<enum-tag>: encountered 0x0136, but expected a valid enum tag --> /root/home/opensrc/wasmtime/pulley/src/opcode.rs:104:18 | 104 | unsafe { core::mem::transmute(byte) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: = note: inside `pulley_interpreter::ExtendedOpcode::unchecked_new` at /root/home/opensrc/wasmtime/pulley/src/opcode.rs:104:18: 104:44 = note: inside `pulley_interpreter::ExtendedOpcode::new` at /root/home/opensrc/wasmtime/pulley/src/opcode.rs:91:27: 91:53 = note: inside `pulley_interpreter::decode::decode_one_extended::<pulley_interpreter::disas::Disassembler<'_>>` at /root/home/opensrc/wasmtime/pulley/src/decode.rs:707:26: 707:51 = note: inside `pulley_interpreter::decode::Decoder::decode_one::<pulley_interpreter::disas::Disassembler<'_>>` at /root/home/opensrc/wasmtime/pulley/src/decode.rs:601:25: 601:53 = note: inside `pulley_interpreter::decode::Decoder::decode_all::<'_, pulley_interpreter::disas::Disassembler<'_>>` at /root/home/opensrc/wasmtime/pulley/src/decode.rs:528:26: 528:53 = note: inside `pulley_interpreter::disas::Disassembler::<'_>::disassemble_all` at /root/home/opensrc/wasmtime/pulley/src/disas.rs:32:9: 32:40 note: inside `main` --> src/main.rs:19:13 | 19 | let _ = Disassembler::disassemble_all(&bytes); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace error: aborting due to 1 previous errorNot a security issue because pully is a tier 2 feature.
Last updated: Mar 23 2026 at 16:19 UTC