Skip to content

Commit

Permalink
Add spawn fuzzing test (#17)
Browse files Browse the repository at this point in the history
* Add spawn fuzzing test

* Fix offset error
  • Loading branch information
XuJiandong authored Apr 10, 2024
1 parent 6c28298 commit 8f40da9
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 5 deletions.
4 changes: 4 additions & 0 deletions script/fuzz/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ target
corpus
artifacts
coverage
flamegraph.svg
perf.data
perf.data.old

6 changes: 6 additions & 0 deletions script/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ name = "syscall_exec"
path = "fuzz_targets/syscall_exec.rs"
test = false
doc = false

[[bin]]
name = "syscall_spawn"
path = "fuzz_targets/syscall_spawn.rs"
test = false
doc = false
2 changes: 1 addition & 1 deletion script/fuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ cargo install cargo-fuzz

run fuzz test
```
cargo +nightly fuzz run transaction_scripts_verifier_data1
cargo +nightly fuzz run -j $(nproc) transaction_scripts_verifier_data1
```

generate coverage report
Expand Down
136 changes: 136 additions & 0 deletions script/fuzz/fuzz_targets/syscall_spawn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#![no_main]
use libfuzzer_sys::fuzz_target;

use ckb_chain_spec::consensus::ConsensusBuilder;
use ckb_script::{TransactionScriptsVerifier, TxVerifyEnv};
use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider};
use ckb_types::{
bytes::Bytes,
core::{
capacity_bytes,
cell::{CellMetaBuilder, ResolvedTransaction},
hardfork::{HardForks, CKB2021, CKB2023},
Capacity, HeaderView, ScriptHashType, TransactionBuilder, TransactionInfo,
},
h256,
packed::{
self, Byte32, CellInput, CellOutput, CellOutputBuilder, OutPoint, Script,
TransactionInfoBuilder, TransactionKeyBuilder,
},
prelude::*,
};

#[derive(Default, PartialEq, Eq, Clone)]
struct MockDataLoader {}

impl CellDataProvider for MockDataLoader {
fn get_cell_data(&self, _out_point: &OutPoint) -> Option<Bytes> {
None
}

fn get_cell_data_hash(&self, _out_point: &OutPoint) -> Option<Byte32> {
None
}
}

impl HeaderProvider for MockDataLoader {
fn get_header(&self, _block_hash: &Byte32) -> Option<HeaderView> {
None
}
}

impl ExtensionProvider for MockDataLoader {
fn get_block_extension(&self, _hash: &Byte32) -> Option<packed::Bytes> {
None
}
}

fn mock_transaction_info() -> TransactionInfo {
TransactionInfoBuilder::default()
.block_number(1u64.pack())
.block_epoch(0u64.pack())
.key(
TransactionKeyBuilder::default()
.block_hash(Byte32::zero())
.index(1u32.pack())
.build(),
)
.build()
.unpack()
}

static PROGRAM_DATA: &[u8] = include_bytes!("../../testdata/spawn_fuzzing");

fn run(data: &[u8]) {
if data.len() < 8 {
return;
}
let split_offset = data[0] as usize;
let split_offset = usize::min(split_offset, data.len() - 1);
let parent_witness = Bytes::copy_from_slice(&data[0..split_offset]);
let child_witness = Bytes::copy_from_slice(&data[split_offset..]);
let witnesses = vec![parent_witness.pack(), child_witness.pack()];

let transaction = TransactionBuilder::default()
.input(CellInput::new(OutPoint::null(), 0))
.set_witnesses(witnesses)
.build();

let data: Bytes = (Vec::from(PROGRAM_DATA)).into();
let script = Script::new_builder()
.hash_type(ScriptHashType::Data2.into())
.code_hash(CellOutput::calc_data_hash(&data))
.build();
let dep_cell = CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(Capacity::bytes(data.len()).unwrap().pack())
.build(),
data,
)
.transaction_info(mock_transaction_info())
.out_point(OutPoint::new(h256!("0x0").pack(), 0))
.build();

let input_cell = CellMetaBuilder::from_cell_output(
CellOutputBuilder::default()
.capacity(capacity_bytes!(100).pack())
.lock(script)
.build(),
Bytes::new(),
)
.transaction_info(mock_transaction_info())
.build();

let rtx = ResolvedTransaction {
transaction,
resolved_cell_deps: vec![dep_cell],
resolved_inputs: vec![input_cell],
resolved_dep_groups: vec![],
};

let provider = MockDataLoader {};
let hardfork_switch = HardForks {
ckb2021: CKB2021::new_mirana().as_builder().build().unwrap(),
ckb2023: CKB2023::new_mirana()
.as_builder()
.rfc_0049(0)
.build()
.unwrap(),
};
let consensus = ConsensusBuilder::default()
.hardfork_switch(hardfork_switch)
.build();
let tx_verify_env =
TxVerifyEnv::new_submit(&HeaderView::new_advanced_builder().epoch(0.pack()).build());
let verifier = TransactionScriptsVerifier::new(
rtx.into(),
provider,
consensus.into(),
tx_verify_env.into(),
);
let _ = verifier.verify(70_000_000);
}

fuzz_target!(|data: &[u8]| {
run(data);
});
3 changes: 0 additions & 3 deletions script/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,9 +490,6 @@ where
.store64(&length_addr, &actual_length)?;
machine.machine.set_register(A0, SUCCESS as u64);
} else {
// TODO: in the previous convention
// https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0009-vm-syscalls/0009-vm-syscalls.md#partial-loading
// this will load data in to address 0 without notice. It is now marked as an error.
machine.machine.set_register(A0, INDEX_OUT_OF_BOUND as u64);
}
continue;
Expand Down
4 changes: 3 additions & 1 deletion script/testdata/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ ALL_BINS := jalr_zero \
spawn_pipe_limits \
spawn_configurable_caller \
spawn_configurable_callee \
spawn_dag
spawn_dag \
spawn_fuzzing

ALL_LIBS := is_even.lib \
add1.lib sub1.lib mul2.lib div2.lib
Expand Down Expand Up @@ -156,3 +157,4 @@ spawn_pipe_limits: spawn_pipe_limits.c
spawn_configurable_caller: spawn_configurable_caller.c spawn_utils.h
spawn_configurable_callee: spawn_configurable_callee.c spawn_utils.h
spawn_dag: spawn_dag.c spawn_dag.h spawn_dag_escape_encoding.h
spawn_fuzzing: spawn_fuzzing.c spawn_utils.h
Binary file added script/testdata/spawn_fuzzing
Binary file not shown.
114 changes: 114 additions & 0 deletions script/testdata/spawn_fuzzing.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "spawn_utils.h"

typedef enum SyscallId { SyscallRead, SyscallWrite, SyscallClose } SyscallId;

typedef struct Command {
SyscallId id;
uint64_t buf_ptr;
uint64_t len_ptr;
size_t fd_index;
} Command;

typedef struct Data {
uint8_t* ptr;
uint64_t offset;
uint64_t total_size;
} Data;

int extract_command(Data* data, Command* cmd) {
if ((data->offset + 1) > data->total_size) {
return -1;
}
uint8_t id = data->ptr[0];

if (id > 250) {
cmd->id = SyscallClose;
cmd->fd_index = (size_t)(id % 2);
data->offset += 1;
} else if (id > 128) {
if ((data->offset + 7) > data->total_size) {
return -1;
}
cmd->id = SyscallRead;
memcpy(&cmd->buf_ptr, &data->ptr[data->offset + 1], 3);
memcpy(&cmd->len_ptr, &data->ptr[data->offset + 4], 3);
data->offset += 7;
} else {
if ((data->offset + 7) > data->total_size) {
return -1;
}
cmd->id = SyscallWrite;
memcpy(&cmd->buf_ptr, &data->ptr[data->offset + 1], 3);
memcpy(&cmd->len_ptr, &data->ptr[data->offset + 4], 3);
data->offset += 7;
}
return 0;
}

int random_read_write(uint64_t fds[2], size_t index) {
int err = 0;
uint8_t cmd_buf[4096] = {0};
uint64_t cmd_len = sizeof(cmd_buf);

err = ckb_load_witness(cmd_buf, &cmd_len, 0, index, CKB_SOURCE_INPUT);
CHECK(err);
Data data = {.ptr = cmd_buf, .total_size = cmd_len, .offset = 0};

while (true) {
Command cmd = {0};
err = extract_command(&data, &cmd);
if (err) break;
if (cmd.id == SyscallRead) {
ckb_read(fds[CKB_STDIN], (void*)cmd.buf_ptr, (uint64_t*)cmd.len_ptr);
// ignore error
} else if (cmd.id == SyscallWrite) {
ckb_write(fds[CKB_STDOUT], (void*)cmd.buf_ptr, (uint64_t*)cmd.len_ptr);
// ignore error
} else if (cmd.id == SyscallClose) {
ckb_close(fds[cmd.fd_index]);
// ignore error
} else {
CHECK2(false, -1);
}
}
exit:
return err;
}

int parent_entry() {
int err = 0;
uint64_t pid = 0;
const char* argv[] = {"", 0};
uint64_t fds[2] = {0};

err = full_spawn(0, 1, argv, fds, &pid);
CHECK(err);
random_read_write(fds, 0);

int8_t exit_code = 0;
err = ckb_wait(pid, &exit_code);
CHECK(err);
CHECK(exit_code);
exit:
return err;
}

int child_entry() {
int err = 0;
uint64_t inherited_fds[2];
size_t inherited_fds_length = 2;
err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length);
CHECK(err);
random_read_write(inherited_fds, 0);

exit:
return err;
}

int main(int argc, const char* argv[]) {
if (argc > 0) {
return child_entry();
} else {
return parent_entry();
}
}

0 comments on commit 8f40da9

Please sign in to comment.