Coverage Report

Created: 2026-10-28 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/r-oss-fuzz/harnesses/readtable.c
Line
Count
Source
1
/*
2
 * libFuzzer harness for read.table() and friends -- the tabular readers
3
 * most untrusted CSV enters R through.
4
 *
5
 * The scan target covers scan.c, but read.table() wraps it in C code of
6
 * its own in src/library/utils/src/io.c that scan never reaches:
7
 * readtablehead (the header/line pre-reader that tracks quotes and
8
 * comments across physical lines), countfields (count.fields), and
9
 * writetable (the quoting and encoding of write.table()).  Each input is
10
 * handed to one of those paths as the text of a file, under a
11
 * configuration chosen by the first byte; the readers that succeed hand
12
 * their data frame to write.table() so the writer sees real, fuzz-shaped
13
 * columns rather than only hand-built ones.
14
 *
15
 * Writers go to nullfile(): the C code runs in full, nothing hits the
16
 * fuzzer's stdout.
17
 */
18
19
#include <stdint.h>
20
#include <string.h>
21
22
#include "common.h"
23
24
693
#define FUZZ_MAX_INPUT (1024 * 64)
25
414
#define N_CALLS 20
26
27
/* Slot layout:
28
 *   [0,12)   read.table() and its csv/csv2/delim presets under assorted
29
 *            separators, quoting, comments, NA strings, fill/flush,
30
 *            escapes, encodings, colClasses and row.names handling
31
 *   [12,15)  count.fields() on a textConnection of the input
32
 *   [15,20)  readers chained into write.table()/write.csv(): quoting
33
 *            methods, factor levels, NA and decimal marks, line endings,
34
 *            and a re-encoding fileEncoding
35
 * Every connection a slot opens is closed before it returns: R caps the
36
 * connection table at 128 slots. */
37
static const char *const sources[N_CALLS] = {
38
    "function(x) read.table(text = x)",
39
    "function(x) read.csv(text = x)",
40
    "function(x) read.csv2(text = x)",
41
    "function(x) read.delim(text = x)",
42
    "function(x) read.table(text = x, header = TRUE, sep = \",\", "
43
    "quote = \"\\\"'\", comment.char = \"#\", fill = TRUE)",
44
    "function(x) read.table(text = x, sep = \",\", allowEscapes = TRUE, "
45
    "strip.white = TRUE, blank.lines.skip = FALSE)",
46
    "function(x) read.table(text = x, header = TRUE, row.names = 1, "
47
    "check.names = TRUE)",
48
    "function(x) read.table(text = x, colClasses = \"character\", "
49
    "nrows = 100, skip = 1, fill = TRUE)",
50
    "function(x) read.table(text = x, header = TRUE, "
51
    "na.strings = c(\"NA\", \"\", \"-\", \"?\"), stringsAsFactors = TRUE)",
52
    "function(x) read.table(text = x, flush = TRUE, numerals = \"no.loss\", "
53
    "dec = \",\")",
54
    "function(x) read.csv(text = x, encoding = \"UTF-8\", skipNul = TRUE)",
55
    "function(x) read.csv(text = x, encoding = \"latin1\", header = FALSE, "
56
    "col.names = paste0(\"V\", 1:3), fill = TRUE)",
57
    "function(x) { con <- textConnection(x); on.exit(close(con)); "
58
    "count.fields(con, sep = \",\", quote = \"\\\"\", comment.char = \"#\") }",
59
    "function(x) { con <- textConnection(x); on.exit(close(con)); "
60
    "count.fields(con, sep = \"\", blank.lines.skip = FALSE, skip = 1) }",
61
    "function(x) { con <- textConnection(x); on.exit(close(con)); "
62
    "count.fields(con, sep = \";\", quote = \"'\", comment.char = \"\") }",
63
    "function(x) { d <- read.csv(text = x, stringsAsFactors = TRUE); "
64
    "write.table(d, nullfile(), sep = \",\", qmethod = \"double\") }",
65
    "function(x) { d <- read.table(text = x, fill = TRUE); "
66
    "write.csv(d, nullfile(), row.names = FALSE, na = \"\") }",
67
    "function(x) { d <- read.csv2(text = x); "
68
    "write.table(d, nullfile(), quote = FALSE, dec = \",\", eol = \"\\r\\n\", "
69
    "col.names = NA) }",
70
    "function(x) { d <- read.delim(text = x); "
71
    "write.table(d, nullfile(), qmethod = \"escape\", quote = seq_along(d)) }",
72
    "function(x) { d <- read.csv(text = x, encoding = \"UTF-8\"); "
73
    "write.csv(d, nullfile(), fileEncoding = \"UTF-16LE\") }",
74
};
75
76
static SEXP x_text;
77
static SEXP calls[N_CALLS];
78
79
int fuzz_target_init(int *argc, char ***argv)
80
2
{
81
2
    (void)argc;
82
2
    (void)argv;
83
2
    fuzz_init_r();
84
85
    /* Reusable text container -- each iteration swaps its CHARSXP. */
86
2
    Rf_protect(x_text = Rf_allocVector(STRSXP, 1));
87
88
42
    for (int i = 0; i < N_CALLS; i++) {
89
40
        SEXP wrapper;
90
40
        Rf_protect(wrapper = fuzz_make_wrapper(sources[i]));
91
40
        Rf_protect(calls[i] = Rf_lang2(wrapper, x_text));
92
40
    }
93
94
    /* Warmup: load utils and prime read.table's method dispatch. */
95
2
    SET_STRING_ELT(x_text, 0, Rf_mkChar("a,b,c\n1,2,x\n4,NA,\"y\"\n"));
96
42
    for (int i = 0; i < N_CALLS; i++)
97
40
        fuzz_eval_silent(calls[i], R_GlobalEnv);
98
99
2
    return 0;
100
2
}
101
102
int fuzz_target_run(const uint8_t *data, size_t size)
103
347
{
104
347
    if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1))
105
17
        return 0;
106
107
330
    char buffer[FUZZ_MAX_INPUT];
108
330
    memcpy(buffer, data + 1, size - 1);
109
330
    buffer[size - 1] = '\0';
110
330
    if (!fuzz_set_string(x_text, buffer))
111
0
        return 0;
112
113
330
    fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv);
114
330
    return 0;
115
330
}