From e80c07a277b476bfcc879b643d6fe4cd7ebbab38 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Thu, 30 Apr 2026 17:34:00 +0100 Subject: [PATCH 1/3] infra: experimental: agent: extend (#15444) Cleans up after use and minor adjustments on code coverage reasoning Signed-off-by: David Korczynski --- infra/experimental/agent-skills/helper.py | 102 +++++++++++++++++- .../agent-skills/oss-fuzz-engineer/SKILL.md | 9 ++ 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/infra/experimental/agent-skills/helper.py b/infra/experimental/agent-skills/helper.py index bd63e9428d0c..d2b0afa47d46 100644 --- a/infra/experimental/agent-skills/helper.py +++ b/infra/experimental/agent-skills/helper.py @@ -36,7 +36,9 @@ import argparse import concurrent.futures +import glob import os +import shutil import subprocess import sys import textwrap @@ -99,11 +101,20 @@ per-file hit/total line counts — use this to find files that are still poorly covered. + **Also re-fetch the latest public OSS-Fuzz coverage report** (same URL + pattern as step 1 of the main workflow) before selecting any new target. + Files and functions already well-covered in the public report (≥ 50% + lines) must NOT be targeted — they are already exercised by the + production fuzzer corpus. Build an explicit blocklist of well-covered + files from the public `summary.json` and cross-check every candidate + against it before writing any harness. + 3. **Candidate functions not yet targeted** — read the "Remaining coverage gaps" section. The previous round identified under-covered functions but may not have targeted all of them. Pick from that list first before doing fresh analysis, prioritising functions with the lowest hit ratio - that sit on a security-relevant code path. + that sit on a security-relevant code path. Cross-check each candidate + against the public coverage report before committing to it. 4. **Threat model notes** — read the "Threat model" section. The previous round reasoned about which input paths reach security-sensitive code. @@ -124,6 +135,11 @@ has the harnesses, Dockerfile changes, and build.sh edits from that round. Do not undo or re-apply them. - Target the coverage gaps and candidate functions surfaced above. + - **Do not target functions in well-covered files.** Read the + "Already-covered files (blocklist)" section from the previous report + and cross-check every candidate against the public `summary.json` + before writing a harness. Targeting already-covered code is the most + common way expansion rounds fail to produce real gains. - If coverage stalled completely in the previous round despite multiple attempts, change subsystem — pick the next-highest-risk area from the threat model notes rather than hammering the same code paths. @@ -159,9 +175,16 @@ reports are at: `https://storage.googleapis.com/oss-fuzz-coverage/{project}/reports/YYYYMMDD/linux/summary.json` Try recent dates (last few days) until you find one. + - **Parse the per-file coverage data** from `summary.json`. Build an + explicit list of source files with low line coverage (< 30% lines hit) + — these are your candidate targets. Also note which files already have + high coverage (≥ 50%) so you can avoid wasting effort on them. - Identify under-covered source files and functions that sit on the attack surface (parsers, network handlers, file I/O, APIs exposed to - untrusted input). + untrusted input). **Only consider files/functions that appear in the + low-coverage list derived from `summary.json`** — do not rely solely + on reading the source structure to judge what is "important", because + important functions are often already covered by existing harnesses. 2. **Clone the target source locally** - Study the Dockerfile to find the upstream repository URL. @@ -170,7 +193,13 @@ of `git clone` so you can iterate quickly. 3. **Design and write new harness(es)** - - Pick the most impactful under-covered area and write a new + - **Before selecting any target function**, verify it has genuinely low + coverage in the public `summary.json` (< 30% lines hit for its source + file). Do NOT write a harness targeting a function whose source file is + already well-covered — this is the most common cause of wasted effort. + If you cannot confirm a function is poorly covered in the public report, + skip it and pick another candidate from the low-coverage file list. + - Pick the most impactful genuinely-uncovered area and write a new libFuzzer-style harness targeting it. - Follow the best practices from the *fuzzing-memory-unsafe-expert* skill: simplicity, determinism, enough entropy, matching the threat @@ -241,6 +270,12 @@ identified but NOT targeted this round, ordered by security relevance. The next round picks from this list first. + - **Already-covered files (blocklist)** *(hand-off)*: the list of + source files that had ≥ 50% line coverage in the public OSS-Fuzz + report at the time of this round (file path and coverage %). Future + rounds must not write harnesses whose primary target belongs to one + of these files. + - **Approaches tried and abandoned** *(hand-off)*: any harness designs that were attempted but discarded (e.g. zero coverage gain, false positives, excessive slowness), with a one-line reason for each. @@ -773,6 +808,26 @@ """) +def cleanup_project_artifacts(project): + """Remove Docker image and local build/coverage artifacts for a project.""" + image = f'gcr.io/oss-fuzz/{project}' + print(f' [cleanup] Removing Docker image {image}') + subprocess.run(['docker', 'rmi', '--force', image], + capture_output=True, + check=False) + + for subdir in ('out', 'work'): + path = os.path.join(OSS_FUZZ_ROOT, 'build', subdir, project) + if os.path.isdir(path): + print(f' [cleanup] Removing {path}') + shutil.rmtree(path, ignore_errors=True) + + for path in glob.glob(os.path.join(OSS_FUZZ_ROOT, f'{project}-cov-*')): + if os.path.isdir(path): + print(f' [cleanup] Removing {path}') + shutil.rmtree(path, ignore_errors=True) + + def get_recent_date_str(days_ago=1): """Return a YYYYMMDD string for a recent date.""" dt = datetime.now() - timedelta(days=days_ago) @@ -1355,6 +1410,7 @@ def _run_expand_sessions(args): sys.exit(1) max_parallel = args.max_parallel + do_cleanup = not args.no_cleanup print(f'[*] Using agent CLI: {agent_cli}') print(f'[*] Task: expand') @@ -1363,6 +1419,7 @@ def _run_expand_sessions(args): print(f'[*] Expansion size: {expansion_size}') print(f'[*] Consolidation agent: {"yes" if run_consolidate else "no"}') print(f'[*] Summary agent: {"yes" if run_summary else "no"}') + print(f'[*] Cleanup after completion: {"yes" if do_cleanup else "no"}') print(f'[*] Max parallel sessions: {max_parallel}') print() @@ -1399,6 +1456,10 @@ def run_project_rounds(project): completed_rounds) results.append(summary_result) + if do_cleanup: + print(f'[*] {project}: cleaning up build artifacts ...') + cleanup_project_artifacts(project) + return results print(f'[*] Launching sessions for {len(projects)} project(s) ' @@ -1470,6 +1531,15 @@ def cmd_integrate_project(args): _run_integrate_sessions(args) +def cmd_clean(args): + """Handle the clean subcommand.""" + _validate_projects(args.projects) + for project in args.projects: + print(f'[*] Cleaning artifacts for: {project}') + cleanup_project_artifacts(project) + print('\n[*] Done.') + + def cmd_show_prompt(args): """Handle the show-prompt subcommand.""" task = args.task @@ -1547,6 +1617,12 @@ def main(): python %(prog)s integrate-project --print-only \\ https://github.com/owner/repo + # Skip post-run cleanup (Docker image + build/coverage dirs): + python %(prog)s expand-oss-fuzz-projects --no-cleanup open62541 + + # Manually clean up artifacts from a previous run: + python %(prog)s clean open62541 json-c + # Use a specific agent CLI: python %(prog)s expand-oss-fuzz-projects --agent gemini htslib @@ -1642,6 +1718,13 @@ def main(): action='store_false', help='Skip the summary agent even when running multiple rounds.', ) + expand_parser.add_argument( + '--no-cleanup', + action='store_true', + default=False, + help='Skip cleanup of Docker images and build/coverage artifacts after ' + 'each project completes (default: cleanup is on).', + ) expand_parser.set_defaults(func=cmd_expand) # fix-builds @@ -1710,6 +1793,19 @@ def main(): ) integrate_parser.set_defaults(func=cmd_integrate_project) + # clean + clean_parser = subparsers.add_parser( + 'clean', + help='Remove Docker images and build/coverage artifacts for one or more ' + 'projects.', + ) + clean_parser.add_argument( + 'projects', + nargs='+', + help='One or more OSS-Fuzz project names to clean up.', + ) + clean_parser.set_defaults(func=cmd_clean) + # show-prompt show_parser = subparsers.add_parser( 'show-prompt', diff --git a/infra/experimental/agent-skills/oss-fuzz-engineer/SKILL.md b/infra/experimental/agent-skills/oss-fuzz-engineer/SKILL.md index 2505849d0862..1eab52e063fa 100644 --- a/infra/experimental/agent-skills/oss-fuzz-engineer/SKILL.md +++ b/infra/experimental/agent-skills/oss-fuzz-engineer/SKILL.md @@ -38,6 +38,15 @@ The agent can also be used to extend and improve the fuzzing posture of existing A useful approach for extending a project is to study the latest code coverage report for the project, which is publicly available, to identify areas of the code that are not well covered by existing fuzz targets. The agent can then write new fuzz targets to cover those areas, and test them locally before concluding on the work for the security engineer to review. +**Critical: avoid targeting already-covered code.** The most common mistake when extending an OSS-Fuzz project is writing harnesses that target functions already well-exercised by the existing production fuzzer corpus. Before selecting any target function: + +1. Fetch the public `summary.json` (see the code_coverage reference). +2. Parse the per-file line coverage percentages. +3. Build an explicit blocklist of files with ≥ 50% line coverage — do NOT write harnesses whose primary target lives in one of these files. +4. Only select targets from files with genuinely low coverage (< 30% lines hit). + +Reading the source code and identifying "important-looking" functions is not sufficient — important functions are frequently already covered. Coverage data from `summary.json` is the authoritative source of truth for what needs work. + Use the local code coverage feature of the `python3 infra/helper.py` tool to generate code coverage reports for fuzz targets locally, for example to validate the code coverage achieved by a new fuzz target. This can be done by running `python3 infra/helper.py introspector --coverage-only PROJECT_NAME` and then studying the generated report in e.g. build/out/PROJECT_NAME/report. Some examples of this include: ``` From d0c3ffe2cf36176711fadcc70e1a285a37bdcd8f Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Thu, 30 Apr 2026 12:34:27 -0400 Subject: [PATCH 2/3] Fix swift protobuf builds (#15443) The second commit is the fix for the project. I'd like to get this in to get things testing again, and will circle back to the better path for handing this generally for other Swift projects. --- projects/swift-protobuf/Dockerfile | 2 +- projects/swift-protobuf/build.sh | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/projects/swift-protobuf/Dockerfile b/projects/swift-protobuf/Dockerfile index b429dcd30a14..a89941b6ad34 100644 --- a/projects/swift-protobuf/Dockerfile +++ b/projects/swift-protobuf/Dockerfile @@ -16,6 +16,6 @@ FROM gcr.io/oss-fuzz-base/base-builder-swift:ubuntu-24-04 -RUN git clone --depth 1 --recurse-submodules --shallow-submodules https://github.com/apple/swift-protobuf.git +RUN git clone --depth 1 https://github.com/apple/swift-protobuf.git COPY build.sh $SRC WORKDIR $SRC/swift-protobuf diff --git a/projects/swift-protobuf/build.sh b/projects/swift-protobuf/build.sh index 4c30102bbba5..7384e64078ad 100755 --- a/projects/swift-protobuf/build.sh +++ b/projects/swift-protobuf/build.sh @@ -19,6 +19,29 @@ . precompile_swift cd FuzzTesting + +# Normally one would use `$SWIFTFLAGS` (from `precompile_swift``) on this +# invocations, but as we found in +# https://github.com/apple/swift-protobuf/pull/2037, the flags needs depend on +# *both* the Swift Toolchain version *and& the `swift-tools-version` in the +# `Package.swift`. +# +# So, for now, manually recode `precompile_swift` with the flags needed since +# swift-protobuf uses a 6.2+ `swift-tools-version`. +export SWIFTFLAGS="-Xswiftc -static-stdlib --static-swift-stdlib" +if [ "$SANITIZER" = "coverage" ] ; then + export SWIFTFLAGS="$SWIFTFLAGS -Xswiftc -profile-generate -Xswiftc -profile-coverage-mapping --sanitize=fuzzer" +else + export SWIFTFLAGS="$SWIFTFLAGS --sanitize=fuzzer --sanitize=$SANITIZER" + for f in $CFLAGS; do + export SWIFTFLAGS="$SWIFTFLAGS -Xcc=$f" + done + + for f in $CXXFLAGS; do + export SWIFTFLAGS="$SWIFTFLAGS -Xcxx=$f" + done +fi + # debug build swift build -c debug $SWIFTFLAGS ( From b3c46436b957f5ef54ebd416f006c9c044f1bee8 Mon Sep 17 00:00:00 2001 From: Bob Friesenhahn Date: Thu, 30 Apr 2026 15:29:11 -0500 Subject: [PATCH 3/3] =?UTF-8?q?Remove=20clone=20of=20libgeotiff.=20Clone?= =?UTF-8?q?=20dav1d.=20Use=20pip3=20to=20install=20the=20late=E2=80=A6=20(?= =?UTF-8?q?#15445)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary clone of libgeotiff. Clone dav1d. Use pip3 to install the latest meson. --- projects/graphicsmagick/Dockerfile | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/projects/graphicsmagick/Dockerfile b/projects/graphicsmagick/Dockerfile index 7e9e3dfe572d..1fe0c228ec8d 100644 --- a/projects/graphicsmagick/Dockerfile +++ b/projects/graphicsmagick/Dockerfile @@ -27,10 +27,17 @@ RUN apt-get update && \ nasm \ pkg-config \ po4a \ + python3-pip \ + python3-setuptools \ + python3-wheel \ ninja-build \ libgflags-dev \ yasm +# Get latest meson +RUN apt purge meson +RUN pip3 install --upgrade pip meson --root-user-action ignore + # Due to libtiff requirements, build requires autoconf 2.71 (or later) RUN curl -LO https://mirrors.edge.kernel.org/ubuntu/pool/main/a/autoconf/autoconf_2.72-3.1ubuntu1_all.deb && \ apt install ./autoconf_2.72-3.1ubuntu1_all.deb @@ -53,9 +60,6 @@ RUN git clone --depth 1 https://github.com/Esri/lerc # Libdeflate RUN git clone --depth 1 https://github.com/ebiggers/libdeflate -# libgeotiff -RUN git clone --depth 1 https://github.com/OSGeo/libgeotiff - # h.265 codec implementation needed by libheif RUN git clone --depth 1 https://github.com/strukturag/libde265 || \ printf "https://github.com/strukturag/libde265 is not available!\n" @@ -70,6 +74,10 @@ RUN git clone --depth 1 https://bitbucket.org/multicoreware/x265_git/src/stable/ RUN git clone --depth 1 https://aomedia.googlesource.com/aom aom || \ printf "https://aomedia.googlesource.com/aom is not available!\n" +# Dav1d Codec Library needed by libheif +RUN git clone --depth 1 https://code.videolan.org/videolan/dav1d || \ + printf "https://code.videolan.org/videolan/dav1d is not available!\n" + # AVC (OpenH264) Codec Library needed by libheif RUN git clone --depth 1 https://github.com/cisco/openh264 openh264