Skip to content

Commit

Permalink
Preliminary support for libfuzzer
Browse files Browse the repository at this point in the history
  • Loading branch information
smoelius committed Apr 19, 2023
1 parent c807e64 commit db7e323
Show file tree
Hide file tree
Showing 58 changed files with 1,613 additions and 584 deletions.
53 changes: 46 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,47 @@ jobs:
cargo clean && cargo +nightly udeps --features=test-fuzz/auto_concretize --all-targets
test:
runs-on: ubuntu-latest

strategy:
matrix:
serde_format: [bincode, cbor, cbor4ii]
toolchain: [stable, nightly]
include:
- fuzzer: aflplusplus
serde_format: bincode
environment: ubuntu-latest
toolchain: stable
- fuzzer: aflplusplus
serde_format: cbor
environment: ubuntu-latest
toolchain: nightly
- fuzzer: aflplusplus
serde_format: cbor4ii
environment: macos-latest
toolchain: stable
- fuzzer: aflplusplus-persistent
serde_format: bincode
environment: macos-latest
toolchain: nightly
- fuzzer: aflplusplus-persistent
serde_format: cbor
environment: ubuntu-latest
toolchain: stable
- fuzzer: aflplusplus-persistent
serde_format: cbor4ii
environment: ubuntu-latest
toolchain: nightly
- fuzzer: libfuzzer
serde_format: bincode
environment: macos-latest
toolchain: stable
- fuzzer: libfuzzer
serde_format: cbor
environment: macos-latest
toolchain: nightly
- fuzzer: libfuzzer
serde_format: cbor4ii
environment: ubuntu-latest
toolchain: stable

runs-on: ${{ matrix.environment }}

env:
CARGO_TERM_COLOR: always
Expand All @@ -114,6 +149,7 @@ jobs:
run: rustup default ${{ matrix.toolchain }}

- name: Install llvm
if: ${{ matrix.environment == 'ubuntu-latest' }}
run: sudo apt-get install llvm

# smoelius: The Substrate tests require `protoc`.
Expand All @@ -137,18 +173,19 @@ jobs:
AUTO_CONCRETIZE=
IGNORED=
SHUFFLE=
if [[ ${{ matrix.toolchain }} = nightly ]]; then
if [[ ${{ matrix.toolchain }} = 'nightly' ]]; then
AUTO_CONCRETIZE='--features=test-fuzz/auto_concretize'
SHUFFLE='-Z unstable-options --shuffle --test-threads=1'
fi
if [[ ${{ github.event_name }} = schedule ]]; then
if [[ ${{ github.event_name }} = 'schedule' ]]; then
IGNORED='--ignored'
fi
cargo test --features=test-fuzz/serde_${{ matrix.serde_format }} "$AUTO_CONCRETIZE" -- --nocapture $IGNORED $SHUFFLE
env:
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1
RUST_BACKTRACE: 1
RUST_LOG: warn
TEST_FUZZ_FUZZER: ${{ matrix.fuzzer }}

test-uninstalled-cargo-afl:
runs-on: ubuntu-latest
Expand All @@ -164,14 +201,15 @@ jobs:
run: |
OUTPUT="$(cargo run -p cargo-test-fuzz -- test-fuzz -p test-fuzz-examples --no-run 2>&1 1>/dev/null || true)"
echo "$OUTPUT"
echo "$OUTPUT" | grep '^Error: Could not determine `cargo-afl` version. Is it installed? Try `cargo install afl`.$'
echo "$OUTPUT" | grep 'Could not determine `cargo-afl` version. Is it installed? Try `cargo install afl`.'
test-incompatible-cargo-afl:
runs-on: ubuntu-latest

env:
CARGO_TERM_COLOR: always
RUSTUP_TOOLCHAIN: nightly
TEST_FUZZ_FUZZER: aflplusplus-persistent

steps:
- uses: actions/checkout@v3
Expand All @@ -194,6 +232,7 @@ jobs:
env:
CARGO_TERM_COLOR: always
RUSTUP_TOOLCHAIN: nightly
TEST_FUZZ_FUZZER: aflplusplus-persistent

steps:
- uses: actions/checkout@v3
Expand Down
90 changes: 46 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,50 +248,52 @@ The `cargo test-fuzz` command is used to interact with fuzz targets, and to mani
#### Options

```
--backtrace Display backtraces
--consolidate Move one target's crashes, hangs, and work queue to its corpus; to
consolidate all targets, use --consolidate-all
--display <OBJECT> Display concretizations, corpus, crashes, `impl` concretizations,
hangs, or work queue. By default, corpus uses an uninstrumented fuzz
target; the others use an instrumented fuzz target. To display the
corpus with instrumentation, use --display corpus-instrumented.
[possible values: concretizations, corpus, corpus-instrumented,
crashes, hangs, impl-concretizations, queue]
--exact Target name is an exact name rather than a substring
--exit-code Exit with 0 if the time limit was reached, 1 for other programmatic
aborts, and 2 if an error occurred; implies --no-ui, does not imply
--run-until-crash or -- -V <SECONDS>
--features <FEATURES> Space or comma separated list of features to activate
--list List fuzz targets
--manifest-path <PATH> Path to Cargo.toml
--no-default-features Do not activate the `default` feature
--no-instrumentation Compile without instrumentation (for testing build process)
--no-run Compile, but don't fuzz
--no-ui Disable user interface
-p, --package <PACKAGE> Package containing fuzz target
--persistent Enable persistent mode fuzzing
--pretty-print Pretty-print debug output when displaying/replaying
--replay <OBJECT> Replay corpus, crashes, hangs, or work queue. By default, corpus uses
an uninstrumented fuzz target; the others use an instrumented fuzz
target. To replay the corpus with instrumentation, use --replay
corpus-instrumented. [possible values: concretizations, corpus,
corpus-instrumented, crashes, hangs, impl-concretizations, queue]
--reset Clear fuzzing data for one target, but leave corpus intact; to reset
all targets, use --reset-all
--resume Resume target's last fuzzing session
--run-until-crash Stop fuzzing once a crash is found
--test <NAME> Integration test containing fuzz target
--timeout <TIMEOUT> Number of milliseconds to consider a hang when fuzzing or replaying
(equivalent to -- -t <TIMEOUT> when fuzzing)
--verbose Show build output when displaying/replaying
-h, --help Print help
-V, --version Print version
To fuzz at most <SECONDS> of time, use:
cargo test-fuzz ... -- -V <SECONDS>
Try `cargo afl fuzz --help` to see additional fuzzer options.
--backtrace Display backtraces
--consolidate Move one target's crashes, hangs, and work queue to its corpus; to
consolidate all targets, use --consolidate-all
--display <OBJECT> Display concretizations, corpus, crashes, `impl` concretizations,
hangs, or work queue. By default, corpus uses an uninstrumented
fuzz target; the others use an instrumented fuzz target. To
display the corpus with instrumentation, use --display
corpus-instrumented. [possible values: concretizations, corpus,
corpus-instrumented, crashes, hangs, impl-concretizations, queue]
--exact Target name is an exact name rather than a substring
--exit-code Exit with 0 if the time limit was reached, 1 for other
programmatic aborts, and 2 if an error occurred; implies --no-ui,
does not imply --run-until-crash or --max-total-time <SECONDS>
--features <FEATURES> Space or comma separated list of features to activate
--fuzzer <FUZZER> Fuzz using <FUZZER> [possible values: aflplusplus,
aflplusplus-persistent, libfuzzer]
--list List fuzz targets
--manifest-path <PATH> Path to Cargo.toml
--max-total-time <SECONDS> Fuzz at most <SECONDS> of time (equivalent to -- -V <SECONDS> for
aflplusplus, and -- --max_total_time <SECONDS> for libfuzzer)
--no-default-features Do not activate the `default` feature
--no-instrumentation Compile without instrumentation (for testing build process)
--no-run Compile, but don't fuzz
--no-ui Disable user interface
-p, --package <PACKAGE> Package containing fuzz target
--pretty-print Pretty-print debug output when displaying/replaying
--replay <OBJECT> Replay corpus, crashes, hangs, or work queue. By default, corpus
uses an uninstrumented fuzz target; the others use an instrumented
fuzz target. To replay the corpus with instrumentation, use
--replay corpus-instrumented. [possible values: concretizations,
corpus, corpus-instrumented, crashes, hangs, impl-concretizations,
queue]
--reset Clear fuzzing data for one target, but leave corpus intact; to
reset all targets, use --reset-all
--resume Resume target's last fuzzing session
--run-until-crash Stop fuzzing once a crash is found
--test <NAME> Integration test containing fuzz target
--timeout <TIMEOUT> Number of milliseconds to consider a hang when fuzzing or
replaying (equivalent to -- -t <TIMEOUT> when fuzzing with
aflplusplus, and -- -timeout <TIMEOUT/1000> when fuzzing with
libfuzzer)
--verbose Show build output when displaying/replaying
-h, --help Print help
-V, --version Print version
Try `cargo afl fuzz --help` to see additional AFLplusplus options.
```

### Convenience functions and macros
Expand Down
8 changes: 6 additions & 2 deletions cargo-test-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cargo-test-fuzz"
version = "3.0.5"
edition = "2018"
edition = "2021"

description = "cargo-test-fuzz"

Expand All @@ -17,18 +17,22 @@ path = "src/bin/cargo_test_fuzz.rs"
doctest = false

[dependencies]
anyhow = "1.0"
anyhow = { version = "1.0", features = ["backtrace"] }
bitflags = "2.1"
cargo-fuzz = { git = "https://github.com/trail-of-forks/cargo-fuzz", features = ["no-manifest-check"] }
cargo_metadata = "0.15"
clap = { version = "4.2", features = ["cargo", "derive", "wrap_help"] }
env_logger = "0.10"
fs_extra = "1.3"
heck = "0.4"
lazy_static = "1.4"
log = "0.4"
once_cell = "1.16"
paste = "1.0"
remain = "0.2"
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum_macros = "0.24"
subprocess = "0.2"

Expand Down
74 changes: 39 additions & 35 deletions cargo-test-fuzz/patches/solana.patch
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,41 @@ index 7f00f43..38eacf7 100644
pub struct ComputeBudget {
/// Number of compute units that a transaction or individual instruction is
diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs
index 9a12416..decd9ca 100644
index cc842dd..ca63985 100644
--- a/program-runtime/src/invoke_context.rs
+++ b/program-runtime/src/invoke_context.rs
@@ -123,12 +123,29 @@ impl fmt::Display for AllocErr {
@@ -118,4 +118,5 @@ impl fmt::Display for AllocErr {
}

+struct DummyAllocator;
+
+impl Alloc for DummyAllocator {
+ fn alloc(&mut self, _layout: Layout) -> Result<u64, AllocErr> {
+ std::process::exit(0);
+ }
+ fn dealloc(&mut self, _addr: u64, _layout: Layout) {
+ std::process::exit(0);
+ }
+}
+
+fn dummy_allocator() -> Rc<RefCell<dyn Alloc>> {
+ Rc::new(RefCell::new(DummyAllocator))
+}
+
+#[derive(Clone, serde::Deserialize, serde::Serialize)]
struct SyscallContext {
check_aligned: bool,
check_size: bool,
orig_account_lengths: Vec<usize>,
+ #[serde(skip, default = "dummy_allocator")]
allocator: Rc<RefCell<dyn Alloc>>,
pub struct BpfAllocator {
len: u64,
@@ -146,4 +147,5 @@ impl BpfAllocator {
}

-#[derive(Default)]
+#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
pub struct TraceLogStackFrame {
pub trace_log: Vec<[u64; 12]>,
@@ -136,8 +153,35 @@ pub struct TraceLogStackFrame {
+#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct SyscallContext {
pub allocator: BpfAllocator,
@@ -152,9 +154,54 @@ pub struct SyscallContext {
}

+pub fn serialize_ref<S, T>(x: &&T, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: serde::Serializer,
+ T: serde::Serialize,
+{
+ <T as serde::Serialize>::serialize(*x, serializer)
+}
+
+pub fn deserialize_ref<'de, D, T>(deserializer: D) -> Result<&'static T, D::Error>
+where
+ D: serde::Deserializer<'de>,
+ T: serde::de::DeserializeOwned + std::fmt::Debug,
+{
+ let x = <T as serde::de::Deserialize>::deserialize(deserializer)?;
+ Ok(Box::leak(Box::new(x)))
+}
+
+pub fn serialize_ref_mut<S, T>(x: &&mut T, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: serde::Serializer,
Expand Down Expand Up @@ -90,17 +88,23 @@ index 9a12416..decd9ca 100644
pre_accounts: Vec<PreAccount>,
+ #[serde(skip, default = "default_builtin_programs")]
builtin_programs: &'a [BuiltinProgram],
+ #[serde(serialize_with = "serialize_ref", deserialize_with = "deserialize_ref")]
sysvar_cache: &'a SysvarCache,
@@ -157,4 +201,22 @@ pub struct InvokeContext<'a> {
log_collector: Option<Rc<RefCell<LogCollector>>>,
@@ -163,4 +210,5 @@ pub struct InvokeContext<'a> {
compute_meter: RefCell<u64>,
accounts_data_meter: AccountsDataMeter,
+ #[serde(skip)]
pub tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
pub feature_set: Arc<FeatureSet>,
@@ -171,4 +219,20 @@ pub struct InvokeContext<'a> {
}

+impl<'a> Clone for InvokeContext<'a> {
+ fn clone(&self) -> Self {
+ Self {
+ transaction_context: Box::leak(Box::new(self.transaction_context.clone())),
+ pre_accounts: self.pre_accounts.clone(),
+ sysvar_cache: self.sysvar_cache.clone(),
+ trace_log_stack: self.trace_log_stack.clone(),
+ log_collector: self.log_collector.clone(),
+ compute_meter: self.compute_meter.clone(),
+ tx_executor_cache: self.tx_executor_cache.clone(),
Expand Down Expand Up @@ -177,10 +181,10 @@ index 79de085..39f0384 100644
[dev-dependencies]
memoffset = "0.8"
diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs
index 65546a1..6c64602 100644
index db06f2c..5e5bab4 100644
--- a/programs/bpf_loader/src/lib.rs
+++ b/programs/bpf_loader/src/lib.rs
@@ -423,6 +423,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
@@ -438,6 +438,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
}

-pub fn process_instruction(
Expand Down Expand Up @@ -209,10 +213,10 @@ index 7e8368d..56d903d 100644
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = { workspace = true }
diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs
index 5437320..3342b47 100644
index 7a85b12..339d328 100644
--- a/sdk/src/feature_set.rs
+++ b/sdk/src/feature_set.rs
@@ -846,5 +846,5 @@ lazy_static! {
@@ -851,5 +851,5 @@ lazy_static! {

/// `FeatureSet` holds the set of currently active/inactive runtime features
-#[derive(AbiExample, Debug, Clone, Eq, PartialEq)]
Expand Down
Loading

0 comments on commit db7e323

Please sign in to comment.