Skip to content

Conversation

vanhauser-thc
Copy link
Contributor

AFL++ received IJON functionality (currently in the dev branch).

This makes functions available that users can use in their C/C++ code to help fuzzing states.
(very impressive, see: https://github.com/RUB-SysSec/ijon/blob/master/img/demo.gif and https://nyx-fuzz.com/papers/ijon.pdf).

I tried to transform all the macros and C functions.

Not sure if this is the way to make them available...

@vanhauser-thc vanhauser-thc marked this pull request as draft September 24, 2025 08:35
@smoelius
Copy link
Member

Thanks, @vanhauser-thc. I'm going to have to read that paper.

Is there a simple test that could be added to verify that the new functions a macros work as intended?

@vanhauser-thc
Copy link
Contributor Author

as long as the afl++ state in afl.rs does not include the feature this could not work :)
I first want to evaluate if this is something you would merge.

@smoelius
Copy link
Member

I first want to evaluate if this is something you would merge.

I will want to read the paper to convince myself this is worthwhile, and there should be a test to verify that this works correctly.

But, beyond that, I can't see any reason to object.

@vanhauser-thc
Copy link
Contributor Author

I added an example (wont work yet), but shows an easy example: finding a way through a maze to an exit.
Any fuzzer will be unable to solve this. With ijon it takes ~30 seconds.

@vanhauser-thc
Copy link
Contributor Author

cd afl
cargo afl build --example maze

finds the way through the maze in just 5-10 seconds:
image

We should wait with merging this until the AFL++ release (in about a week).

NOTE

  1. I renamed libafl-llvm-rt to the correct name of afl-compiler-rt.o ... libafl makes it sound like the sister projects LibAFL.

  2. Why are you using an archive file usually? In C/C++ projects that can result in issues with archive positions vs function resolution, which does not happen with object files. I doubt this is different for rust - so I am curious, why did you using that? I can put that back in, I removed that because it made my testing more difficult.

@smoelius
Copy link
Member

finds the way through the maze in just 5-10 seconds:

Nice!

  1. Why are you using an archive file usually? In C/C++ projects that can result in issues with archive positions vs function resolution, which does not happen with object files.

I don't know the problem you are referring to. Could you provide me a link?

@vanhauser-thc
Copy link
Contributor Author

uh I somehow didn't push the change.

Let me paste it here:

diff --git a/cargo-afl/src/common.rs b/cargo-afl/src/common.rs
index 470f98c..df6eee2 100644
--- a/cargo-afl/src/common.rs
+++ b/cargo-afl/src/common.rs
@@ -52,12 +52,14 @@ pub fn afl_llvm_dir() -> Result<PathBuf> {
 }
 
 pub fn object_file_path() -> Result<PathBuf> {
-    afl_llvm_dir().map(|path| path.join("libafl-llvm-rt.o"))
+    afl_llvm_dir().map(|path| path.join("afl-compilter-rt.o"))
 }
 
+/* //Why is this needed?
 pub fn archive_file_path() -> Result<PathBuf> {
-    afl_llvm_dir().map(|path| path.join("libafl-llvm-rt.a"))
+    afl_llvm_dir().map(|path| path.join("afl-compiler-rt.a"))
 }
+*/
 
 pub fn plugins_available() -> Result<bool> {
     let afl_llvm_dir = afl_llvm_dir()?;
diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs
index 7b849cd..3479a8e 100644
--- a/cargo-afl/src/config.rs
+++ b/cargo-afl/src/config.rs
@@ -38,7 +38,7 @@ pub struct Args {
 
 pub fn config(args: &Args) -> Result<()> {
     let archive_file_path = common::archive_file_path()?;
-    if !args.force && archive_file_path.exists() && args.plugins == common::plugins_available()? {
+    if !args.force && object_file_path.exists() && args.plugins == common::plugins_available()? {
         let version = common::afl_rustc_version()?;
         bail!(
             "AFL LLVM runtime was already built for Rust {version}; run `cargo afl config --build \
@@ -79,7 +79,7 @@ pub fn config(args: &Args) -> Result<()> {
     let work_dir = tempdir.path().join(AFL_SRC_PATH);
 
     build_afl(args, &work_dir)?;
-    build_afl_llvm_runtime(args, &work_dir)?;
+    //build_afl_llvm_runtime(args, &work_dir)?;
 
     if args.plugins {
         copy_afl_llvm_plugins(args, &work_dir)?;
@@ -129,6 +129,7 @@ fn build_afl(args: &Args, work_dir: &Path) -> Result<()> {
     Ok(())
 }
 
+/*
 fn build_afl_llvm_runtime(args: &Args, work_dir: &Path) -> Result<()> {
     let object_file_path = common::object_file_path()?;
     let _: u64 = std::fs::copy(work_dir.join("afl-compiler-rt.o"), &object_file_path)
@@ -151,6 +152,7 @@ fn build_afl_llvm_runtime(args: &Args, work_dir: &Path) -> Result<()> {
 
     Ok(())
 }
+*/
 
 fn copy_afl_llvm_plugins(_args: &Args, work_dir: &Path) -> Result<()> {
     // Iterate over the files in the directory.

@smoelius

This comment was marked as resolved.

@vanhauser-thc

This comment was marked as resolved.

@vanhauser-thc
Copy link
Contributor Author

added a test.

$ cargo test integration_maze

[...]

[*] Fuzzing test case #148 (162 total, 1 crashes saved, state: started :-), mode=explore, perf_score=800, weight=0, favorite=1, was_fuzzed=0, exec_us=28, hits=0, map=152, ascii=0, run_time=0:00:00:03)...


+++ Testing aborted programmatically +++
[*] Statistics: 161 new corpus items found, 0.60% coverage achieved, 1 crashes saved, 0 timeouts saved, total runtime 0 days, 0 hrs, 0 min, 3 sec
[+] We're done here. Have a nice day!

test integration_maze ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 19.72s

@smoelius smoelius mentioned this pull request Sep 26, 2025
@vanhauser-thc vanhauser-thc marked this pull request as ready for review September 28, 2025 11:02
@vanhauser-thc
Copy link
Contributor Author

there are built failures for arm64 I do not understand why they happen. does arm64 need something specific handling?

@smoelius
Copy link
Member

there are built failures for arm64 I do not understand why they happen. does arm64 need something specific handling?

Not that I know of, but that's also not a platform that we normally test. Could you point me to the failures?

@vanhauser-thc
Copy link
Contributor Author

Ah it looks like the CI failures happen when the "have afl++ native plugins" is false - obviously, as ijon requires that, and hence the maze cannot be solved.
I do not know the code base well enough to be able to put the right check into cargo-afl/tests/integration.rs:integration_maze() to only be run when the native plugins are present. could you add this please?

@smoelius
Copy link
Member

could you add this please?

There may be a better way, but 19d50be should work.

@vanhauser-thc vanhauser-thc changed the title WIP: add AFL++ IJON functionality add AFL++ IJON functionality Sep 29, 2025
@vanhauser-thc
Copy link
Contributor Author

I will update the PR once the release commit is done, then it can be merged.

two items:

  1. linter issue:
error: use of `writeln!(stderr(), ...).unwrap()`
  --> cargo-afl/tests/integration.rs:65:9
   |
65 | /         writeln!(
66 | |             std::io::stderr(),
67 | |             "Skipping `integration_maze` test as plugins are unavailable"
68 | |         )
69 | |         .unwrap();
   | |_________________^ help: try: `eprintln!("Skipping `integration_maze` test as plugins are unavailable")`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write
   = note: `-D clippy::explicit-write` implied by `-D warnings`
   = help: to override `-D warnings` add `#[allow(clippy::explicit_write)]`

as this is your code dunno how you want this fixed.

  1. in C, IJON macros are all written in uppercase, e.g. IJON_MAX(foo); IJON_SET(bar);
    do you want this for rust as well? I kept them lowercase, as it seems this is more rust-y, but honestly I do not care either way :-)

@smoelius
Copy link
Member

I will update the PR once the release commit is done, then it can be merged.

two items:

  1. linter issue: ... as this is your code dunno how you want this fixed.

I opened a Clippy issue to see whether they would consider disabling that lint in test code: rust-lang/rust-clippy#15780

But, for now, you can just precede the statement with:

#[allow(clippy::explicit_write)]
  1. in C, IJON macros are all written in uppercase, e.g. IJON_MAX(foo); IJON_SET(bar);
    do you want this for rust as well? I kept them lowercase, as it seems this is more rust-y, but honestly I do not care either way :-)

Please keep it the Rusty way.

Just FYI, I still have to review the PR carefully. I may have additional suggestions.

@vanhauser-thc
Copy link
Contributor Author

One Ubuntu instance is failing, and I notice that it executed the maze twice. the first time it works fine, the second there is not a single coverage item found.
What is different between the two runs?

@smoelius
Copy link
Member

One Ubuntu instance is failing, and I notice that it executed the maze twice.

I'm having trouble spotting the successful run. Could you point me to where in GitHub's output this is indicated?

Can I take it the test passes consistently locally?

@vanhauser-thc
Copy link
Contributor Author

One Ubuntu instance is failing, and I notice that it executed the maze twice.

I'm having trouble spotting the successful run. Could you point me to where in GitHub's output this is indicated?

https://github.com/rust-fuzz/afl.rs/actions/runs/18123061265/job/51571862454?pr=655
Line 486 and before.
In Line 487 it starts another run on the maze that does not work at all.

I don't know what is different between the two runs.

Can I take it the test passes consistently locally?

works without issues for me. and for the CI, except for that one specific test target - see above

@smoelius
Copy link
Member

smoelius commented Sep 30, 2025

I think like 486 is a different test:

#[test]
fn integration() {

EDIT: But to your point, I do wonder if the tests could be interfering with each other somehow.

@vanhauser-thc
Copy link
Contributor Author

then I don't know why it is failing.

Rust / build (ubuntu-latest, nightly, true, clang) (pull_request) Failing after 8m
it succeeded yesterday, and the changes since then have nothing to do with plugins, so I think this is a breaking change in ubuntu-latest.

all other ubuntu and macos variants are succeeding.

@smoelius
Copy link
Member

Is there any chance the feature is non-deterministic in AFL++?

@vanhauser-thc
Copy link
Contributor Author

Fuzzing should not be deterministic :-)

Issue was this:

[*] Fuzzing test case #0 (1 total, 0 crashes saved, state: started :-), mode=explore, perf_score=100, weight=1, favorite=1, was_fuzzed=1, exec_us=45, hits=0, map=8, ascii=0, run_time=0:00:00:10)...

it did not find a single bit of coverage, and that means something went very wrong. and this is not something that happens due bad RNG.

But now the CI is green. so dunno what happened. between the failed CI and the green CI there was only a clippy ignore change, so I can only attribute this to ubuntu-latest/llvm issues.

@vanhauser-thc
Copy link
Contributor Author

new AFL++ version was released, this could now be merged.
If you want changes, I am available the next 24 hours, then I will be AFK for some days.

@smoelius
Copy link
Member

smoelius commented Oct 1, 2025

You probably noticed this, but two ubuntu-latest jobs failed this time.

I would like the tests to pass consistently.

I can think of three possibilities right now:

  • a problem with how afl.rs calls AFL++
  • a problem with the testing infrastructure itself (e.g., fuzz_example)
  • something else

@vanhauser-thc
Copy link
Contributor Author

I tried the newest AFL++ with ubuntu:latest with a Docker container and everything including ijon runs fine.
no clue what the issue could be. would need to debug in their container ...

@vanhauser-thc
Copy link
Contributor Author

done and green

Copy link
Member

@smoelius smoelius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of my comments are nits, but the changes to afl/src/lib.rs will be tedious.

Thanks a lot for doing this.

vanhauser-thc and others added 17 commits October 8, 2025 15:17
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
Co-authored-by: Samuel Moelius <[email protected]>
@vanhauser-thc
Copy link
Contributor Author

no clue what the issue with the CI is.
cargo test integration_maze works fine for me

@smoelius
Copy link
Member

smoelius commented Oct 9, 2025

no clue what the issue with the CI is. cargo test integration_maze works fine for me

Sorry, but doescargo test integration_maze really pass 100% of the time for you? Because when I test it, it does occasionally fail.

Here, for example, is a run where it seems to get stuck at 116 corpus entries. It starts to find its way out, but the time remaining is not sufficient to find a crash: https://github.com/smoelius/afl.rs/actions/runs/18360989062/job/52304402582#step:9:597

Would you consider adapting integration_maze so that it tries three times (say), and so long as a crash is found one of those times, the test is considered a success?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants