Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions fuzz/fuzz_targets/differential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ fn execute_one(data: &[u8]) -> Result<()> {
gen_cfg.max_tables = 1;
// Keep wasm-smith table limits inside rwasm runtime hard cap.
gen_cfg.max_table_elements = rwasm::N_MAX_TABLE_SIZE as u64;
// Force several segments so `data.drop`/`elem.drop` sequences with descending indices
// are reachable, they used to truncate the dropped-segment bitset.
gen_cfg.min_data_segments = 4;
gen_cfg.min_element_segments = 4;
gen_cfg.export_everything = true;

STATS.wasm_smith_modules.fetch_add(1, SeqCst);
Expand Down
9 changes: 7 additions & 2 deletions src/vm/executor/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,13 @@ impl<'a, T> RwasmExecutor<'a, T> {
#[inline(always)]
pub(crate) fn visit_data_drop(&mut self, data_segment_idx: DataSegmentIdx) {
let empty_data_segments = &mut self.store.empty_data_segments;
empty_data_segments.resize(data_segment_idx as usize + 1, false);
empty_data_segments.set(data_segment_idx as usize, true);
// grow only, `resize` would truncate the bitset and forget previously dropped segments
// with a higher index
let idx = data_segment_idx as usize;
if idx >= empty_data_segments.len() {
empty_data_segments.resize(idx + 1, false);
}
empty_data_segments.set(idx, true);
self.ip.add(1);
}
}
9 changes: 7 additions & 2 deletions src/vm/executor/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,13 @@ impl<'a, T> RwasmExecutor<'a, T> {
#[inline(always)]
pub(crate) fn visit_element_drop(&mut self, element_segment_idx: ElementSegmentIdx) {
let empty_elem_segments = &mut self.store.empty_elem_segments;
empty_elem_segments.resize(element_segment_idx as usize + 1, false);
empty_elem_segments.set(element_segment_idx as usize, true);
// grow only, `resize` would truncate the bitset and forget previously dropped segments
// with a higher index
let idx = element_segment_idx as usize;
if idx >= empty_elem_segments.len() {
empty_elem_segments.resize(idx + 1, false);
}
empty_elem_segments.set(idx, true);
self.ip.add(1);
}
}
92 changes: 92 additions & 0 deletions tests/segment_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use rwasm::{
always_failing_syscall_handler, instruction_set, ExecutionEngine, ImportLinker, InstructionSet,
RwasmModuleBuilder, RwasmStore, TrapCode,
};

fn execute(code_section: InstructionSet, data: &[u8], elem: &[u32]) -> Result<(), TrapCode> {
let module = RwasmModuleBuilder::new(code_section)
.with_data_section(data)
.with_elem_section(elem)
.build();
let engine = ExecutionEngine::new();
let mut store = RwasmStore::new(
ImportLinker::default().into(),
(),
always_failing_syscall_handler,
None,
None,
);
engine
.execute(&mut store, &module, &[], &mut [])
.map(|_| ())
}

fn data_drop_code(dropped: &[u32]) -> InstructionSet {
let mut code = instruction_set! {
I32Const(1)
MemoryGrow
Drop
};
for segment in dropped {
code.op_data_drop(*segment);
}
code.op_i32_const(0); // d
code.op_i32_const(0); // s
code.op_i32_const(1); // n
code.op_memory_init(5);
code.op_return();
code
}

fn elem_drop_code(dropped: &[u32]) -> InstructionSet {
let mut code = instruction_set! {
I32Const(0) // init
I32Const(1) // delta
TableGrow(0)
Drop
};
for segment in dropped {
code.op_elem_drop(*segment);
}
code.op_i32_const(0); // d
code.op_i32_const(0); // s
code.op_i32_const(1); // n
code.op_table_init(5);
code.op_table_get(0); // table index payload of `table.init`
code.op_return();
code
}

#[test]
fn test_data_drop_keeps_higher_dropped_segments() {
let data = &[0xaa, 0xbb, 0xcc, 0xdd];
// not dropped at all, `memory.init` reads the real data section
assert_eq!(execute(data_drop_code(&[]), data, &[]), Ok(()));
// dropped, `memory.init` must observe a zero-length segment and trap
assert_eq!(
execute(data_drop_code(&[5]), data, &[]),
Err(TrapCode::MemoryOutOfBounds),
);
// dropping a lower-indexed segment afterward must not resurrect segment 5
assert_eq!(
execute(data_drop_code(&[5, 2]), data, &[]),
Err(TrapCode::MemoryOutOfBounds),
);
}

#[test]
fn test_elem_drop_keeps_higher_dropped_segments() {
let elem = &[1u32, 2, 3, 4];
// not dropped at all, `table.init` reads the real element section
assert_eq!(execute(elem_drop_code(&[]), &[], elem), Ok(()));
// dropped, `table.init` must observe a zero-length segment and trap
assert_eq!(
execute(elem_drop_code(&[5]), &[], elem),
Err(TrapCode::TableOutOfBounds),
);
// dropping a lower-indexed segment afterward must not resurrect segment 5
assert_eq!(
execute(elem_drop_code(&[5, 2]), &[], elem),
Err(TrapCode::TableOutOfBounds),
);
}
Loading