/src/r-oss-fuzz/harnesses/decompress.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for R's memDecompress / compression wrappers. |
3 | | * |
4 | | * Feeds raw bytes to memDecompress() for each supported type: gzip, |
5 | | * bzip2, xz, and "unknown" (magic-byte auto-detection). The target is |
6 | | * not the compression libraries (OSS-Fuzz fuzzes those directly) but R's |
7 | | * wrapper layer in src/main/connections.c: buffer sizing, error recovery, |
8 | | * and R_alloc output handling. |
9 | | * |
10 | | * Adapted from r-afl's decompress harness. |
11 | | */ |
12 | | |
13 | | #include <stdint.h> |
14 | | #include <string.h> |
15 | | |
16 | | /* Legitimate small inputs expand past 1GB here (a 64KB bzip2 stream of |
17 | | * zeros decompresses to ~1.25GB, and memDecompress keeps its doubling |
18 | | * retries live on the R_alloc stack), so the shared 1Gb default would |
19 | | * cut off exactly the buffer-sizing paths this target exists to cover. */ |
20 | 1 | #define FUZZ_R_MAX_VSIZE "4Gb" |
21 | | |
22 | | #include "common.h" |
23 | | |
24 | 633 | #define FUZZ_MAX_INPUT (1024 * 64) |
25 | | |
26 | | static SEXP calls[4]; |
27 | | |
28 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
29 | 2 | { |
30 | 2 | fuzz_init_r(); |
31 | | |
32 | 2 | SEXP sym_memDecompress = Rf_install("memDecompress"); |
33 | | |
34 | 2 | SEXP str_gzip, str_bzip2, str_xz, str_unknown; |
35 | 2 | Rf_protect(str_gzip = Rf_mkString("gzip")); |
36 | 2 | Rf_protect(str_bzip2 = Rf_mkString("bzip2")); |
37 | 2 | Rf_protect(str_xz = Rf_mkString("xz")); |
38 | 2 | Rf_protect(str_unknown = Rf_mkString("unknown")); |
39 | | |
40 | | /* One call per compression type, sharing a placeholder RAWSXP that |
41 | | * each iteration swaps out via SETCADR. */ |
42 | 2 | SEXP placeholder; |
43 | 2 | Rf_protect(placeholder = Rf_allocVector(RAWSXP, 1)); |
44 | | |
45 | 2 | SEXP type_strs[4] = { str_gzip, str_bzip2, str_xz, str_unknown }; |
46 | 10 | for (int i = 0; i < 4; i++) { |
47 | 8 | Rf_protect(calls[i] = Rf_lang3(sym_memDecompress, placeholder, |
48 | 8 | type_strs[i])); |
49 | 8 | SET_TAG(CDDR(calls[i]), Rf_install("type")); |
50 | 8 | } |
51 | | |
52 | | /* Warmup: a minimal gzip stream (1f 8b header + empty deflate). */ |
53 | 2 | { |
54 | 2 | int error = 0; |
55 | 2 | unsigned char gz[] = {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, |
56 | 2 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, |
57 | 2 | 0x00, 0x00, 0x00, 0x00}; |
58 | 2 | SEXP w_raw; |
59 | 2 | Rf_protect(w_raw = Rf_allocVector(RAWSXP, sizeof(gz))); |
60 | 2 | memcpy(RAW(w_raw), gz, sizeof(gz)); |
61 | 10 | for (int i = 0; i < 4; i++) { |
62 | 8 | SETCADR(calls[i], w_raw); |
63 | 8 | R_tryEval(calls[i], R_GlobalEnv, &error); |
64 | 8 | error = 0; |
65 | 8 | } |
66 | 2 | Rf_unprotect(1); /* w_raw */ |
67 | 2 | } |
68 | | |
69 | 2 | return 0; |
70 | 2 | } |
71 | | |
72 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
73 | 634 | { |
74 | 634 | if (size < 2 || size > FUZZ_MAX_INPUT) |
75 | 9 | return 0; |
76 | | |
77 | | /* First byte selects the decompressor; the rest is the payload. |
78 | | * One type per input keeps libFuzzer's coverage feedback clean and |
79 | | * avoids cross-library state bleed. */ |
80 | 625 | int idx = data[0] & 0x03; |
81 | | |
82 | 625 | if (!fuzz_set_raw_arg(calls[idx], data + 1, size - 1)) |
83 | 0 | return 0; |
84 | | |
85 | 625 | fuzz_eval_data_t ed = { .call = calls[idx], .env = R_GlobalEnv }; |
86 | 625 | R_ToplevelExec(fuzz_do_eval, &ed); |
87 | | |
88 | 625 | return 0; |
89 | 625 | } |