بنقرة واحدة
fuzzing-memory-unsafe-expert
Use this skill to fuzz open source C/C++ software projects.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Use this skill to fuzz open source C/C++ software projects.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use this skill to interact with the OSS-Fuzz infrastructure.
Use this skill to fuzz open source Go software projects.
Use this skill to fuzz open source JVM projects (Java, Kotlin, Scala, etc.) using Jazzer.
Use this skill to fuzz open source Python software projects using Atheris.
Use this skill to fuzz open source Rust software projects.
| name | fuzzing-memory-unsafe-expert |
| description | Use this skill to fuzz open source C/C++ software projects. |
This skill provides the agent with the necessary knowledge and tools to fuzz open source software projects, particularly those that are part of the OSS-Fuzz program. The agent can use this skill to build fuzzers, run fuzzing campaigns, analyze results, and improve the fuzzing posture of projects.
The fuzzing abilities focused on here is libFuzzer-style harnesses, which are commonly used in OSS-Fuzz.
Before writing a single line of harness code, decide what you are trying to find and where an attacker's input actually enters the code. A harness is only as good as the entry point it targets: a technically-correct harness pointed at code no attacker can influence finds bugs no maintainer will fix. This step is what separates a valuable harness from a noisy one.
For the target project, answer these four questions explicitly and write the answers into the harness's rationale doc:
assert()-guarded programmer-error precondition, usually is not — fuzzing it
produces findings maintainers reject.Concrete, common attack surfaces in C/C++ projects, and the entry points to target for each:
png_read_info, TIFFClientOpen, av_read_frame,
archive_read_open_memory. Attacker controls the whole file. High-value,
classic OSS-Fuzz target.SSL_read, parse_packet, deserialize_message. Attacker
controls the byte stream; often you must also drive a state machine (see the
reachability note below).The threat model dictates the harness shape:
LLVMFuzzerInitialize).
Fuzzing the config too invents a threat that does not exist and yields
rejected findings.An entry point that is untrusted-reachable in production may still be unreachable from your harness if getting to the interesting code requires state the harness never sets up — an open session, a completed handshake, a prior header packet, a registered codec. Two consequences:
summary.json, decide
whether it is seed-limited (a better corpus would reach it — write seeds) or
harness-limited (no input reaches it as the harness stands — the harness
itself must set up more state).Consider a library that loads a custom image format via
int img_load(const uint8_t *buf, size_t len, img_opts *opts), where opts
comes from the application, not the file:
buf/len (the image file). Trusted: opts.img_load — the outermost public function
an attacker's bytes reach.The harness therefore fixes opts to a representative default once, and feeds
the mutated bytes as buf/len:
#include <fuzzer/FuzzedDataProvider.h>
static img_opts g_opts; // trusted config, set once — not fuzzed.
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
img_opts_default(&g_opts); // represents the real application's setup
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Attacker controls the whole file; feed it directly.
img_load(data, size, &g_opts);
return 0;
}
Fuzzing opts instead, or calling an internal decode_scanline directly with
raw bytes, would either invent a non-existent threat or trip asserts the public
API prevents — both produce findings maintainers will not act on.
A fuzzing harness is a function that takes a byte array as input and processes it in a way that can trigger bugs or vulnerabilities in the software being tested. The core function of a fuzzing harness typically looks like this:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Process the input data and trigger potential bugs
return 0; // Return 0 to indicate successful processing
}
Return 0 in all normal cases. Return -1 to tell libFuzzer to reject an
input and not add it to the corpus (rarely needed). Never exit() on bad
input — it aborts the whole run and breaks coverage tooling.
Most non-trivial targets do not take a single flat byte buffer — they need an
integer here, a length there, a string, several sub-buffers. Passing the raw
data/size directly to such an API means the fuzzer almost never produces a
well-formed call. FuzzedDataProvider splits the one input stream into typed
values, so libFuzzer's mutations map onto the parameters the target actually
expects. It is the standard tool for this in OSS-Fuzz (used by well over a
hundred projects) and is shipped in the base image — no dependency to add:
#include <fuzzer/FuzzedDataProvider.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fdp(data, size);
// Consume typed, bounded values up front...
int width = fdp.ConsumeIntegralInRange<int>(1, 4096);
bool strict = fdp.ConsumeBool();
std::string name = fdp.ConsumeRandomLengthString(64);
// ...then hand the rest of the bytes to the payload argument.
std::vector<uint8_t> payload = fdp.ConsumeRemainingBytes<uint8_t>();
my_decode(name.c_str(), width, strict, payload.data(), payload.size());
return 0;
}
Commonly used methods (see compiler-rt's FuzzedDataProvider.h for the full
set):
| Method | Description |
|---|---|
ConsumeIntegral<T>() | one integer of type T |
ConsumeIntegralInRange<T>(min, max) | integer bounded to [min, max] — use this to pick sizes, indices, enum values |
ConsumeBool() | one boolean |
ConsumeEnum<E>() | a value of a scoped enum with kMaxValue |
ConsumeFloatingPoint<T>() / ...InRange(min, max) | float/double |
ConsumeRandomLengthString(max) | a std::string whose length the fuzzer controls (stops at an escaped \) |
ConsumeBytes<T>(count) / ConsumeBytesAsString(count) | exactly count bytes |
ConsumeRemainingBytes<T>() / ConsumeRemainingBytesAsString() | everything left |
remaining_bytes() | how many bytes are left to consume |
Guidance:
ConsumeRemaining.... Consuming a fixed count first and the remainder last
keeps the layout robust to mutation.ConsumeIntegralInRange rather than masking
a raw integer — it wastes fewer inputs and keeps allocations sane.parse(data, size)), pass data/size straight
through — FDP would only add noise.If the harness needs setup that must happen once before fuzzing (parsing
argv, setting environment variables, initialising a library global), put it
in the optional LLVMFuzzerInitialize hook, which libFuzzer calls once at
startup — never do per-input work there:
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
setenv("HOME", "/tmp", 1);
my_library_global_init();
return 0;
}
Keep expensive but per-process-constant state (loaded config, a compiled
schema, an allocated context reused across inputs) in a file-scope
static/global initialised once — either here or lazily on the first
LLVMFuzzerTestOneInput call — so it is not rebuilt on every iteration.
A C/C++ harness rarely detects a bug by itself — the sanitizer the project is built with is what turns silent memory corruption into a crash the fuzzer can catch. OSS-Fuzz builds each target under a sanitizer (default AddressSanitizer; also UndefinedBehaviorSanitizer and MemorySanitizer):
null pointer use,
bad casts, invalid enum values.Design implication: the harness's job is to reach untrusted-input-driven
memory operations with enough variety; the sanitizer reports the violation.
build.sh must compile both the target library and the harness with the
OSS-Fuzz $CFLAGS/$CXXFLAGS (which carry the sanitizer + coverage flags) —
linking against a separately, non-instrumented build silently disables the
oracle and coverage guidance.
build.sh compiles the project (as a static library where possible) and then
compiles + links each harness against it with $LIB_FUZZING_ENGINE. Always use
the OSS-Fuzz-provided $CC/$CXX and $CFLAGS/$CXXFLAGS so instrumentation
and the sanitizer are applied:
# build.sh — build the library with instrumentation, statically.
mkdir build && cd build
cmake -DBUILD_SHARED_LIBS=OFF .. # a static .a is easiest to link the harness against
make -j$(nproc)
cd ..
# Compile and link each harness against the instrumented library.
for f in $SRC/project/fuzz/*_fuzzer.cc; do
fuzzer=$(basename "$f" _fuzzer.cc)
$CXX $CXXFLAGS -std=c++17 -I$SRC/project/include \
"$f" -o "$OUT/${fuzzer}" \
$LIB_FUZZING_ENGINE "$SRC/project/build/libproject.a"
done
Key points:
$LIB_FUZZING_ENGINE, not a hardcoded
-fsanitize=fuzzer — this lets OSS-Fuzz swap the engine (libFuzzer, AFL++,
Centipede, or the coverage/check_build no-op engine).LD_LIBRARY_PATH juggling at
run time and ensures the linked code is instrumented.$OUT/<fuzzer>_seed_corpus.zip) and dictionaries
($OUT/<fuzzer>.dict) in the same script.Study existing harnesses before writing your own — they show the idioms above applied to real projects, and matching an established style makes your harness easier for maintainers to accept. Each example below is checked into this repository; open the file and read it in full.
projects/sentencepiece/sample_encode_fuzzer.cc — the canonical
FuzzedDataProvider split. It carves the one input into an int
(ConsumeIntegral), a float (ConsumeFloatingPoint) and the remaining
bytes as the text argument (ConsumeRemainingBytesAsString), then calls
SampleEncodeAsSerializedProto(text, nbest, alpha). Note the ordering: the
fixed-size scalars are consumed first and the variable-length string last.
This is the template whenever the target API takes several typed parameters
rather than one buffer.
projects/spdlog/fuzz/pattern_fuzzer.cc — lazy one-time initialisation
and a tightly-scoped threat model. A static logger is created on the first
call and reused across iterations (never rebuilt per input), it guards
size == 0, and it fuzzes exactly one attacker-relevant entry point:
spdlog::set_pattern(str), the format-pattern parser. It does not fuzz the
logging config — a clean example of "mutate only what the attacker controls."
projects/file/magic_fuzzer_fd.cc — LLVMFuzzerInitialize for expensive
setup, and feeding an API that needs a real file descriptor. The magic
database is loaded once in an Environment built from argv[0]'s directory
(setup that must not repeat per input), and each input is written to a
FuzzerTemporaryFile so it can be handed to magic_descriptor(fd). Use this
pattern when the target only accepts a path/fd rather than an in-memory
buffer — but note that going through a temp file is slower, so prefer an
in-memory entry point when the library exposes one.
When extending an existing OSS-Fuzz project, the best reference is that
project's own existing harnesses: match their file layout, naming
(*_fuzzer.cc), build-script wiring and error-handling conventions.
The below are characteristics commonly found in good fuzzing harnesses, which contribute to their effectiveness in finding bugs. They are not all necessary for a good harness and at times it may even be desirable to actively not pursue some of them.
python3 infra/helper.py build_fuzzers <project_name>
python3 infra/helper.py run_fuzzer <project_name> <fuzzer_name> -- -max_total_time=30 # run the fuzzer for 30 seconds to validate it works
python3 infra/helper.py check_build <project_name>
A good harness needs a good initial corpus. Place seed files in
$OUT/<fuzzer_name>_seed_corpus.zip and dictionaries in
$OUT/<fuzzer_name>.dict.
For targets that parse a structured format (binary containers like ELF/PE, or
codec/network bitstreams, or text grammars), a few hand-picked sample files
are rarely enough: random mutation almost never gets past the parser's magic /
length / checksum checks, so the deep parsing code stays dark. The most
effective approach is a script that constructs structurally-valid inputs
from scratch, run from build.sh and appended to the corpus. It is
reproducible, needs no external samples, and lets you target specific
dark-but-reachable code identified from coverage. See the OSS-Fuzz engineer
skill's structured seed generation
reference for
the full workflow and projects/vlc/generate_seeds.py for a worked example.