Skip to content

Commit bb7ec8e

Browse files
khagankhanKhagan Karimov
andauthored
Fix Out-of-memory in table-ops (#11392)
* Fix Out-of-memory in table-ops * Remove missed println! --------- Co-authored-by: Khagan Karimov <[email protected]>
1 parent c645077 commit bb7ec8e

File tree

2 files changed

+88
-50
lines changed

2 files changed

+88
-50
lines changed

crates/fuzzing/src/generators/table_ops.rs

Lines changed: 84 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@ use wasm_encoder::{
1010
TypeSection, ValType,
1111
};
1212

13-
/// A description of a Wasm module that makes a series of `externref` table
14-
/// operations.
13+
/// Limits controlling the structure of a generated Wasm module.
1514
#[derive(Debug, Default, Serialize, Deserialize)]
16-
pub struct TableOps {
15+
pub struct TableOpsLimits {
1716
pub(crate) num_params: u32,
1817
pub(crate) num_globals: u32,
1918
pub(crate) table_size: i32,
19+
}
20+
21+
/// A description of a Wasm module that makes a series of `externref` table
22+
/// operations.
23+
#[derive(Debug, Default, Serialize, Deserialize)]
24+
pub struct TableOps {
25+
pub(crate) limits: TableOpsLimits,
2026
pub(crate) ops: Vec<TableOp>,
2127
}
2228

@@ -37,7 +43,21 @@ impl TableOps {
3743
/// The "run" function does not terminate; you should run it with limited
3844
/// fuel. It also is not guaranteed to avoid traps: it may access
3945
/// out-of-bounds of the table.
40-
pub fn to_wasm_binary(&self) -> Vec<u8> {
46+
pub fn to_wasm_binary(&mut self) -> Vec<u8> {
47+
// Clamp limits to generate opcodes within bounds
48+
self.limits.table_size = self
49+
.limits
50+
.table_size
51+
.clamp(*TABLE_SIZE_RANGE.start(), *TABLE_SIZE_RANGE.end());
52+
self.limits.num_params = self
53+
.limits
54+
.num_params
55+
.clamp(*NUM_PARAMS_RANGE.start(), *NUM_PARAMS_RANGE.end());
56+
self.limits.num_globals = self
57+
.limits
58+
.num_globals
59+
.clamp(*NUM_GLOBALS_RANGE.start(), *NUM_GLOBALS_RANGE.end());
60+
4161
let mut module = Module::new();
4262

4363
// Encode the types for all functions that we are using.
@@ -56,8 +76,8 @@ impl TableOps {
5676
);
5777

5878
// 1: "run"
59-
let mut params: Vec<ValType> = Vec::with_capacity(self.num_params as usize);
60-
for _i in 0..self.num_params {
79+
let mut params: Vec<ValType> = Vec::with_capacity(self.limits.num_params as usize);
80+
for _i in 0..self.limits.num_params {
6181
params.push(ValType::EXTERNREF);
6282
}
6383
let results = vec![];
@@ -85,15 +105,15 @@ impl TableOps {
85105
let mut tables = TableSection::new();
86106
tables.table(TableType {
87107
element_type: RefType::EXTERNREF,
88-
minimum: self.table_size as u64,
108+
minimum: self.limits.table_size as u64,
89109
maximum: None,
90110
table64: false,
91111
shared: false,
92112
});
93113

94114
// Define our globals.
95115
let mut globals = GlobalSection::new();
96-
for _ in 0..self.num_globals {
116+
for _ in 0..self.limits.num_globals {
97117
globals.global(
98118
wasm_encoder::GlobalType {
99119
val_type: wasm_encoder::ValType::EXTERNREF,
@@ -117,7 +137,7 @@ impl TableOps {
117137

118138
func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
119139
for op in &self.ops {
120-
op.insert(&mut func, self.num_params);
140+
op.insert(&mut func, self.limits.num_params);
121141
}
122142
func.instruction(&Instruction::Br(0));
123143
func.instruction(&Instruction::End);
@@ -154,14 +174,14 @@ impl TableOps {
154174
/// Fixes the stack after mutating the `idx`th op.
155175
///
156176
/// The abstract stack depth starting at the `idx`th opcode must be `stack`.
157-
fn fixup(&mut self, idx: usize, mut stack: usize) {
177+
///
178+
fn fixup(&mut self) {
158179
let mut new_ops = Vec::with_capacity(self.ops.len());
159-
new_ops.extend_from_slice(&self.ops[..idx]);
180+
let mut stack = 0;
181+
182+
for mut op in self.ops.iter().copied() {
183+
op.fixup(&self.limits);
160184

161-
// Iterate through all ops including and after `idx`, inserting a null
162-
// ref for any missing operands when they want to pop more operands than
163-
// exist on the stack.
164-
new_ops.extend(self.ops[idx..].iter().copied().flat_map(|op| {
165185
let mut temp = SmallVec::<[_; 4]>::new();
166186

167187
while stack < op.operands_len() {
@@ -172,11 +192,10 @@ impl TableOps {
172192
temp.push(op);
173193
stack = stack - op.operands_len() + op.results_len();
174194

175-
temp
176-
}));
195+
new_ops.extend(temp);
196+
}
177197

178-
// Now make sure that the stack is empty at the end of the ops by
179-
// inserting drops as necessary.
198+
// Insert drops to balance the final stack state
180199
for _ in 0..stack {
181200
new_ops.push(TableOp::Drop());
182201
}
@@ -205,7 +224,7 @@ impl Mutate<TableOps> for TableOpsMutator {
205224
let stack = ops.abstract_stack_depth(idx);
206225
let (op, _new_stack_size) = TableOp::generate(ctx, &ops, stack)?;
207226
ops.ops.insert(idx, op);
208-
ops.fixup(idx, stack);
227+
ops.fixup();
209228
}
210229
Ok(())
211230
})?;
@@ -218,9 +237,8 @@ impl Mutate<TableOps> for TableOpsMutator {
218237
.rng()
219238
.gen_index(ops.ops.len())
220239
.expect("ops is not empty");
221-
let stack = ops.abstract_stack_depth(idx);
222240
ops.ops.remove(idx);
223-
ops.fixup(idx, stack);
241+
ops.fixup();
224242
Ok(())
225243
})?;
226244
}
@@ -255,9 +273,11 @@ impl Generate<TableOps> for TableOpsMutator {
255273
let table_size = m::range(TABLE_SIZE_RANGE).generate(ctx)?;
256274

257275
let mut ops = TableOps {
258-
num_params,
259-
num_globals,
260-
table_size,
276+
limits: TableOpsLimits {
277+
num_params,
278+
num_globals,
279+
table_size,
280+
},
261281
ops: vec![
262282
TableOp::Null(),
263283
TableOp::Drop(),
@@ -288,7 +308,7 @@ impl Generate<TableOps> for TableOpsMutator {
288308
macro_rules! define_table_ops {
289309
(
290310
$(
291-
$op:ident $( ( $($limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
311+
$op:ident $( ( $($limit_var:ident : $limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
292312
)*
293313
) => {
294314
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
@@ -335,7 +355,7 @@ macro_rules! define_table_ops {
335355
#[allow(non_snake_case, reason = "macro-generated code")]
336356
fn $op(
337357
_ctx: &mut mutatis::Context,
338-
_ops: &TableOps,
358+
_limits: &TableOpsLimits,
339359
stack: usize,
340360
) -> mutatis::Result<(TableOp, usize)> {
341361
#[allow(unused_comparisons, reason = "macro-generated code")]
@@ -345,8 +365,8 @@ macro_rules! define_table_ops {
345365

346366
let op = TableOp::$op(
347367
$($({
348-
let limit_fn = $limit as fn(&TableOps) -> $ty;
349-
let limit = (limit_fn)(_ops);
368+
let limit_fn = $limit as fn(&TableOpsLimits) -> $ty;
369+
let limit = (limit_fn)(_limits);
350370
debug_assert!(limit > 0);
351371
m::range(0..=limit - 1).generate(_ctx)?
352372
})*)?
@@ -357,21 +377,35 @@ macro_rules! define_table_ops {
357377
)*
358378

359379
impl TableOp {
380+
fn fixup(&mut self, limits: &TableOpsLimits) {
381+
match self {
382+
$(
383+
Self::$op( $( $( $limit_var ),* )? ) => {
384+
$( $(
385+
let limit_fn = $limit as fn(&TableOpsLimits) -> $ty;
386+
let limit = (limit_fn)(limits);
387+
debug_assert!(limit > 0);
388+
*$limit_var = *$limit_var % limit;
389+
)* )?
390+
}
391+
)*
392+
}
393+
}
394+
360395
fn generate(
361396
ctx: &mut mutatis::Context,
362397
ops: &TableOps,
363398
stack: usize,
364399
) -> mutatis::Result<(TableOp, usize)> {
365400
let mut valid_choices: Vec<
366-
fn (&mut mutatis::Context, &TableOps, usize) -> mutatis::Result<(TableOp, usize)>
401+
fn(&mut Context, &TableOpsLimits, usize) -> mutatis::Result<(TableOp, usize)>
367402
> = vec![];
368-
369403
$(
370404
#[allow(unused_comparisons, reason = "macro-generated code")]
371405
if stack >= $params $($(
372406
&& {
373-
let limit_fn: fn(&TableOps) -> $ty = $limit;
374-
let limit = (limit_fn)(ops);
407+
let limit_fn = $limit as fn(&TableOpsLimits) -> $ty;
408+
let limit = (limit_fn)(&ops.limits);
375409
limit > 0
376410
}
377411
)*)? {
@@ -383,7 +417,7 @@ macro_rules! define_table_ops {
383417
.choose(&valid_choices)
384418
.expect("should always have a valid op choice");
385419

386-
(f)(ctx, ops, stack)
420+
(f)(ctx, &ops.limits, stack)
387421
}
388422
}
389423
};
@@ -396,14 +430,14 @@ define_table_ops! {
396430
TakeRefs : 3 => 0,
397431

398432
// Add one to make sure that out of bounds table accesses are possible, but still rare.
399-
TableGet(|ops| ops.table_size + 1 => i32) : 0 => 1,
400-
TableSet(|ops| ops.table_size + 1 => i32) : 1 => 0,
433+
TableGet(elem_index: |ops| ops.table_size + 1 => i32) : 0 => 1,
434+
TableSet(elem_index: |ops| ops.table_size + 1 => i32) : 1 => 0,
401435

402-
GlobalGet(|ops| ops.num_globals => u32) : 0 => 1,
403-
GlobalSet(|ops| ops.num_globals => u32) : 1 => 0,
436+
GlobalGet(global_index: |ops| ops.num_globals => u32) : 0 => 1,
437+
GlobalSet(global_index: |ops| ops.num_globals => u32) : 1 => 0,
404438

405-
LocalGet(|ops| ops.num_params => u32) : 0 => 1,
406-
LocalSet(|ops| ops.num_params => u32) : 1 => 0,
439+
LocalGet(local_index: |ops| ops.num_params => u32) : 0 => 1,
440+
LocalSet(local_index: |ops| ops.num_params => u32) : 1 => 0,
407441

408442
Drop : 1 => 0,
409443

@@ -465,19 +499,23 @@ mod tests {
465499
/// Creates empty TableOps
466500
fn empty_test_ops(num_params: u32, num_globals: u32, table_size: i32) -> TableOps {
467501
TableOps {
468-
num_params,
469-
num_globals,
470-
table_size,
502+
limits: TableOpsLimits {
503+
num_params,
504+
num_globals,
505+
table_size,
506+
},
471507
ops: vec![],
472508
}
473509
}
474510

475511
/// Creates TableOps with all default opcodes
476512
fn test_ops(num_params: u32, num_globals: u32, table_size: i32) -> TableOps {
477513
TableOps {
478-
num_params,
479-
num_globals,
480-
table_size,
514+
limits: TableOpsLimits {
515+
num_params,
516+
num_globals,
517+
table_size,
518+
},
481519
ops: vec![
482520
TableOp::Null(),
483521
TableOp::Drop(),

crates/fuzzing/src/oracles.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ pub fn wast_test(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<()> {
775775
/// case -- used to test below that gc happens reasonably soon and eventually.
776776
pub fn table_ops(
777777
mut fuzz_config: generators::Config,
778-
ops: generators::table_ops::TableOps,
778+
mut ops: generators::table_ops::TableOps,
779779
) -> Result<usize> {
780780
let expected_drops = Arc::new(AtomicUsize::new(0));
781781
let num_dropped = Arc::new(AtomicUsize::new(0));
@@ -922,9 +922,9 @@ pub fn table_ops(
922922

923923
log::info!(
924924
"table_ops: begin allocating {} externref arguments",
925-
ops.num_globals
925+
ops.limits.num_globals
926926
);
927-
let args: Vec<_> = (0..ops.num_params)
927+
let args: Vec<_> = (0..ops.limits.num_params)
928928
.map(|_| {
929929
Ok(Val::ExternRef(Some(ExternRef::new(
930930
&mut scope,
@@ -934,7 +934,7 @@ pub fn table_ops(
934934
.collect::<Result<_>>()?;
935935
log::info!(
936936
"table_ops: end allocating {} externref arguments",
937-
ops.num_globals
937+
ops.limits.num_globals
938938
);
939939

940940
// The generated function should always return a trap. The only two

0 commit comments

Comments
 (0)