Skip to main content

oss-fuzz

Run Tika's OSS-Fuzz Jazzer targets locally against a working-tree checkout — build the image, build fuzzers from local source, fuzz a target, run a corpus as a regression pass, reproduce a crash, and add seeds. Use for "fuzz the OneNote parser", "run OneNoteParserFuzzer against these files", "reproduce an OSS-Fuzz crash", "fuzz my branch before merge".

Informações da origem

Repositório
apache/tika
Última atividade na origem
27 de agosto de 2026 às 17:44
Idioma detectado do SKILL.md
inglês
Estrelas
4.096
Forks
1.030

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
oss-fuzz
description
Run Tika's OSS-Fuzz Jazzer targets locally against a working-tree checkout — build the image, build fuzzers from local source, fuzz a target, run a corpus as a regression pass, reproduce a crash, and add seeds. Use for "fuzz the OneNote parser", "run OneNoteParserFuzzer against these files", "reproduce an OSS-Fuzz crash", "fuzz my branch before merge".
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> Local override: `$TIKA_SKILLS_LOCAL/oss-fuzz/LOCAL.md` (default `~/.tika-skills`), read after this file, wins on conflict. # Tika OSS-Fuzz — local fuzzing Tika is already in OSS-Fuzz as the **`apache-tika`** project (not `tika`). It is Jazzer-based (coverage-guided, in-process JVM fuzzing), not the old `tika-fuzzing` seed-mutation module (removed in TIKA-4506). The fuzz targets and seed logic live in the **oss-fuzz** repo under `projects/apache-tika/`, *not* in this repo: - `project-parent/fuzz-targets/src/main/java/com/example/*Fuzzer.java` — one Jazzer target per parser family. Each calls `ParserFuzzer.parseOne(...)` (parse-from-bytes and parse-from-file) and swallows `TikaException | SAXException | IOException`; anything else — `Error` (OOM, StackOverflow), a hang, or an unexpected `RuntimeException` — is a finding. - `build.sh` — builds `tika-app`, then the `fuzz-targets` module. - `build_seeds.sh` — packs Tika's own unit-test files into `<Target>_seed_corpus.zip` by file extension. Targets (as of this writing): `AudioVideoParsersFuzzer`, `AutoDetectParserFuzzer`, `CompressorParserFuzzer`, `HtmlParserFuzzer`, `ImageParsersFuzzer`, `JackcessParserFuzzer`, `OOXMLParserFuzzer`, `OfficeParserFuzzer`, `OneNoteParserFuzzer`, `PDFParserFuzzer`, `PackageParserFuzzer`, `RFC822ParserFuzzer`, `RTFParserFuzzer`, `TextAndCSVParserFuzzer`, `XMLReaderUtilsFuzzer`. `ParserFuzzer` is the shared helper, not a target (`build.sh` skips it). This list drifts — get the current one after a build with `ls build/out/apache-tika/*Fuzzer`, or from source with `find $OSSFUZZ/projects/apache-tika/project-parent/fuzz-targets -name '*Fuzzer.java'`. Primary contact on the project is `tallison@apache.org`, so OSS-Fuzz crash mail / ClusterFuzz notifications land in that inbox — check there for what continuous fuzzing has already found before treating a bug as newly discovered. ## Prerequisites - Docker daemon running (`docker ps`). - A local oss-fuzz checkout: `git clone --depth 1 https://github.com/google/oss-fuzz`. All `helper.py` commands run from the oss-fuzz root. `$OSSFUZZ` below = that dir. - `python3` (helper.py is Python). - **An amd64 host.** The OSS-Fuzz base images are amd64-only, so every `docker run` here pins `--platform linux/amd64`. On Apple Silicon that is qemu emulation — slow for a CPU-bound fuzzing workload and it distorts exactly the timing-based findings this skill tells you to trust, so treat `-timeout` / slow-unit results under emulation as suspect and run real campaigns on a native amd64 box. - **Disk:** budget many GB — the base-builder/base-runner images are multi-GB each, plus `build/out` and any corpus copies. ## 1. Build the image (non-interactive) `build_image` **prompts** to pull base images; a plain background/non-tty run dies with `EOFError: EOF when reading a line`. Always pass `--no-pull` (or `--pull` to force a refresh) and redirect stdin: ```bash cd $OSSFUZZ python3 infra/helper.py build_image --no-pull apache-tika < /dev/null ``` Pulls the `base-builder-jvm` image on first run (multi-GB); slow once, cached after. ## 2. Build fuzzers from a LOCAL checkout (the point of local dev) Give `build_fuzzers` a source path and it mounts your working tree over the Dockerfile's `git clone` of Tika — so it fuzzes uncommitted code (a branch under review, a candidate cap-fix), not upstream `main`. **Gotcha — the mount path is nested.** The Dockerfile clones Tika into `$SRC/project-parent/tika`, but `helper.py` defaults a local mount to `/src/tika` (basename of `main_repo`). The default lands in the wrong place and the build silently uses the baked-in clone instead of your tree. Pin it: ```bash python3 infra/helper.py build_fuzzers \ --mount_path /src/project-parent/tika \ apache-tika /home/<user>/path/to/tika ``` **Verify the mount actually took** before trusting any result — a wrong `--mount_path` fails open (silently builds the baked-in upstream clone and reports clean). The reliable check is a negative control: inject a guaranteed compile error into a source file on the build path (e.g. a bare `THIS_MUST_NOT_COMPILE` token in `OneNoteParser.java`), rebuild, and confirm the build **fails at your file and line**. If it still succeeds, the mount is being ignored. Then revert and rebuild clean. Note the failure surfaces as a **spotless lint error** naming your file:line (`removeUnusedImports ... error: <identifier> expected`), not a raw javac message — spotless runs first. Watch the real exit code, not a wrapper's: `helper.py` prints `ERROR:__main__:Building fuzzers failed.` and exits non-zero on failure. Sanity-check the target exists after build: `ls build/out/apache-tika/OneNoteParserFuzzer`. ## 3a. Regression pass over YOUR OWN corpus (run each file once) The common ask — "run `OneNoteParserFuzzer` against these files" — is a read-only regression pass: execute each of your inputs once, report crashes, change nothing. **Do NOT use `helper.py run_fuzzer --corpus-dir` for this.** That path is destructive and does not run your files: the base-runner `run_fuzzer` wrapper **clears the mounted corpus dir and unpacks the baked-in `<Target>_seed_corpus.zip`** (Tika's own unit-test files) into it, then fuzzes those. Point it at a 260-file corpus and it runs the ~12 seed files instead and wipes your dir down to libFuzzer's minimized set. Two failure modes in one: a false "no crash" (it ran the seeds, which never crash) and a destroyed corpus. Instead, invoke the built target **binary directly**, bypassing the wrapper, with your corpus as a libFuzzer positional arg and `-runs=0` (load corpus, run each once, exit — no mutation). Keep the pristine corpus elsewhere and hand the container a throwaway copy: ```bash docker run --rm --platform linux/amd64 --shm-size=2g \ -e FUZZING_ENGINE=libfuzzer -e SANITIZER=address \ -v <throwaway-corpus-copy>:/corpus \ -v $HOME/oss-fuzz/build/out/apache-tika:/out \ -t gcr.io/oss-fuzz-base/base-runner:latest \ bash -c '/out/OneNoteParserFuzzer -runs=0 -timeout=60 -rss_limit_mb=3600 /corpus' ``` Mount `/out` (the target script resolves its Jazzer jars and classpath from its own dir). `Done N runs` should equal your file count — if it says ~12, you hit the seed-corpus substitution above. **Corpus must be a FLAT dir of files.** libFuzzer does not recurse into subdirectories. A sharded/nested corpus (e.g. `onenote/a3/47/<sha>`) must be flattened first — and flatten **collision-safe**: a plain `cp` of a human-named tree (`a/test.pdf`, `b/test.pdf`) silently overwrites, so you fuzz fewer files than you think (the same "ran fewer than expected" trap this section opened with). Name each flattened file by its content hash, which also dedups identical inputs: ```bash mkdir -p flat find <nested> -type f -exec sh -c 'cp "$1" flat/$(sha1sum "$1" | cut -c1-40)' _ {} \; ``` (sha-named source blobs are already unique; the hash-rename makes any corpus safe). ## 3b. Open-ended (mutational) fuzzing Drop `-runs=0` and let Jazzer mutate from the corpus to hunt new paths. Use the **direct-binary** invocation again, not `helper.py run_fuzzer --corpus-dir` — that wrapper still clears your dir and substitutes the baked seed corpus (see 3a). Mount a **writable** working copy of the corpus (libFuzzer writes new coverage-increasing units back into it; keep the pristine corpus elsewhere) and an artifact dir for any reproducer it finds: ```bash docker run --rm --platform linux/amd64 --shm-size=2g \ -e FUZZING_ENGINE=libfuzzer -e SANITIZER=address \ -v <writable-corpus-copy>:/corpus -v <artifact-dir>:/artifacts \ -v $HOME/oss-fuzz/build/out/apache-tika:/out \ -t gcr.io/oss-fuzz-base/base-runner:latest \ bash -c '/out/OneNoteParserFuzzer -max_total_time=1200 -timeout=60 \ -rss_limit_mb=3600 -artifact_prefix=/artifacts/ /corpus' ``` (If you ever do route flags through `helper.py run_fuzzer`, they need a `--` separator — `run_fuzzer ... OneNoteParserFuzzer -- -runs=0` — or argparse rejects the leading-dash flags. The direct-binary form above avoids that entirely.) Memory note: `build.sh` runs these targets at `-Xmx3000m -rss_limit_mb=3600` on purpose — audio/video/image/onenote parsers hit `new byte[~Integer.MAX_VALUE]` single-allocation OOMs. An OOM under ~2–3 GB is a real finding; if you see one above the rss limit, the fix is a bound in the parser, not a bigger heap. ### Finding → fixing → re-fuzzing (usually whack-a-mole) libFuzzer **halts on the first finding** by default — so one campaign yields one bug, and an early crash/OOM/hang after a few hundred execs means the target barely explored the space. That is not a clean bill of health. You *can* enumerate several bugs in one run: `-fork=1` with `-ignore_crashes` / `-ignore_ooms` / `-ignore_timeouts` keeps going past each finding and drops one artifact per distinct crash. That genuinely works when the bugs sit on **independent paths** — two roughly-equally-reachable bugs, neither downstream of the other. What it does **not** do is get you past a bug that *blocks* what's behind it. A resource-exhaustion bug (unbounded allocation, runaway recursion, hang) is a **wall**: every input that reaches it dies *there*, so code *downstream on that same path* never executes and its bugs stay invisible no matter how long you fuzz or how many `-ignore_*` flags you set. And in practice the easy, early bugs tend to sit on shared entry paths and block the harder, deeper ones — so ignoring them mostly burns cycles re-hitting the same wall. That tendency (not a law) is why the loop is usually: 1. Fuzz until it halts on a finding; save the reproducer. 2. **Fix that bug** in the parser (bound the count/array, cap the recursion) so the input survives past it. 3. Rebuild fuzzers (step 2 above) against the fix. 4. Re-fuzz — mutations now proceed past the old wall and reach the next bug. Rule of thumb: reach for `-ignore_*`/`-fork` to *triage breadth* (roughly how many independent problems are in here?); fix-and-re-fuzz to actually make *depth* progress past a blocker. Corollary: a wall early in a shared entry point (e.g. an OOM in the legacy `OneNotePtr` path) also blocks fuzzing of *sibling* code (the fsshttpb/MS-ONESTORE path), because mutations that flip the format-routing bytes fall into the wall before reaching the sibling. Fix the walls nearest the entry point first. ## 3c. Tuning heap, timeout, and RSS limit The generated target wrapper (`build/out/apache-tika/<Target>`) bakes in `--jvm_args="-Xmx3000m:-Xss1024k"` and `-rss_limit_mb=3600mb`, then appends whatever args you pass (`$@`). So: - **Per-input timeout** (hang cutoff): `-timeout=<sec>` — a libFuzzer flag, passes straight through. A single input exceeding it is reported as a slow-unit / timeout finding. - **RSS OOM trigger**: `-rss_limit_mb=<N>` — libFuzzer flag, passes through; the process is killed (exit 71) when total RSS crosses it. - **JVM heap**: append `--jvm_args=-Xmx<N>m:-Xss1024k`. The wrapper's `$@` lands *after* its own `--jvm_args`, and **Jazzer's last `--jvm_args` wins** (verified: appending `-Xmx1024m` makes the JVM throw at ~1 GB, printing `use '-Xmx921m' to reproduce`). **Testing at a lower heap (~1 GB) is the point** — it surfaces allocation-heavy parses far sooner than the 3 GB default, and turns a bare libFuzzer RSS-OOM (no stack) into a `java.lang.OutOfMemoryError` that Jazzer prints *with a Java stack trace*. ```bash docker run --rm --platform linux/amd64 --shm-size=2g \ -e FUZZING_ENGINE=libfuzzer -e SANITIZER=address \ -v <corpus-or-artifact>:/in -v $HOME/oss-fuzz/build/out/apache-tika:/out \ -t gcr.io/oss-fuzz-base/base-runner:latest \ bash -c '/out/OneNoteParserFuzzer -timeout=60 -rss_limit_mb=3600 \ --jvm_args=-Xmx1024m:-Xss1024k /in/<reproducer>' ``` **Caveat — the OOM stack is the straw, not always the cause.** Capping the heap makes the JVM throw wherever it happens to run out; that frame is where the collector gave up, not necessarily the runaway allocation. Use it as a *lead*: walk up the stack and confirm the unbounded count/array in code, or take a heap histogram (dominant object class) to find what actually accumulated. (Real example: a heap-capped OOM surfaced at `PropertyValue.<init>`, but the cause was an unbounded 32-bit count two frames up driving `Stream.generate(...).limit(val32)`.) **Caveat — colon-bearing JVM options are SILENTLY DROPPED by `--jvm_args`.** Jazzer splits `--jvm_args` on `:` and creates the JVM with `ignoreUnrecognized`, so an option that itself contains a colon (`-XX:+HeapDumpOnOutOfMemoryError`, `-Xlog:gc`) is split into fragments and then **silently ignored — no error, no effect** (verified: `--jvm_args=-XX:+PrintFlagsFinal` printed no flag dump and exited 0). That is worse than an error — you think a flag is set when it isn't. Colon-free options (`-Xmx`, `-Xss`) go through fine. For `-XX`/`-Xlog` flags, use the `JAVA_TOOL_OPTIONS` env var instead — the embedded JVM reads it directly (verified: `-e JAVA_TOOL_OPTIONS=-XX:+PrintFlagsFinal` logs `Picked up JAVA_TOOL_OPTIONS` and dumps the flag table): `-e JAVA_TOOL_OPTIONS='-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/in'`. ## 4. Reproduce a specific crash Run the target **binary** on the single testcase — the direct form used throughout this skill (and the one verified here). A file positional makes libFuzzer run just that input and exit: ```bash docker run --rm --platform linux/amd64 --shm-size=2g \ -e FUZZING_ENGINE=libfuzzer -e SANITIZER=address \ -v <dir-with-testcase>:/in -v $HOME/oss-fuzz/build/out/apache-tika:/out \ -t gcr.io/oss-fuzz-base/base-runner:latest \ bash -c '/out/OneNoteParserFuzzer -rss_limit_mb=3600 /in/<testcase>' ``` Add `--jvm_args=-Xmx1024m:-Xss1024k` for a Java stack on an OOM (§3c). Rebuild fuzzers (step 2) against a candidate fix and re-run to confirm the crash clears. `helper.py reproduce apache-tika <Target> <file>` does the same via the wrapper and is safe — it runs a single testcase, not a corpus dir, so the `run_fuzzer --corpus-dir` destruction in §3a does not apply. ## 5. Add seeds / a new target - **Seeds:** edit `projects/apache-tika/build_seeds.sh` — one `find ... -name '*.ext' | xargs zip -u <Target>_seed_corpus.zip` line per extension. Real, structurally-valid files matter far more than count: coverage-guided fuzzing needs a seed that already passes the parser's magic/structure checks to reach the interesting code (a OneNote seed must carry the `.one` GUID header). A private/real-document corpus stays local — do not commit it to oss-fuzz. - **Gathering a real corpus from Common Crawl:** [`commoncrawl-fetcher-lite`](https://github.com/tballison/commoncrawl-fetcher-lite) samples binary files out of Common Crawl by HTTP-declared and Tika-detected type (skipping truncated payloads) into an output directory — a fast way to assemble a large, format-specific, structurally-valid corpus for a target. - **New target:** add `FooParserFuzzer.java` next to the others following the `ParserFuzzer.parseOne` + swallow-expected-exceptions pattern. ## Cleanup — the container leaves root-owned files in your working tree `build_fuzzers` with a local `--mount_path` compiles your working tree *inside
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub