/src/r-oss-fuzz/harnesses/load.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for load() -- R's .RData reader. |
3 | | * |
4 | | * load() reads a five-byte magic and, for RD[ABX][2-9], hands the rest |
5 | | * of the file to the serialization code that unserialize() already |
6 | | * fuzzes. Anything else falls through to .Internal(load()) and the |
7 | | * readers in src/main/saveload.c: the pre-R-1.4.0 formats (RDA1, RDB1, |
8 | | * RDX1 via NewAsciiLoad/NewBinaryLoad/NewXdrLoad) and the even older |
9 | | * numeric-magic layouts (1971, 1972, 1975-1977 via DataLoad). Those |
10 | | * readers are ~1700 lines that no test suite exercises, yet they still |
11 | | * run on any .RData file handed to load(), which users routinely do with |
12 | | * files from the network. |
13 | | * |
14 | | * Both slots load into a fresh environment so nothing accumulates across |
15 | | * iterations. The entry point only takes a path, so the input is staged |
16 | | * in a per-process scratch file (see common.h). |
17 | | */ |
18 | | |
19 | | #include <stdint.h> |
20 | | #include <string.h> |
21 | | |
22 | | #include "common.h" |
23 | | |
24 | 9.17k | #define FUZZ_MAX_INPUT (1024 * 64) |
25 | 9.14k | #define N_CALLS 2 |
26 | | |
27 | | static char *scratch_path; |
28 | | static SEXP calls[N_CALLS]; |
29 | | |
30 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
31 | 2 | { |
32 | 2 | (void)argc; |
33 | 2 | (void)argv; |
34 | 2 | fuzz_init_r(); |
35 | | |
36 | 2 | scratch_path = fuzz_scratch_file("load"); |
37 | 2 | if (scratch_path == NULL) |
38 | 0 | abort(); |
39 | | |
40 | | /* [0] the public entry point: R-level magic check, then either |
41 | | * loadFromConn2 (v2/v3) or the legacy .Internal(load) fallback. |
42 | | * [1] the legacy fallback directly, so the fuzzer does not have to |
43 | | * get past the R-level grepl() on the magic to reach it. */ |
44 | 2 | static const char *const sources[N_CALLS] = { |
45 | 2 | "function(path) load(path, envir = new.env())", |
46 | 2 | "function(path) .Internal(load(path, new.env()))", |
47 | 2 | }; |
48 | 2 | SEXP path; |
49 | 2 | Rf_protect(path = Rf_mkString(scratch_path)); |
50 | 6 | for (int i = 0; i < N_CALLS; i++) { |
51 | 4 | SEXP wrapper; |
52 | 4 | Rf_protect(wrapper = fuzz_make_wrapper(sources[i])); |
53 | 4 | Rf_protect(calls[i] = Rf_lang2(wrapper, path)); |
54 | 4 | } |
55 | 2 | return 0; |
56 | 2 | } |
57 | | |
58 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
59 | 9.17k | { |
60 | 9.17k | if (size < 2 || size > FUZZ_MAX_INPUT) |
61 | 40 | return 0; |
62 | | |
63 | 9.13k | if (!fuzz_write_scratch(scratch_path, data + 1, size - 1)) |
64 | 0 | return 0; |
65 | | |
66 | 9.13k | fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv); |
67 | 9.13k | return 0; |
68 | 9.13k | } |