Skip to content

Latest commit

 

History

History
153 lines (120 loc) · 7.27 KB

File metadata and controls

153 lines (120 loc) · 7.27 KB

r-oss-fuzz

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.

Layout

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

Fuzz targets

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).

Adding or updating a target

The build is convention-driven — there is no per-target wiring in ossfuzz.sh. To add a target named <name>:

  1. Add harnesses/<name>.c (include "common.h" for the shared R setup).
  2. Optionally add, all keyed on the same <name> stem:
    • dictionaries/<name>.dict
    • options/<name>.options
    • seeds/<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.

R build configuration

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.

System dependencies

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.

Building locally

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 parse

The 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.

Editor setup

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.txt

Continuous integration

Fuzzing 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.

License

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.