Coverage Report

Created: 2026-09-14 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}