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/connections.c
Line
Count
Source
1
/*
2
 * Exercise R's connection layer (src/main/connections.c) as a reader of
3
 * untrusted files.
4
 *
5
 * memDecompress, readBin and readLines(textConnection) already fuzz their
6
 * narrow entry points; what they never reach is the machinery around
7
 * them: the encoding= re-encoding layer (dummy_fgetc), the compressed-file
8
 * readers (gzfile/bzfile/xzfile/zstdfile and gzcon), rawConnection, the
9
 * line buffering behind readLines/readChar/scan, seek, pushBack, and the
10
 * write side (writeLines/writeChar/rawConnectionValue).  Each input is
11
 * staged in a scratch file and read back through one of those paths.
12
 *
13
 * Every slot is an R closure that closes its connection on exit: R caps
14
 * the connection table at 128, so a leak would quietly turn every later
15
 * iteration into the same "all connections are in use" error.
16
 */
17
18
#include <stdint.h>
19
#include <string.h>
20
21
#include "common.h"
22
23
2.51k
#define FUZZ_MAX_INPUT (1024 * 64)
24
25
/* Encodings handed to file(encoding=).  "" means no re-encoding, the
26
 * others go through iconv in dummy_fgetc; UTF-8-BOM and UCS-2LE take the
27
 * byte-order-mark branches. */
28
2.65k
#define N_ENCODINGS 9
29
static const char *const encodings[N_ENCODINGS] = {
30
    "", "UTF-8", "latin1", "UTF-16LE", "UTF-16BE", "UTF-16", "UCS-2LE",
31
    "CP1252", "UTF-8-BOM"
32
};
33
34
2.54k
#define N_OPENERS 4
35
static const char *const openers[N_OPENERS] = {
36
    "gzfile", "bzfile", "xzfile", "zstdfile"
37
};
38
39
/* Slot layout, in the order the calls are built below:
40
 *   [0,9)    readLines(file(path, encoding), skipNul = FALSE)
41
 *   [9,18)   readLines(..., skipNul = TRUE)
42
 *   [18,27)  readChar(file(path, "r", encoding), useBytes = TRUE)
43
 *   [27,36)  readChar(..., useBytes = FALSE)
44
 *   [36,45)  scan(file(path, encoding), what = "")
45
 *   [45,54)  open(con, "rt") then chunked readLines/seek/readChar
46
 *   [54,58)  <opener>(path, "rb") + readBin
47
 *   [58,62)  <opener>(path, "rt") + readLines
48
 *   62       gzcon(file(path, "rb")) + readBin
49
 *   63       rawConnection(x) reads, seek, readChar
50
 *   64       pushBack over a textConnection of the lines
51
 *   65       write side: rawConnection/textConnection/gzcon writers
52
 */
53
2.51k
#define N_CALLS (6 * N_ENCODINGS + 2 * N_OPENERS + 4)
54
55
static SEXP calls[N_CALLS];
56
static char *scratch_path;
57
58
/* Every wrapper takes (x, path, ...) so that fuzz_set_raw_arg can swap the
59
 * raw vector in at CADR regardless of which slot runs. */
60
static const char wrapper_lines[] =
61
    "function(x, path, enc, skip) {"
62
    "  con <- file(path, encoding = enc); on.exit(close(con));"
63
    "  readLines(con, n = 1000L, warn = FALSE, skipNul = skip) }";
64
65
static const char wrapper_chars[] =
66
    "function(x, path, enc, ub) {"
67
    "  con <- file(path, 'r', encoding = enc); on.exit(close(con));"
68
    "  readChar(con, c(1L, 10L, 100L, 1000L), useBytes = ub) }";
69
70
static const char wrapper_scan[] =
71
    "function(x, path, enc) {"
72
    "  con <- file(path, encoding = enc); on.exit(close(con));"
73
    "  scan(con, what = '', quiet = TRUE, nmax = 1000L) }";
74
75
static const char wrapper_chunks[] =
76
    "function(x, path, enc) {"
77
    "  con <- file(path, encoding = enc); open(con, 'rt'); on.exit(close(con));"
78
    "  a <- readLines(con, n = 2L, warn = FALSE); isIncomplete(con);"
79
    "  b <- readLines(con, n = 500L, warn = FALSE);"
80
    "  if (isSeekable(con)) seek(con, 0L);"
81
    "  list(a, b, readChar(con, 10L, useBytes = TRUE)) }";
82
83
static const char wrapper_compressed_bin[] =
84
    "function(x, path, opener) {"
85
    "  con <- opener(path, 'rb'); on.exit(close(con));"
86
    "  readBin(con, 'raw', 65536L) }";
87
88
static const char wrapper_compressed_text[] =
89
    "function(x, path, opener) {"
90
    "  con <- opener(path, 'rt'); on.exit(close(con));"
91
    "  readLines(con, n = 1000L, warn = FALSE) }";
92
93
static const char wrapper_gzcon[] =
94
    "function(x, path) {"
95
    "  con <- gzcon(file(path, 'rb')); on.exit(close(con));"
96
    "  readBin(con, 'raw', 65536L) }";
97
98
static const char wrapper_raw[] =
99
    "function(x, path) {"
100
    "  con <- rawConnection(x); on.exit(close(con));"
101
    "  a <- readLines(con, n = 1000L, warn = FALSE);"
102
    "  isSeekable(con); seek(con, 3L);"
103
    "  b <- readChar(con, c(5L, 50L), useBytes = TRUE);"
104
    "  seek(con, 0L, 'start');"
105
    "  list(a, b, readBin(con, 'integer', 100L, size = 2L)) }";
106
107
static const char wrapper_pushback[] =
108
    "function(x, path) {"
109
    "  con <- rawConnection(x); on.exit(close(con));"
110
    "  l <- readLines(con, n = 20L, warn = FALSE);"
111
    "  tc <- textConnection(l); on.exit(close(tc), add = TRUE);"
112
    "  pushBack(rev(l), tc, newLine = FALSE); pushBack(l, tc);"
113
    "  pushBackLength(tc); a <- readLines(tc, n = 5L, warn = FALSE);"
114
    "  clearPushBack(tc); list(a, readLines(tc, warn = FALSE)) }";
115
116
static const char wrapper_write[] =
117
    "function(x, path) {"
118
    "  l <- readLines(rc <- rawConnection(x), n = 50L, warn = FALSE); close(rc);"
119
    "  out <- rawConnection(raw(0), 'w'); on.exit(close(out));"
120
    "  writeLines(l, out); writeChar(l, out, eos = NULL); writeBin(x, out);"
121
    "  seek(out, 0L); truncate(out); writeLines(l, out, sep = '\\r\\n');"
122
    "  v <- rawConnectionValue(out);"
123
    "  tc <- textConnection(NULL, 'w'); on.exit(close(tc), add = TRUE);"
124
    "  writeLines(l, tc); cat(l, file = tc, sep = '|'); t <- textConnectionValue(tc);"
125
    "  gz <- gzcon(rawConnection(raw(0), 'w')); writeBin(x, gz); close(gz);"
126
    "  list(v, t) }";
127
128
int LLVMFuzzerInitialize(int *argc, char ***argv)
129
2
{
130
2
    (void)argc;
131
2
    (void)argv;
132
2
    fuzz_init_r();
133
134
2
    scratch_path = fuzz_scratch_file("connections");
135
2
    if (scratch_path == NULL)
136
0
        abort();
137
138
2
    SEXP placeholder, path, true_value, false_value;
139
2
    Rf_protect(placeholder = Rf_allocVector(RAWSXP, 1));
140
2
    Rf_protect(path = Rf_mkString(scratch_path));
141
2
    Rf_protect(true_value = Rf_ScalarLogical(TRUE));
142
2
    Rf_protect(false_value = Rf_ScalarLogical(FALSE));
143
144
2
    SEXP w_lines, w_chars, w_scan, w_chunks, w_cbin, w_ctext;
145
2
    Rf_protect(w_lines = fuzz_make_wrapper(wrapper_lines));
146
2
    Rf_protect(w_chars = fuzz_make_wrapper(wrapper_chars));
147
2
    Rf_protect(w_scan = fuzz_make_wrapper(wrapper_scan));
148
2
    Rf_protect(w_chunks = fuzz_make_wrapper(wrapper_chunks));
149
2
    Rf_protect(w_cbin = fuzz_make_wrapper(wrapper_compressed_bin));
150
2
    Rf_protect(w_ctext = fuzz_make_wrapper(wrapper_compressed_text));
151
152
2
    int n = 0;
153
2
    SEXP enc[N_ENCODINGS];
154
20
    for (int i = 0; i < N_ENCODINGS; i++)
155
18
        Rf_protect(enc[i] = Rf_mkString(encodings[i]));
156
157
20
    for (int i = 0; i < N_ENCODINGS; i++)
158
18
        Rf_protect(calls[n++] = Rf_lang5(w_lines, placeholder, path, enc[i],
159
18
                                         false_value));
160
20
    for (int i = 0; i < N_ENCODINGS; i++)
161
18
        Rf_protect(calls[n++] = Rf_lang5(w_lines, placeholder, path, enc[i],
162
18
                                         true_value));
163
20
    for (int i = 0; i < N_ENCODINGS; i++)
164
18
        Rf_protect(calls[n++] = Rf_lang5(w_chars, placeholder, path, enc[i],
165
18
                                         true_value));
166
20
    for (int i = 0; i < N_ENCODINGS; i++)
167
18
        Rf_protect(calls[n++] = Rf_lang5(w_chars, placeholder, path, enc[i],
168
18
                                         false_value));
169
20
    for (int i = 0; i < N_ENCODINGS; i++)
170
18
        Rf_protect(calls[n++] = Rf_lang4(w_scan, placeholder, path, enc[i]));
171
20
    for (int i = 0; i < N_ENCODINGS; i++)
172
18
        Rf_protect(calls[n++] = Rf_lang4(w_chunks, placeholder, path, enc[i]));
173
174
    /* The openers are base functions looked up once; passing the closure
175
     * itself keeps the wrapper free of any eval of a name. */
176
2
    SEXP opener[N_OPENERS];
177
10
    for (int i = 0; i < N_OPENERS; i++)
178
8
        Rf_protect(opener[i] = Rf_findFun(Rf_install(openers[i]), R_BaseEnv));
179
10
    for (int i = 0; i < N_OPENERS; i++)
180
8
        Rf_protect(calls[n++] = Rf_lang4(w_cbin, placeholder, path, opener[i]));
181
10
    for (int i = 0; i < N_OPENERS; i++)
182
8
        Rf_protect(calls[n++] = Rf_lang4(w_ctext, placeholder, path, opener[i]));
183
184
2
    SEXP w_gzcon, w_raw, w_pushback, w_write;
185
2
    Rf_protect(w_gzcon = fuzz_make_wrapper(wrapper_gzcon));
186
2
    Rf_protect(w_raw = fuzz_make_wrapper(wrapper_raw));
187
2
    Rf_protect(w_pushback = fuzz_make_wrapper(wrapper_pushback));
188
2
    Rf_protect(w_write = fuzz_make_wrapper(wrapper_write));
189
2
    Rf_protect(calls[n++] = Rf_lang3(w_gzcon, placeholder, path));
190
2
    Rf_protect(calls[n++] = Rf_lang3(w_raw, placeholder, path));
191
2
    Rf_protect(calls[n++] = Rf_lang3(w_pushback, placeholder, path));
192
2
    Rf_protect(calls[n++] = Rf_lang3(w_write, placeholder, path));
193
2
    if (n != N_CALLS)
194
0
        abort();
195
2
    return 0;
196
2
}
197
198
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
199
2.51k
{
200
2.51k
    if (size < 2 || size > FUZZ_MAX_INPUT)
201
2
        return 0;
202
203
2.51k
    int selected = data[0] % N_CALLS;
204
2.51k
    if (!fuzz_write_scratch(scratch_path, data + 1, size - 1))
205
0
        return 0;
206
2.51k
    if (!fuzz_set_raw_arg(calls[selected], data + 1, size - 1))
207
0
        return 0;
208
209
2.51k
    fuzz_eval_silent(calls[selected], R_GlobalEnv);
210
2.51k
    return 0;
211
2.51k
}