Fuzz harnesses and build logic for the R programming language, maintained for continuous fuzzing on OSS-Fuzz.
The OSS-Fuzz project r is intentionally thin: its Dockerfile checks out
the R source and clones this repository, then its build.sh delegates to
ossfuzz.sh here. Keeping the harnesses in this repo lets
them be developed and updated on their own cadence — via ordinary git
pull requests — independently of both R (whose canonical source is SVN)
and the google/oss-fuzz monorepo.
harnesses/ <name>.c targets + shared common.h
dictionaries/ <name>.dict (libFuzzer dictionaries)
options/ <name>.options (libFuzzer per-target options)
seeds/<name>/ static seed corpus files for that target
ossfuzz.sh the build script OSS-Fuzz runs
.clusterfuzzlite/ ClusterFuzzLite build definition (fuzzing from this repo's CI)
docker/base/ base image holding a prebuilt, instrumented R
| Target | Exercises |
|---|---|
parse |
R_ParseVector — the lexer/parser |
unserialize |
unserialize() / readRDS — the deserializer (security-critical) |
grep |
TRE and PCRE2 regex engines, plus sub() |
coerce |
string→type conversion (as.numeric, as.complex, as.logical, type.convert) |
datetime |
strptime / as.Date / as.POSIXct / as.POSIXlt (Rstrptime.h, datetime.c) |
decompress |
memDecompress — R's gzip/bzip2/xz wrapper layer |
scan |
scan() — the delimited-text parser (scan.c) |
agrep |
agrep / agrepl — TRE approximate (edit-distance) matching |
All targets embed R via Rf_initEmbeddedR and share the setup in
harnesses/common.h (suppress R's signal handlers via R_SignalHandlers = 0
so the sanitizers' own handlers stay in place and traps crash cleanly,
suppress warnings, and wrap each call in R_ToplevelExec to catch R's
longjmp-on-error).
The build is convention-driven — there is no per-target wiring in
ossfuzz.sh. To add a target named <name>:
- Add
harnesses/<name>.c(include"common.h"for the shared R setup). - Optionally add, all keyed on the same
<name>stem:dictionaries/<name>.dictoptions/<name>.optionsseeds/<name>/containing seed input files
ossfuzz.sh globs harnesses/*.c, builds each, and attaches the matching
dictionary/options/seed-corpus if present. Nothing else to edit.
Seed inputs that must be generated by R itself (e.g. the .rds blobs for
unserialize) are produced at build time by ossfuzz.sh using the R it
just built, and staged into the same seeds/<name>/ layout.
ossfuzz.sh builds R with --enable-strict-barrier, which turns on
strict checking of the generational GC's write barrier. This surfaces C
code that mutates R objects without the proper barrier — a class of
memory-safety bug the sanitizers would otherwise miss.
Building R needs a handful of system packages (gfortran, pcre2, readline,
lzma, bz2, zlib, curl, subversion, texinfo). Because the runtime image
(base-runner) is separate from the build image, ossfuzz.sh copies the
Fortran runtime (libgfortran, libquadmath) next to libR.so; the other
libraries R links are already present in base-runner. The full apt list
lives in the OSS-Fuzz project Dockerfile (projects/r/Dockerfile in
google/oss-fuzz); adding a target that needs a new system library
therefore requires a small change there too.
With a checkout of google/oss-fuzz:
python3 infra/helper.py build_fuzzers r
python3 infra/helper.py check_build r
python3 infra/helper.py run_fuzzer r parseThe project Dockerfile clones this repository, so pushed changes here are
what get built. To iterate on uncommitted local changes, mount this repo
into the builder in place of the clone.
The harnesses need R's headers, which live in an environment-specific
location. Generate a local (git-ignored) compile_flags.txt so clangd can
resolve Rinternals.h etc.:
printf -- '-I%s/include\n-DR_NO_REMAP=1\n' "$(R RHOME)" > compile_flags.txtFuzzing runs from this repository's own CI via ClusterFuzzLite, independently of OSS-Fuzz:
| Workflow | Trigger | Does |
|---|---|---|
base-image.yml |
weekly + manual | builds the base image (an instrumented R) and pushes it to GHCR |
cflite-pr.yml |
pull requests | builds the harnesses and fuzzes what the change affects |
cflite-batch.yml |
daily | fuzzes every target and grows the stored corpus |
cflite-prune.yml |
weekly | minimises the stored corpus |
Building R takes the better part of an hour, which is far too slow to repeat
per pull request. So base-image.yml builds R once into a container image and
the fuzzing workflows only compile the harnesses against it — seconds rather
than an hour. The trade-off is that ClusterFuzzLite fuzzes the R snapshot
baked into that image rather than live trunk; the weekly rebuild bounds the
lag. OSS-Fuzz proper is unaffected and still builds trunk from source daily.
The same ossfuzz.sh drives both, so they cannot drift apart
in how R is configured. Two opt-in variables, both unset under OSS-Fuzz, do
the work: R_BUILD_ONLY (build and install R, then stop — used when baking
the base image) and R_PREBUILT (skip the R build, use an existing install —
used by .clusterfuzzlite/build.sh).
A prebuilt R is only valid for the sanitizer and engine it was instrumented for. Mismatched, the harnesses would still build and run while reporting no coverage at all from inside R — silent and useless — so the image records what it was built for and the ClusterFuzzLite build refuses a mismatch. Adding a second sanitizer therefore means publishing a second base image tag.
Corpora persist through ClusterFuzzLite's GitHub Actions filestore (the
Actions cache), which is subject to size limits and eviction. If the corpus
becomes valuable enough to guarantee, move it to a dedicated storage
repository via the actions' storage-repo input.
cifuzz.yml runs OSS-Fuzz
CIFuzz,
which does the same job for pull requests but builds the upstream r project
definition — so it cannot work until that project is merged into
google/oss-fuzz. It is manual-only until then.
Distributed under the same terms as R itself: GPL-2 | GPL-3 (you may
use it under either the GNU General Public License version 2 or version 3).
The full text of GPL-2 is in LICENSE; GPL-3 is available at
https://www.gnu.org/licenses/gpl-3.0.html. The harnesses were adapted
from the earlier r-afl work.