Coverage Report

Created: 2026-10-16 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
344
#define FUZZ_MAX_INPUT (1024 * 64)
25
338
#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
344
{
60
344
    if (size < 2 || size > FUZZ_MAX_INPUT)
61
12
        return 0;
62
63
332
    if (!fuzz_write_scratch(scratch_path, data + 1, size - 1))
64
0
        return 0;
65
66
332
    fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv);
67
332
    return 0;
68
332
}