Skip to content

Commit f6395c5

Browse files
authored
Add support for memory-related bulk-memory instructions (bytecodealliance#24)
This commit adds support for some new instructions: * `data.drop` * `memory.init` * `memory.copy` * `memory.fill` This also adds support for passive data and the data count section. A new `Config::bulk_memory_enabled` boolean, defaulting to `false`, is added which gates all of this support. Finally the generation of data segments was moved before the code so we know whether `data.drop` will be valid by the time we're generating code. Finally, a new fuzz target was added which validates all possible modules (generated by `SwarmConfig`) with `wasmparser`.
1 parent 6d6893c commit f6395c5

7 files changed

Lines changed: 239 additions & 20 deletions

File tree

.github/workflows/rust.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ jobs:
2727
- run: cargo fuzz build validate --sanitizer none
2828
- run: cargo fuzz run validate --sanitizer none -- -max_total_time=60
2929

30+
validate_swarm_fuzz_target:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v2
34+
- run: cargo install cargo-fuzz
35+
- run: cargo fuzz build validate-swarm --sanitizer none
36+
- run: cargo fuzz run validate-swarm --sanitizer none -- -max_total_time=60
37+
3038
validate_ensure_terminated_fuzz_target:
3139
runs-on: ubuntu-latest
3240
steps:

fuzz/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ name = "validate-ensure-termination"
2525
path = "fuzz_targets/validate-ensure-termination.rs"
2626
test = false
2727
doc = false
28+
29+
[[bin]]
30+
name = "validate-swarm"
31+
path = "fuzz_targets/validate-swarm.rs"
32+
test = false
33+
doc = false
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
use wasm_smith::{Config, ConfiguredModule, SwarmConfig};
5+
6+
fuzz_target!(|m: ConfiguredModule<SwarmConfig>| {
7+
let bytes = m.to_bytes();
8+
9+
let mut validator = wasmparser::Validator::new();
10+
validator.wasm_features(wasmparser::WasmFeatures {
11+
multi_value: true,
12+
bulk_memory: m.config().bulk_memory_enabled(),
13+
multi_memory: m.config().max_memories() > 1,
14+
..wasmparser::WasmFeatures::default()
15+
});
16+
if let Err(e) = validator.validate_all(&bytes) {
17+
std::fs::write("test.wasm", bytes).unwrap();
18+
panic!("Invalid module: {}", e);
19+
}
20+
});

src/code_builder.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ instructions! {
105105
(Some(i64_store_valid), i64_store_32),
106106
(Some(have_memory), memory_size),
107107
(Some(memory_grow_valid), memory_grow),
108+
(Some(memory_init_valid), memory_init),
109+
(Some(data_drop_valid), data_drop),
110+
(Some(memory_copy_valid), memory_copy),
111+
(Some(memory_fill_valid), memory_fill),
108112
// Numeric instructions.
109113
(None, i32_const),
110114
(None, i64_const),
@@ -1053,6 +1057,11 @@ fn have_memory_and_offset<C: Config>(
10531057
have_memory(module, builder) && builder.type_on_stack(ValType::I32)
10541058
}
10551059

1060+
#[inline]
1061+
fn have_data<C: Config>(module: &ConfiguredModule<C>, _: &mut CodeBuilder<C>) -> bool {
1062+
module.data.len() > 0
1063+
}
1064+
10561065
fn i32_load<C: Config>(
10571066
u: &mut Unstructured,
10581067
module: &ConfiguredModule<C>,
@@ -1395,6 +1404,80 @@ fn memory_grow<C: Config>(
13951404
Ok(Instruction::MemoryGrow(memory_index(u, builder)?))
13961405
}
13971406

1407+
#[inline]
1408+
fn memory_init_valid<C: Config>(
1409+
module: &ConfiguredModule<C>,
1410+
builder: &mut CodeBuilder<C>,
1411+
) -> bool {
1412+
have_memory(module, builder)
1413+
&& have_data(module, builder)
1414+
&& module.config.bulk_memory_enabled()
1415+
&& builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32])
1416+
}
1417+
1418+
fn memory_init<C: Config>(
1419+
u: &mut Unstructured,
1420+
module: &ConfiguredModule<C>,
1421+
builder: &mut CodeBuilder<C>,
1422+
) -> Result<Instruction> {
1423+
let mem = memory_index(u, builder)?;
1424+
let data = data_index(u, module)?;
1425+
builder.pop_operands(&[ValType::I32, ValType::I32, ValType::I32]);
1426+
Ok(Instruction::MemoryInit { mem, data })
1427+
}
1428+
1429+
#[inline]
1430+
fn memory_fill_valid<C: Config>(
1431+
module: &ConfiguredModule<C>,
1432+
builder: &mut CodeBuilder<C>,
1433+
) -> bool {
1434+
have_memory(module, builder)
1435+
&& module.config.bulk_memory_enabled()
1436+
&& builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32])
1437+
}
1438+
1439+
fn memory_fill<C: Config>(
1440+
u: &mut Unstructured,
1441+
_module: &ConfiguredModule<C>,
1442+
builder: &mut CodeBuilder<C>,
1443+
) -> Result<Instruction> {
1444+
let mem = memory_index(u, builder)?;
1445+
builder.pop_operands(&[ValType::I32, ValType::I32, ValType::I32]);
1446+
Ok(Instruction::MemoryFill(mem))
1447+
}
1448+
1449+
#[inline]
1450+
fn memory_copy_valid<C: Config>(
1451+
module: &ConfiguredModule<C>,
1452+
builder: &mut CodeBuilder<C>,
1453+
) -> bool {
1454+
memory_fill_valid(module, builder)
1455+
}
1456+
1457+
fn memory_copy<C: Config>(
1458+
u: &mut Unstructured,
1459+
_module: &ConfiguredModule<C>,
1460+
builder: &mut CodeBuilder<C>,
1461+
) -> Result<Instruction> {
1462+
let src = memory_index(u, builder)?;
1463+
let dst = memory_index(u, builder)?;
1464+
builder.pop_operands(&[ValType::I32, ValType::I32, ValType::I32]);
1465+
Ok(Instruction::MemoryCopy { dst, src })
1466+
}
1467+
1468+
#[inline]
1469+
fn data_drop_valid<C: Config>(module: &ConfiguredModule<C>, builder: &mut CodeBuilder<C>) -> bool {
1470+
have_data(module, builder) && module.config.bulk_memory_enabled()
1471+
}
1472+
1473+
fn data_drop<C: Config>(
1474+
u: &mut Unstructured,
1475+
module: &ConfiguredModule<C>,
1476+
_builder: &mut CodeBuilder<C>,
1477+
) -> Result<Instruction> {
1478+
Ok(Instruction::DataDrop(data_index(u, module)?))
1479+
}
1480+
13981481
fn i32_const<C: Config>(
13991482
u: &mut Unstructured,
14001483
_: &ConfiguredModule<C>,
@@ -2885,3 +2968,13 @@ fn memory_index<C: Config>(u: &mut Unstructured, builder: &mut CodeBuilder<C>) -
28852968
u.int_in_range(0..=builder.allocs.num_memories - 1)
28862969
}
28872970
}
2971+
2972+
fn data_index<C: Config>(u: &mut Unstructured, module: &ConfiguredModule<C>) -> Result<u32> {
2973+
let data = module.data.len() as u32;
2974+
assert!(data > 0);
2975+
if data == 1 {
2976+
Ok(0)
2977+
} else {
2978+
u.int_in_range(0..=data - 1)
2979+
}
2980+
}

src/config.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ pub trait Config: Arbitrary + Default {
107107
fn min_uleb_size(&self) -> u8 {
108108
1
109109
}
110+
111+
/// Determines whether the bulk memory proposal is enabled for generating
112+
/// insructions. Defaults to `false`.
113+
fn bulk_memory_enabled(&self) -> bool {
114+
false
115+
}
110116
}
111117

112118
/// The default configuration.
@@ -133,6 +139,7 @@ pub struct SwarmConfig {
133139
max_instructions: usize,
134140
max_memories: u32,
135141
min_uleb_size: u8,
142+
bulk_memory_enabled: bool,
136143
}
137144

138145
impl Arbitrary for SwarmConfig {
@@ -147,8 +154,9 @@ impl Arbitrary for SwarmConfig {
147154
max_elements: u.int_in_range(0..=MAX_MAXIMUM)?,
148155
max_data_segments: u.int_in_range(0..=MAX_MAXIMUM)?,
149156
max_instructions: u.int_in_range(0..=MAX_MAXIMUM)?,
150-
max_memories: u.int_in_range(0..=(MAX_MAXIMUM as u32))?,
157+
max_memories: u.int_in_range(0..=100)?,
151158
min_uleb_size: u.int_in_range(0..=5)?,
159+
bulk_memory_enabled: u.arbitrary()?,
152160
})
153161
}
154162
}
@@ -193,4 +201,8 @@ impl Config for SwarmConfig {
193201
fn min_uleb_size(&self) -> u8 {
194202
self.min_uleb_size
195203
}
204+
205+
fn bulk_memory_enabled(&self) -> bool {
206+
self.bulk_memory_enabled
207+
}
196208
}

src/encode.rs

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ where
3030
self.encode_exports(&mut bytes);
3131
self.encode_start(&mut bytes);
3232
self.encode_elems(&mut bytes);
33+
self.encode_data_count(&mut bytes);
3334
self.encode_code(&mut bytes);
3435
self.encode_data(&mut bytes);
3536

@@ -347,6 +348,28 @@ where
347348
bytes.push(0x40);
348349
self.encode_u32(bytes, *i);
349350
}
351+
Instruction::MemoryInit { mem, data } => {
352+
bytes.push(0xfc);
353+
self.encode_u32(bytes, 8);
354+
self.encode_u32(bytes, *data);
355+
self.encode_u32(bytes, *mem);
356+
}
357+
Instruction::DataDrop(data) => {
358+
bytes.push(0xfc);
359+
self.encode_u32(bytes, 9);
360+
self.encode_u32(bytes, *data);
361+
}
362+
Instruction::MemoryCopy { src, dst } => {
363+
bytes.push(0xfc);
364+
self.encode_u32(bytes, 10);
365+
self.encode_u32(bytes, *dst);
366+
self.encode_u32(bytes, *src);
367+
}
368+
Instruction::MemoryFill(mem) => {
369+
bytes.push(0xfc);
370+
self.encode_u32(bytes, 11);
371+
self.encode_u32(bytes, *mem);
372+
}
350373

351374
// Numeric instructions.
352375
Instruction::I32Const(x) => {
@@ -671,6 +694,20 @@ where
671694
});
672695
}
673696

697+
fn encode_data_count(&self, bytes: &mut Vec<u8>) {
698+
// Without bulk memory there's no need for a data count section
699+
if !self.config.bulk_memory_enabled() {
700+
return;
701+
}
702+
// ... and also if there's no data no need for a data count section
703+
if self.data.is_empty() {
704+
return;
705+
}
706+
self.section(bytes, 12, |bytes| {
707+
self.encode_u32(bytes, self.data.len() as u32);
708+
})
709+
}
710+
674711
fn encode_code(&self, bytes: &mut Vec<u8>) {
675712
if self.code.is_empty() {
676713
return;
@@ -704,14 +741,28 @@ where
704741
fn encode_data(&self, bytes: &mut Vec<u8>) {
705742
self.section(bytes, 11, |bytes| {
706743
self.encode_vec(bytes, &self.data, |bytes, data| {
707-
if data.memory_index == 0 {
708-
self.encode_u32(bytes, 0);
709-
} else {
710-
self.encode_u32(bytes, 2);
711-
self.encode_u32(bytes, data.memory_index);
744+
match &data.kind {
745+
DataSegmentKind::Active {
746+
memory_index: 0,
747+
offset,
748+
} => {
749+
bytes.push(0x00);
750+
self.encode_instruction(bytes, offset);
751+
self.encode_instruction(bytes, &Instruction::End);
752+
}
753+
DataSegmentKind::Passive => {
754+
bytes.push(0x01);
755+
}
756+
DataSegmentKind::Active {
757+
memory_index,
758+
offset,
759+
} => {
760+
bytes.push(0x02);
761+
self.encode_u32(bytes, *memory_index);
762+
self.encode_instruction(bytes, offset);
763+
self.encode_instruction(bytes, &Instruction::End);
764+
}
712765
}
713-
self.encode_instruction(bytes, &data.offset);
714-
self.encode_instruction(bytes, &Instruction::End);
715766
self.encode_vec(bytes, &data.init, |bytes, b| {
716767
bytes.push(*b);
717768
});

src/lib.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ where
106106
data: Vec<DataSegment>,
107107
}
108108

109+
impl<C: Config> ConfiguredModule<C> {
110+
/// Returns a reference to the internal configuration.
111+
pub fn config(&self) -> &C {
112+
&self.config
113+
}
114+
}
115+
109116
impl<C: Config> Arbitrary for ConfiguredModule<C> {
110117
fn arbitrary(u: &mut Unstructured) -> Result<Self> {
111118
let mut module = ConfiguredModule::<C>::default();
@@ -339,6 +346,10 @@ enum Instruction {
339346
I64Store32(MemArg),
340347
MemorySize(u32),
341348
MemoryGrow(u32),
349+
MemoryInit { mem: u32, data: u32 },
350+
DataDrop(u32),
351+
MemoryCopy { src: u32, dst: u32 },
352+
MemoryFill(u32),
342353

343354
// Numeric instructions.
344355
I32Const(i32),
@@ -485,11 +496,19 @@ enum Instruction {
485496

486497
#[derive(Debug)]
487498
struct DataSegment {
488-
memory_index: u32,
489-
offset: Instruction,
499+
kind: DataSegmentKind,
490500
init: Vec<u8>,
491501
}
492502

503+
#[derive(Debug)]
504+
enum DataSegmentKind {
505+
Passive,
506+
Active {
507+
memory_index: u32,
508+
offset: Instruction,
509+
},
510+
}
511+
493512
impl<C> ConfiguredModule<C>
494513
where
495514
C: Config,
@@ -507,8 +526,8 @@ where
507526
self.arbitrary_exports(u)?;
508527
self.arbitrary_start(u)?;
509528
self.arbitrary_elems(u)?;
510-
self.arbitrary_code(u, allow_invalid)?;
511529
self.arbitrary_data(u)?;
530+
self.arbitrary_code(u, allow_invalid)?;
512531
Ok(())
513532
}
514533

@@ -825,8 +844,10 @@ where
825844
}
826845

827846
fn arbitrary_data(&mut self, u: &mut Unstructured) -> Result<()> {
847+
// With bulk-memory we can generate passive data, otherwise if there are
848+
// no memories we can't generate any data.
828849
let memories = self.memories.len() as u32 + self.memory_imports();
829-
if memories == 0 {
850+
if memories == 0 && !self.config.bulk_memory_enabled() {
830851
return Ok(());
831852
}
832853

@@ -852,15 +873,23 @@ where
852873
}
853874
}
854875

855-
let f = u.choose(&choices)?;
856-
let offset = f(u)?;
876+
// Passive data can only be generated if bulk memory is enabled.
877+
// Otherwise if there are no memories we *only* generate passive
878+
// data. Finally if all conditions are met we use an input byte to
879+
// determine if it should be passive or active.
880+
let kind = if self.config.bulk_memory_enabled() && (memories == 0 || u.arbitrary()?) {
881+
DataSegmentKind::Passive
882+
} else {
883+
let f = u.choose(&choices)?;
884+
let offset = f(u)?;
885+
let memory_index = u.int_in_range(0..=memories - 1)?;
886+
DataSegmentKind::Active {
887+
offset,
888+
memory_index,
889+
}
890+
};
857891
let init = u.arbitrary()?;
858-
let memory_index = u.int_in_range(0..=memories - 1)?;
859-
self.data.push(DataSegment {
860-
memory_index,
861-
offset,
862-
init,
863-
});
892+
self.data.push(DataSegment { kind, init });
864893
Ok(())
865894
})
866895
}

0 commit comments

Comments
 (0)