Skip to content

Commit db7e323

Browse files
committed
Preliminary support for libfuzzer
1 parent c807e64 commit db7e323

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1613
-584
lines changed

.github/workflows/ci.yml

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,47 @@ jobs:
9797
cargo clean && cargo +nightly udeps --features=test-fuzz/auto_concretize --all-targets
9898
9999
test:
100-
runs-on: ubuntu-latest
101-
102100
strategy:
103101
matrix:
104-
serde_format: [bincode, cbor, cbor4ii]
105-
toolchain: [stable, nightly]
102+
include:
103+
- fuzzer: aflplusplus
104+
serde_format: bincode
105+
environment: ubuntu-latest
106+
toolchain: stable
107+
- fuzzer: aflplusplus
108+
serde_format: cbor
109+
environment: ubuntu-latest
110+
toolchain: nightly
111+
- fuzzer: aflplusplus
112+
serde_format: cbor4ii
113+
environment: macos-latest
114+
toolchain: stable
115+
- fuzzer: aflplusplus-persistent
116+
serde_format: bincode
117+
environment: macos-latest
118+
toolchain: nightly
119+
- fuzzer: aflplusplus-persistent
120+
serde_format: cbor
121+
environment: ubuntu-latest
122+
toolchain: stable
123+
- fuzzer: aflplusplus-persistent
124+
serde_format: cbor4ii
125+
environment: ubuntu-latest
126+
toolchain: nightly
127+
- fuzzer: libfuzzer
128+
serde_format: bincode
129+
environment: macos-latest
130+
toolchain: stable
131+
- fuzzer: libfuzzer
132+
serde_format: cbor
133+
environment: macos-latest
134+
toolchain: nightly
135+
- fuzzer: libfuzzer
136+
serde_format: cbor4ii
137+
environment: ubuntu-latest
138+
toolchain: stable
139+
140+
runs-on: ${{ matrix.environment }}
106141

107142
env:
108143
CARGO_TERM_COLOR: always
@@ -114,6 +149,7 @@ jobs:
114149
run: rustup default ${{ matrix.toolchain }}
115150

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

119155
# smoelius: The Substrate tests require `protoc`.
@@ -137,18 +173,19 @@ jobs:
137173
AUTO_CONCRETIZE=
138174
IGNORED=
139175
SHUFFLE=
140-
if [[ ${{ matrix.toolchain }} = nightly ]]; then
176+
if [[ ${{ matrix.toolchain }} = 'nightly' ]]; then
141177
AUTO_CONCRETIZE='--features=test-fuzz/auto_concretize'
142178
SHUFFLE='-Z unstable-options --shuffle --test-threads=1'
143179
fi
144-
if [[ ${{ github.event_name }} = schedule ]]; then
180+
if [[ ${{ github.event_name }} = 'schedule' ]]; then
145181
IGNORED='--ignored'
146182
fi
147183
cargo test --features=test-fuzz/serde_${{ matrix.serde_format }} "$AUTO_CONCRETIZE" -- --nocapture $IGNORED $SHUFFLE
148184
env:
149185
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1
150186
RUST_BACKTRACE: 1
151187
RUST_LOG: warn
188+
TEST_FUZZ_FUZZER: ${{ matrix.fuzzer }}
152189

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

172209
env:
173210
CARGO_TERM_COLOR: always
174211
RUSTUP_TOOLCHAIN: nightly
212+
TEST_FUZZ_FUZZER: aflplusplus-persistent
175213

176214
steps:
177215
- uses: actions/checkout@v3
@@ -194,6 +232,7 @@ jobs:
194232
env:
195233
CARGO_TERM_COLOR: always
196234
RUSTUP_TOOLCHAIN: nightly
235+
TEST_FUZZ_FUZZER: aflplusplus-persistent
197236

198237
steps:
199238
- uses: actions/checkout@v3

README.md

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -248,50 +248,52 @@ The `cargo test-fuzz` command is used to interact with fuzz targets, and to mani
248248
#### Options
249249

250250
```
251-
--backtrace Display backtraces
252-
--consolidate Move one target's crashes, hangs, and work queue to its corpus; to
253-
consolidate all targets, use --consolidate-all
254-
--display <OBJECT> Display concretizations, corpus, crashes, `impl` concretizations,
255-
hangs, or work queue. By default, corpus uses an uninstrumented fuzz
256-
target; the others use an instrumented fuzz target. To display the
257-
corpus with instrumentation, use --display corpus-instrumented.
258-
[possible values: concretizations, corpus, corpus-instrumented,
259-
crashes, hangs, impl-concretizations, queue]
260-
--exact Target name is an exact name rather than a substring
261-
--exit-code Exit with 0 if the time limit was reached, 1 for other programmatic
262-
aborts, and 2 if an error occurred; implies --no-ui, does not imply
263-
--run-until-crash or -- -V <SECONDS>
264-
--features <FEATURES> Space or comma separated list of features to activate
265-
--list List fuzz targets
266-
--manifest-path <PATH> Path to Cargo.toml
267-
--no-default-features Do not activate the `default` feature
268-
--no-instrumentation Compile without instrumentation (for testing build process)
269-
--no-run Compile, but don't fuzz
270-
--no-ui Disable user interface
271-
-p, --package <PACKAGE> Package containing fuzz target
272-
--persistent Enable persistent mode fuzzing
273-
--pretty-print Pretty-print debug output when displaying/replaying
274-
--replay <OBJECT> Replay corpus, crashes, hangs, or work queue. By default, corpus uses
275-
an uninstrumented fuzz target; the others use an instrumented fuzz
276-
target. To replay the corpus with instrumentation, use --replay
277-
corpus-instrumented. [possible values: concretizations, corpus,
278-
corpus-instrumented, crashes, hangs, impl-concretizations, queue]
279-
--reset Clear fuzzing data for one target, but leave corpus intact; to reset
280-
all targets, use --reset-all
281-
--resume Resume target's last fuzzing session
282-
--run-until-crash Stop fuzzing once a crash is found
283-
--test <NAME> Integration test containing fuzz target
284-
--timeout <TIMEOUT> Number of milliseconds to consider a hang when fuzzing or replaying
285-
(equivalent to -- -t <TIMEOUT> when fuzzing)
286-
--verbose Show build output when displaying/replaying
287-
-h, --help Print help
288-
-V, --version Print version
289-
290-
To fuzz at most <SECONDS> of time, use:
291-
292-
cargo test-fuzz ... -- -V <SECONDS>
293-
294-
Try `cargo afl fuzz --help` to see additional fuzzer options.
251+
--backtrace Display backtraces
252+
--consolidate Move one target's crashes, hangs, and work queue to its corpus; to
253+
consolidate all targets, use --consolidate-all
254+
--display <OBJECT> Display concretizations, corpus, crashes, `impl` concretizations,
255+
hangs, or work queue. By default, corpus uses an uninstrumented
256+
fuzz target; the others use an instrumented fuzz target. To
257+
display the corpus with instrumentation, use --display
258+
corpus-instrumented. [possible values: concretizations, corpus,
259+
corpus-instrumented, crashes, hangs, impl-concretizations, queue]
260+
--exact Target name is an exact name rather than a substring
261+
--exit-code Exit with 0 if the time limit was reached, 1 for other
262+
programmatic aborts, and 2 if an error occurred; implies --no-ui,
263+
does not imply --run-until-crash or --max-total-time <SECONDS>
264+
--features <FEATURES> Space or comma separated list of features to activate
265+
--fuzzer <FUZZER> Fuzz using <FUZZER> [possible values: aflplusplus,
266+
aflplusplus-persistent, libfuzzer]
267+
--list List fuzz targets
268+
--manifest-path <PATH> Path to Cargo.toml
269+
--max-total-time <SECONDS> Fuzz at most <SECONDS> of time (equivalent to -- -V <SECONDS> for
270+
aflplusplus, and -- --max_total_time <SECONDS> for libfuzzer)
271+
--no-default-features Do not activate the `default` feature
272+
--no-instrumentation Compile without instrumentation (for testing build process)
273+
--no-run Compile, but don't fuzz
274+
--no-ui Disable user interface
275+
-p, --package <PACKAGE> Package containing fuzz target
276+
--pretty-print Pretty-print debug output when displaying/replaying
277+
--replay <OBJECT> Replay corpus, crashes, hangs, or work queue. By default, corpus
278+
uses an uninstrumented fuzz target; the others use an instrumented
279+
fuzz target. To replay the corpus with instrumentation, use
280+
--replay corpus-instrumented. [possible values: concretizations,
281+
corpus, corpus-instrumented, crashes, hangs, impl-concretizations,
282+
queue]
283+
--reset Clear fuzzing data for one target, but leave corpus intact; to
284+
reset all targets, use --reset-all
285+
--resume Resume target's last fuzzing session
286+
--run-until-crash Stop fuzzing once a crash is found
287+
--test <NAME> Integration test containing fuzz target
288+
--timeout <TIMEOUT> Number of milliseconds to consider a hang when fuzzing or
289+
replaying (equivalent to -- -t <TIMEOUT> when fuzzing with
290+
aflplusplus, and -- -timeout <TIMEOUT/1000> when fuzzing with
291+
libfuzzer)
292+
--verbose Show build output when displaying/replaying
293+
-h, --help Print help
294+
-V, --version Print version
295+
296+
Try `cargo afl fuzz --help` to see additional AFLplusplus options.
295297
```
296298

297299
### Convenience functions and macros

cargo-test-fuzz/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "cargo-test-fuzz"
33
version = "3.0.5"
4-
edition = "2018"
4+
edition = "2021"
55

66
description = "cargo-test-fuzz"
77

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

1919
[dependencies]
20-
anyhow = "1.0"
20+
anyhow = { version = "1.0", features = ["backtrace"] }
2121
bitflags = "2.1"
22+
cargo-fuzz = { git = "https://github.com/trail-of-forks/cargo-fuzz", features = ["no-manifest-check"] }
2223
cargo_metadata = "0.15"
2324
clap = { version = "4.2", features = ["cargo", "derive", "wrap_help"] }
2425
env_logger = "0.10"
26+
fs_extra = "1.3"
2527
heck = "0.4"
2628
lazy_static = "1.4"
2729
log = "0.4"
30+
once_cell = "1.16"
2831
paste = "1.0"
2932
remain = "0.2"
3033
semver = "1.0"
3134
serde = { version = "1.0", features = ["derive"] }
35+
serde_json = "1.0"
3236
strum_macros = "0.24"
3337
subprocess = "0.2"
3438

cargo-test-fuzz/patches/solana.patch

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,41 @@ index 7f00f43..38eacf7 100644
2121
pub struct ComputeBudget {
2222
/// Number of compute units that a transaction or individual instruction is
2323
diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs
24-
index 9a12416..decd9ca 100644
24+
index cc842dd..ca63985 100644
2525
--- a/program-runtime/src/invoke_context.rs
2626
+++ b/program-runtime/src/invoke_context.rs
27-
@@ -123,12 +123,29 @@ impl fmt::Display for AllocErr {
27+
@@ -118,4 +118,5 @@ impl fmt::Display for AllocErr {
2828
}
2929

30-
+struct DummyAllocator;
31-
+
32-
+impl Alloc for DummyAllocator {
33-
+ fn alloc(&mut self, _layout: Layout) -> Result<u64, AllocErr> {
34-
+ std::process::exit(0);
35-
+ }
36-
+ fn dealloc(&mut self, _addr: u64, _layout: Layout) {
37-
+ std::process::exit(0);
38-
+ }
39-
+}
40-
+
41-
+fn dummy_allocator() -> Rc<RefCell<dyn Alloc>> {
42-
+ Rc::new(RefCell::new(DummyAllocator))
43-
+}
44-
+
4530
+#[derive(Clone, serde::Deserialize, serde::Serialize)]
46-
struct SyscallContext {
47-
check_aligned: bool,
48-
check_size: bool,
49-
orig_account_lengths: Vec<usize>,
50-
+ #[serde(skip, default = "dummy_allocator")]
51-
allocator: Rc<RefCell<dyn Alloc>>,
31+
pub struct BpfAllocator {
32+
len: u64,
33+
@@ -146,4 +147,5 @@ impl BpfAllocator {
5234
}
5335

54-
-#[derive(Default)]
55-
+#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
56-
pub struct TraceLogStackFrame {
57-
pub trace_log: Vec<[u64; 12]>,
58-
@@ -136,8 +153,35 @@ pub struct TraceLogStackFrame {
36+
+#[derive(Clone, serde::Deserialize, serde::Serialize)]
37+
pub struct SyscallContext {
38+
pub allocator: BpfAllocator,
39+
@@ -152,9 +154,54 @@ pub struct SyscallContext {
5940
}
6041

42+
+pub fn serialize_ref<S, T>(x: &&T, serializer: S) -> Result<S::Ok, S::Error>
43+
+where
44+
+ S: serde::Serializer,
45+
+ T: serde::Serialize,
46+
+{
47+
+ <T as serde::Serialize>::serialize(*x, serializer)
48+
+}
49+
+
50+
+pub fn deserialize_ref<'de, D, T>(deserializer: D) -> Result<&'static T, D::Error>
51+
+where
52+
+ D: serde::Deserializer<'de>,
53+
+ T: serde::de::DeserializeOwned + std::fmt::Debug,
54+
+{
55+
+ let x = <T as serde::de::Deserialize>::deserialize(deserializer)?;
56+
+ Ok(Box::leak(Box::new(x)))
57+
+}
58+
+
6159
+pub fn serialize_ref_mut<S, T>(x: &&mut T, serializer: S) -> Result<S::Ok, S::Error>
6260
+where
6361
+ S: serde::Serializer,
@@ -90,17 +88,23 @@ index 9a12416..decd9ca 100644
9088
pre_accounts: Vec<PreAccount>,
9189
+ #[serde(skip, default = "default_builtin_programs")]
9290
builtin_programs: &'a [BuiltinProgram],
91+
+ #[serde(serialize_with = "serialize_ref", deserialize_with = "deserialize_ref")]
9392
sysvar_cache: &'a SysvarCache,
94-
@@ -157,4 +201,22 @@ pub struct InvokeContext<'a> {
93+
log_collector: Option<Rc<RefCell<LogCollector>>>,
94+
@@ -163,4 +210,5 @@ pub struct InvokeContext<'a> {
95+
compute_meter: RefCell<u64>,
96+
accounts_data_meter: AccountsDataMeter,
97+
+ #[serde(skip)]
98+
pub tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
99+
pub feature_set: Arc<FeatureSet>,
100+
@@ -171,4 +219,20 @@ pub struct InvokeContext<'a> {
95101
}
96102

97103
+impl<'a> Clone for InvokeContext<'a> {
98104
+ fn clone(&self) -> Self {
99105
+ Self {
100106
+ transaction_context: Box::leak(Box::new(self.transaction_context.clone())),
101107
+ pre_accounts: self.pre_accounts.clone(),
102-
+ sysvar_cache: self.sysvar_cache.clone(),
103-
+ trace_log_stack: self.trace_log_stack.clone(),
104108
+ log_collector: self.log_collector.clone(),
105109
+ compute_meter: self.compute_meter.clone(),
106110
+ tx_executor_cache: self.tx_executor_cache.clone(),
@@ -177,10 +181,10 @@ index 79de085..39f0384 100644
177181
[dev-dependencies]
178182
memoffset = "0.8"
179183
diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs
180-
index 65546a1..6c64602 100644
184+
index db06f2c..5e5bab4 100644
181185
--- a/programs/bpf_loader/src/lib.rs
182186
+++ b/programs/bpf_loader/src/lib.rs
183-
@@ -423,6 +423,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
187+
@@ -438,6 +438,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
184188
}
185189

186190
-pub fn process_instruction(
@@ -209,10 +213,10 @@ index 7e8368d..56d903d 100644
209213
[target.'cfg(target_arch = "wasm32")'.dependencies]
210214
js-sys = { workspace = true }
211215
diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs
212-
index 5437320..3342b47 100644
216+
index 7a85b12..339d328 100644
213217
--- a/sdk/src/feature_set.rs
214218
+++ b/sdk/src/feature_set.rs
215-
@@ -846,5 +846,5 @@ lazy_static! {
219+
@@ -851,5 +851,5 @@ lazy_static! {
216220

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

0 commit comments

Comments
 (0)