From b87ef2ec296adaf14a175be1e5bb454aff4cae27 Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Tue, 5 May 2026 15:22:02 -0400 Subject: [PATCH 1/6] [libafl_cc] Fix PassPlugin LLVM header location `PassPlugin.h` was moved from `Passes/` to `Plugins/` in LLVM commit: `f54df0d09e19 ([LLVM][NFC] Move PassPlugin from Passes to separate library, 2025-12-22)` (First tagged in LLVM 22 releases) --- crates/libafl_cc/src/common-llvm.h | 6 +++++- crates/libafl_cc/src/function-logging.cc | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/libafl_cc/src/common-llvm.h b/crates/libafl_cc/src/common-llvm.h index 97229d9df93..c3edd6b414a 100644 --- a/crates/libafl_cc/src/common-llvm.h +++ b/crates/libafl_cc/src/common-llvm.h @@ -25,7 +25,11 @@ constexpr std::nullopt_t None = std::nullopt; #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Verifier.h" #include "llvm/IR/CFG.h" -#include "llvm/Passes/PassPlugin.h" +#if LLVM_VERSION_MAJOR >= 22 + #include "llvm/Plugins/PassPlugin.h" +#else + #include "llvm/Passes/PassPlugin.h" +#endif #include "llvm/Passes/PassBuilder.h" #include "llvm/IR/PassManager.h" diff --git a/crates/libafl_cc/src/function-logging.cc b/crates/libafl_cc/src/function-logging.cc index 28da42cbe52..b51b30e83a3 100644 --- a/crates/libafl_cc/src/function-logging.cc +++ b/crates/libafl_cc/src/function-logging.cc @@ -38,7 +38,11 @@ #include "llvm/ADT/Statistic.h" #include "llvm/IR/IRBuilder.h" -#include "llvm/Passes/PassPlugin.h" +#if LLVM_VERSION_MAJOR >= 22 + #include "llvm/Plugins/PassPlugin.h" +#else + #include "llvm/Passes/PassPlugin.h" +#endif #include "llvm/Passes/PassBuilder.h" #include "llvm/IR/PassManager.h" From b36fbf152b18a289ab5d61cc5edf88bad3d91ed8 Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Tue, 5 May 2026 15:28:20 -0400 Subject: [PATCH 2/6] [libafl_cc] Fix for LLVM opaque pointers in passes LLVM began introducing opaque pointers (pointer types with purposefully undefined pointee types) around LLVM 13[1] like in commit: `2155dc51d700 ([IR] Introduce the opaque pointer type, 2021-05-01)` introducing the function: `PointerType::get(LLVMContext &C, unsigned AddressSpace)` and had wholesale switched to only opaque pointers by LLVM 17[2]. Part of that effort deprecates many functions, including: `PointerType::get(Type *ElementType, unsigned AddressSpace)` though this function was depreacted much later in LLVM 21 in commit: `146ad71bc71a ([IR] Deprecate PointerType::get/getUnqual pointee type overload (#134517), 2025-04-07)` Switch libafl_cc's passes to the new function to avoid deprecation warnings [1]: https://releases.llvm.org/13.0.1/docs/ReleaseNotes.html#changes-to-the-llvm-ir [2]: https://releases.llvm.org/22.1.0/docs/OpaquePointers.html#version-support --- crates/libafl_cc/src/autotokens-pass.cc | 12 ++++-------- crates/libafl_cc/src/cmplog-instructions-pass.cc | 2 +- crates/libafl_cc/src/cmplog-routines-pass.cc | 8 +++----- crates/libafl_cc/src/coverage-accounting-pass.cc | 6 +++--- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/libafl_cc/src/autotokens-pass.cc b/crates/libafl_cc/src/autotokens-pass.cc index a343531a423..eb9cd471106 100644 --- a/crates/libafl_cc/src/autotokens-pass.cc +++ b/crates/libafl_cc/src/autotokens-pass.cc @@ -299,13 +299,11 @@ PreservedAnalyses AutoTokensPass::run(Module &M, ModuleAnalysisManager &MAM) { isStrcmp &= FT->getNumParams() == 2 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0) == FT->getParamType(1) && - FT->getParamType(0) == - IntegerType::getInt8Ty(M.getContext())->getPointerTo(0); + FT->getParamType(0) == PointerType::get(M.getContext(), 0); isStrcasecmp &= FT->getNumParams() == 2 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0) == FT->getParamType(1) && - FT->getParamType(0) == - IntegerType::getInt8Ty(M.getContext())->getPointerTo(0); + FT->getParamType(0) == PointerType::get(M.getContext(), 0); isMemcmp &= FT->getNumParams() == 3 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0)->isPointerTy() && @@ -314,14 +312,12 @@ PreservedAnalyses AutoTokensPass::run(Module &M, ModuleAnalysisManager &MAM) { isStrncmp &= FT->getNumParams() == 3 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0) == FT->getParamType(1) && - FT->getParamType(0) == - IntegerType::getInt8Ty(M.getContext())->getPointerTo(0) && + FT->getParamType(0) == PointerType::get(M.getContext(), 0) && FT->getParamType(2)->isIntegerTy(); isStrncasecmp &= FT->getNumParams() == 3 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0) == FT->getParamType(1) && - FT->getParamType(0) == - IntegerType::getInt8Ty(M.getContext())->getPointerTo(0) && + FT->getParamType(0) == PointerType::get(M.getContext(), 0) && FT->getParamType(2)->isIntegerTy(); isStdString &= FT->getNumParams() >= 2 && FT->getParamType(0)->isPointerTy() && diff --git a/crates/libafl_cc/src/cmplog-instructions-pass.cc b/crates/libafl_cc/src/cmplog-instructions-pass.cc index a4a0fe42b9f..d27d8570cc7 100644 --- a/crates/libafl_cc/src/cmplog-instructions-pass.cc +++ b/crates/libafl_cc/src/cmplog-instructions-pass.cc @@ -158,7 +158,7 @@ bool CmpLogInstructions::hookInstrs(Module &M) { } #endif - Constant *Null = Constant::getNullValue(PointerType::get(Int8Ty, 0)); + Constant *Null = Constant::getNullValue(PointerType::get(M.getContext(), 0)); /* iterate over all functions, bbs and instruction and add suitable calls */ for (auto &F : M) { diff --git a/crates/libafl_cc/src/cmplog-routines-pass.cc b/crates/libafl_cc/src/cmplog-routines-pass.cc index 52b67b932e2..baf55c7d4bf 100644 --- a/crates/libafl_cc/src/cmplog-routines-pass.cc +++ b/crates/libafl_cc/src/cmplog-routines-pass.cc @@ -75,7 +75,7 @@ bool CmpLogRoutines::hookRtns(Module &M) { IntegerType *Int8Ty = IntegerType::getInt8Ty(C); IntegerType *Int64Ty = IntegerType::getInt64Ty(C); IntegerType *Int32Ty = IntegerType::getInt32Ty(C); - PointerType *i8PtrTy = PointerType::get(Int8Ty, 0); + PointerType *i8PtrTy = PointerType::get(M.getContext(), 0); FunctionCallee cmplogHookFn; FunctionCallee cmplogLlvmStdStd; @@ -229,8 +229,7 @@ bool CmpLogRoutines::hookRtns(Module &M) { isStrcmp &= FT->getNumParams() == 2 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0) == FT->getParamType(1) && - FT->getParamType(0) == - IntegerType::getInt8Ty(M.getContext())->getPointerTo(0); + FT->getParamType(0) == PointerType::get(M.getContext(), 0); bool isStrncmp = (!FuncName.compare("strncmp") || !FuncName.compare("xmlStrncmp") || @@ -246,8 +245,7 @@ bool CmpLogRoutines::hookRtns(Module &M) { isStrncmp &= FT->getNumParams() == 3 && FT->getReturnType()->isIntegerTy(32) && FT->getParamType(0) == FT->getParamType(1) && - FT->getParamType(0) == - IntegerType::getInt8Ty(M.getContext())->getPointerTo(0) && + FT->getParamType(0) == PointerType::get(M.getContext(), 0) && FT->getParamType(2)->isIntegerTy(); bool isGccStdStringStdString = diff --git a/crates/libafl_cc/src/coverage-accounting-pass.cc b/crates/libafl_cc/src/coverage-accounting-pass.cc index 6dfa1b459dc..f0786acbe6d 100644 --- a/crates/libafl_cc/src/coverage-accounting-pass.cc +++ b/crates/libafl_cc/src/coverage-accounting-pass.cc @@ -232,8 +232,8 @@ PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) { __afl_acc_prev_loc is thread-local. */ GlobalVariable *AFLMemOpPtr = new GlobalVariable( - M, PointerType::get(Int32Ty, 0), false, GlobalValue::ExternalLinkage, 0, - "__afl_acc_memop_ptr"); + M, PointerType::get(M.getContext(), 0), false, + GlobalValue::ExternalLinkage, 0, "__afl_acc_memop_ptr"); GlobalVariable *AFLPrevLoc; @@ -301,7 +301,7 @@ PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) { /* Load SHM pointer */ LoadInst *MemReadPtr = - IRB.CreateLoad(PointerType::get(Int32Ty, 0), AFLMemOpPtr); + IRB.CreateLoad(PointerType::get(M.getContext(), 0), AFLMemOpPtr); MemReadPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); Value *MemReadPtrIdx = From bafa7c76a83d9649db5b0fc999b346402014eaf9 Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Tue, 5 May 2026 16:07:57 -0400 Subject: [PATCH 3/6] [libafl_cc] Fix for changes to ArrayRef construct Constructing an ArrayRef from a nullopt was deprecated in LLVM commit: `2529de5c935a ([ADT] Deprecate ArrayRef(std::nullopt) (#146011), 2025-06-27)` (First tagged in LLVM 21) Then removed in LLVM commit: `cfbb4cc31215 ([ADT] Remove ArrayRef(std::nullopt_t) (#165831), 2025-10-31)` (First tagged in LLVM 22) The LLVM authors recommend switching to the `{}` C++ syntax, so introduce that conditionally if and only if the LLVM version is greater than or equal to 21 and fall back to the previous nullopt workaround, otherwise --- crates/libafl_cc/src/common-llvm.h | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/libafl_cc/src/common-llvm.h b/crates/libafl_cc/src/common-llvm.h index c3edd6b414a..fc22ac1e6f3 100644 --- a/crates/libafl_cc/src/common-llvm.h +++ b/crates/libafl_cc/src/common-llvm.h @@ -10,11 +10,6 @@ #define HAVE_VECTOR_INTRINSICS 1 #include -#if LLVM_VERSION_MAJOR >= 16 -// None constant being deprecated for LLVM-16, it is recommended -// to use the std::nullopt_t type instead. (#1010) -constexpr std::nullopt_t None = std::nullopt; -#endif // all llvm includes and friends #include "llvm/Support/CommandLine.h" @@ -33,6 +28,23 @@ constexpr std::nullopt_t None = std::nullopt; #include "llvm/Passes/PassBuilder.h" #include "llvm/IR/PassManager.h" +#if LLVM_VERSION_MAJOR >= 21 + // None is only used in libafl_cc as the second parameter of MDNode::get() + // that param is an ArrayRef, which can no longer be constructed with a + // nullopt since LLVM 21+ + // + // Keep these around to keep IWYU quiet + #include "llvm/ADT/ArrayRef.h" +namespace llvm { +class Metadata; +} +inline constexpr llvm::ArrayRef None{}; +#elif LLVM_VERSION_MAJOR >= 16 +// None constant being deprecated for LLVM-16, it is recommended +// to use the std::nullopt_t type instead. (#1010) +constexpr std::nullopt_t None = std::nullopt; +#endif + #define FATAL(...) \ do { \ fprintf(stderr, "FATAL: " __VA_ARGS__); \ From 36f7135a4e7b12749368cc719f14df264b507477 Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Tue, 5 May 2026 16:13:34 -0400 Subject: [PATCH 4/6] [libafl_cc] Remove duplicate FATAL preproc macro `FATAL` is defined in the `common-llvm.h` and is therefore accessible to all of the passes, remove the duplicate in the pass `.cc` file to remove the compiler warning --- crates/libafl_cc/src/dump-cfg-pass.cc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/libafl_cc/src/dump-cfg-pass.cc b/crates/libafl_cc/src/dump-cfg-pass.cc index 9903515ef5c..815b166f598 100644 --- a/crates/libafl_cc/src/dump-cfg-pass.cc +++ b/crates/libafl_cc/src/dump-cfg-pass.cc @@ -38,13 +38,6 @@ #include -#define FATAL(x...) \ - do { \ - fprintf(stderr, "FATAL: " x); \ - exit(1); \ - \ - } while (0) - using namespace llvm; namespace { From c4519debcc95411a71d8c24e3972c91f97229963 Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Wed, 13 May 2026 22:08:19 -0400 Subject: [PATCH 5/6] [CI] move LLVM to 22 Address comments made in commit: `2a92a831 (Fix CI on MacOS (#3813), 2026-05-13)` --- .github/workflows/build_and_test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index fd98cd89d80..d04769b3dfd 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -14,8 +14,7 @@ env: AFL_PIZZA_MODE: "-1" # this is sad, I know, but it breaks on a certain spring day otherwise :( CARGO_TERM_COLOR: always CARGO_NET_GIT_FETCH_WITH_CLI: true - # FIXME: please unpin macos rust stable toolchain once LLVM is bumped to version 22 - MAIN_LLVM_VERSION: 21 + MAIN_LLVM_VERSION: 22 concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -976,7 +975,7 @@ jobs: macos: runs-on: macOS-latest steps: - - uses: dtolnay/rust-toolchain@1.94.1 + - uses: dtolnay/rust-toolchain@stable with: components: clippy - name: Install nightly From be1251672d717302d1295182e13001e4852c096e Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Thu, 14 May 2026 21:56:13 -0400 Subject: [PATCH 6/6] [forkserver_libafl_cc] Adjust test fuzzer * Remove 1 second per testcase timeout. The crashes are taking longer and are erroneously treated as a timeout * Add a max input length parameter akin to AFL++'s afl-fuzz CLI args so that the mutations are more likely to find the objective (crash) * Set the default test to length 10 as the two potential crashes can be found mutating the first 3 or 4 bytes --- fuzzers/forkserver/forkserver_libafl_cc/Justfile | 2 +- .../forkserver/forkserver_libafl_cc/src/main.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/fuzzers/forkserver/forkserver_libafl_cc/Justfile b/fuzzers/forkserver/forkserver_libafl_cc/Justfile index 3567df1e643..7c8219a70f6 100644 --- a/fuzzers/forkserver/forkserver_libafl_cc/Justfile +++ b/fuzzers/forkserver/forkserver_libafl_cc/Justfile @@ -56,7 +56,7 @@ run: fuzzer [macos] test: fuzzer #!/bin/bash - timeout 30s {{ FORKSERVER }} ./{{ FUZZER_NAME }} ./corpus/ -t 1000 | tee fuzz_stdout.log || true + timeout 30s {{ FORKSERVER }} -G 10 ./{{ FUZZER_NAME }} ./corpus/ | tee fuzz_stdout.log || true if grep -qa "objectives: 1" fuzz_stdout.log; then echo "Fuzzer is working" else diff --git a/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs b/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs index 0df9721c2e8..38e40e9521f 100644 --- a/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs +++ b/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs @@ -5,7 +5,10 @@ use clap::Parser; use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::SimpleEventManager, - executors::{forkserver::ForkserverExecutor, HasObservers, StdChildArgs}, + executors::{ + forkserver::{ForkserverExecutor, MAX_INPUT_SIZE_DEFAULT}, + HasObservers, StdChildArgs, + }, feedback_and_fast, feedback_or, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, @@ -42,6 +45,14 @@ struct Opt { )] executable: String, + #[arg( + help = "set max length of generated fuzz input", + short = 'G', + long = "maxlen", + default_value_t = MAX_INPUT_SIZE_DEFAULT + )] + max_input_len: usize, + #[arg( help = "The directory to read initial inputs from ('seeds')", name = "INPUT_DIR", @@ -180,6 +191,7 @@ pub fn main() { .parse_afl_cmdline(args) .coverage_map_size(MAP_SIZE) .timeout(Duration::from_millis(opt.timeout)) + .max_input_size(opt.max_input_len) .kill_signal(opt.signal) .build(tuple_list!(time_observer, edges_observer)) .unwrap();