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/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
9.17k
#define FUZZ_MAX_INPUT (1024 * 64)
25
26
static SEXP calls[4];
27
28
int LLVMFuzzerInitialize(int *argc, char ***argv)
29
6
{
30
6
    fuzz_init_r();
31
32
6
    SEXP sym_memDecompress = Rf_install("memDecompress");
33
34
6
    SEXP str_gzip, str_bzip2, str_xz, str_unknown;
35
6
    Rf_protect(str_gzip    = Rf_mkString("gzip"));
36
6
    Rf_protect(str_bzip2   = Rf_mkString("bzip2"));
37
6
    Rf_protect(str_xz      = Rf_mkString("xz"));
38
6
    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
6
    SEXP placeholder;
43
6
    Rf_protect(placeholder = Rf_allocVector(RAWSXP, 1));
44
45
6
    SEXP type_strs[4] = { str_gzip, str_bzip2, str_xz, str_unknown };
46
66
    for (int i = 0; i < 4; i++) {
47
60
        Rf_protect(calls[i] = Rf_lang3(sym_memDecompress, placeholder,
48
60
                                       type_strs[i]));
49
60
        SET_TAG(CDDR(calls[i]), Rf_install("type"));
50
60
    }
51
52
    /* Warmup: a minimal gzip stream (1f 8b header + empty deflate). */
53
6
    {
54
6
        int error = 0;
55
6
        unsigned char gz[] = {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
56
6
                              0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
57
6
                              0x00, 0x00, 0x00, 0x00};
58
6
        SEXP w_raw;
59
6
        Rf_protect(w_raw = Rf_allocVector(RAWSXP, sizeof(gz)));
60
6
        memcpy(RAW(w_raw), gz, sizeof(gz));
61
70
        for (int i = 0; i < 4; i++) {
62
64
            SETCADR(calls[i], w_raw);
63
64
            R_tryEval(calls[i], R_GlobalEnv, &error);
64
64
            error = 0;
65
64
        }
66
6
        Rf_unprotect(1); /* w_raw */
67
6
    }
68
69
6
    return 0;
70
6
}
71
72
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
73
9.17k
{
74
9.17k
    if (size < 2 || size > FUZZ_MAX_INPUT)
75
40
        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
9.13k
    int idx = data[0] & 0x03;
81
82
9.13k
    if (!fuzz_set_raw_arg(calls[idx], data + 1, size - 1))
83
0
        return 0;
84
85
9.13k
    fuzz_eval_data_t ed = { .call = calls[idx], .env = R_GlobalEnv };
86
9.13k
    R_ToplevelExec(fuzz_do_eval, &ed);
87
88
9.13k
    return 0;
89
9.13k
}