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/encoding.c
Line
Count
Source
1
/*
2
 * Exercise R's iconv wrapper (do_iconv in src/main/sysutils.c) and the
3
 * encoding-mark helpers around it with arbitrary byte strings.
4
 *
5
 * The first byte selects a call slot, the rest is the payload.  Slots 0-5
6
 * hand the payload to iconv() as a raw vector in a list (the original
7
 * shape of this target).  Later slots pass it as a character vector --
8
 * native, or the same bytes marked UTF-8 or latin1 without validation,
9
 * as Encoding<- lets users do -- which is what reaches the substitution
10
 * modes (sub=NA, "", "byte", "Unicode", "c99"), the mark= handling, the
11
 * from="" branches keyed on the CHARSXP mark, and enc2utf8/enc2native/
12
 * validUTF8/validEnc.  A few more raw-list slots use stateful or
13
 * BOM-carrying encodings so the error and restart handling in R's
14
 * conversion loop sees EINVAL as well as EILSEQ.
15
 */
16
17
#include <stdint.h>
18
#include <string.h>
19
20
#include "common.h"
21
22
1.60k
#define FUZZ_MAX_INPUT (1024 * 64)
23
34
#define N_RAW_CALLS 6
24
1.60k
#define N_CALLS 28
25
26
/* Which input a slot reads */
27
1.65k
#define IN_RAW    0
28
36
#define IN_STR    1
29
142
#define IN_UTF8   2
30
52
#define IN_LATIN1 3
31
32
static SEXP x_list;     /* list(<raw>) */
33
static SEXP x_str;      /* native character */
34
static SEXP x_utf8;     /* same bytes marked UTF-8 */
35
static SEXP x_latin1;   /* same bytes marked latin1 */
36
static SEXP calls[N_CALLS];
37
static int inputs[N_CALLS];
38
39
typedef struct {
40
    const uint8_t *data;
41
    size_t size;
42
} encoding_input_t;
43
44
static void set_encoding_input(void *data)
45
693
{
46
693
    encoding_input_t *input = data;
47
693
    SEXP raw = Rf_allocVector(RAWSXP, (R_xlen_t)input->size);
48
693
    memcpy(RAW(raw), input->data, input->size);
49
693
    SET_VECTOR_ELT(x_list, 0, raw);
50
693
}
51
52
/* iconv(x, from, to, sub, <extra>) with every argument named.  extra is
53
 * either mark= or toRaw=, which keeps each call within Rf_lang6. */
54
static SEXP iconv_call(SEXP x, const char *from, const char *to, SEXP sub,
55
                       const char *extra_name, SEXP extra)
56
46
{
57
46
    SEXP from_string, to_string, call;
58
46
    Rf_protect(from_string = Rf_mkString(from));
59
46
    Rf_protect(to_string = Rf_mkString(to));
60
46
    if (extra == NULL)
61
24
        call = Rf_lang5(Rf_install("iconv"), x, from_string, to_string, sub);
62
22
    else
63
22
        call = Rf_lang6(Rf_install("iconv"), x, from_string, to_string, sub,
64
22
                        extra);
65
46
    Rf_protect(call);
66
67
46
    SET_TAG(CDDR(call), Rf_install("from"));
68
46
    SET_TAG(CDR(CDDR(call)), Rf_install("to"));
69
46
    SET_TAG(CDR(CDR(CDDR(call))), Rf_install("sub"));
70
46
    if (extra != NULL)
71
22
        SET_TAG(CDR(CDR(CDR(CDDR(call)))), Rf_install(extra_name));
72
73
    /* The caller protects the returned call before anything allocates. */
74
46
    Rf_unprotect(3);
75
46
    return call;
76
46
}
77
78
int LLVMFuzzerInitialize(int *argc, char ***argv)
79
4
{
80
4
    (void)argc;
81
4
    (void)argv;
82
4
    fuzz_init_r();
83
84
4
    static const char *const from[N_RAW_CALLS] = {
85
4
        "UTF-8", "latin1", "CP1252", "UTF-16LE", "UTF-16BE", ""
86
4
    };
87
4
    static const char *const to[N_RAW_CALLS] = {
88
4
        "UTF-8", "UTF-8", "UTF-8", "UTF-8", "UTF-8", "ASCII//TRANSLIT"
89
4
    };
90
91
4
    Rf_protect(x_list = Rf_allocVector(VECSXP, 1));
92
4
    SET_VECTOR_ELT(x_list, 0, Rf_allocVector(RAWSXP, 0));
93
4
    Rf_protect(x_str = Rf_allocVector(STRSXP, 1));
94
4
    Rf_protect(x_utf8 = Rf_allocVector(STRSXP, 1));
95
4
    Rf_protect(x_latin1 = Rf_allocVector(STRSXP, 1));
96
97
4
    SEXP sub_byte, sub_unicode, sub_c99, sub_empty, sub_na, sub_qm;
98
4
    SEXP true_val, false_val;
99
4
    Rf_protect(sub_byte = Rf_mkString("byte"));
100
4
    Rf_protect(sub_unicode = Rf_mkString("Unicode"));
101
4
    Rf_protect(sub_c99 = Rf_mkString("c99"));
102
4
    Rf_protect(sub_empty = Rf_mkString(""));
103
4
    Rf_protect(sub_na = Rf_ScalarString(NA_STRING));
104
4
    Rf_protect(sub_qm = Rf_mkString("?"));
105
4
    Rf_protect(true_val = Rf_ScalarLogical(TRUE));
106
4
    Rf_protect(false_val = Rf_ScalarLogical(FALSE));
107
108
4
    int k = 0;
109
34
    for (int i = 0; i < N_RAW_CALLS; i++) {
110
30
        Rf_protect(calls[k] = iconv_call(x_list, from[i], to[i], sub_byte,
111
30
                                         "toRaw", true_val));
112
30
        inputs[k++] = IN_RAW;
113
30
    }
114
115
    /* Character input, explicit from: the substitution modes */
116
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "latin1", sub_byte,
117
4
                                     NULL, NULL));
118
4
    inputs[k++] = IN_STR;
119
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "ASCII", sub_unicode,
120
4
                                     NULL, NULL));
121
4
    inputs[k++] = IN_STR;
122
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "ASCII", sub_c99,
123
4
                                     NULL, NULL));
124
4
    inputs[k++] = IN_STR;
125
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "UTF-16LE", sub_byte,
126
4
                                     "toRaw", true_val));
127
4
    inputs[k++] = IN_STR;
128
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "latin1", sub_na,
129
4
                                     NULL, NULL));
130
4
    inputs[k++] = IN_STR;
131
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "ASCII", sub_empty,
132
4
                                     NULL, NULL));
133
4
    inputs[k++] = IN_STR;
134
135
    /* from="" with marked input: do_iconv keys the converter on the
136
     * CHARSXP mark, with its own fallback substitutions */
137
4
    Rf_protect(calls[k] = iconv_call(x_utf8, "", "latin1", sub_qm,
138
4
                                     "mark", true_val));
139
4
    inputs[k++] = IN_UTF8;
140
4
    Rf_protect(calls[k] = iconv_call(x_latin1, "", "UTF-8", sub_byte,
141
4
                                     "mark", true_val));
142
4
    inputs[k++] = IN_LATIN1;
143
4
    Rf_protect(calls[k] = iconv_call(x_utf8, "", "ASCII//TRANSLIT", sub_byte,
144
4
                                     NULL, NULL));
145
4
    inputs[k++] = IN_UTF8;
146
4
    Rf_protect(calls[k] = iconv_call(x_str, "UTF-8", "UTF-8", sub_unicode,
147
4
                                     NULL, NULL));
148
4
    inputs[k++] = IN_STR;
149
4
    Rf_protect(calls[k] = iconv_call(x_latin1, "latin1", "UTF-8", sub_byte,
150
4
                                     "mark", false_val));
151
4
    inputs[k++] = IN_LATIN1;
152
153
    /* The mark helpers around iconv */
154
4
    Rf_protect(calls[k] = Rf_lang2(Rf_install("enc2utf8"), x_latin1));
155
4
    inputs[k++] = IN_LATIN1;
156
4
    Rf_protect(calls[k] = Rf_lang2(Rf_install("enc2native"), x_utf8));
157
4
    inputs[k++] = IN_UTF8;
158
4
    Rf_protect(calls[k] = Rf_lang2(Rf_install("validUTF8"), x_str));
159
4
    inputs[k++] = IN_STR;
160
4
    Rf_protect(calls[k] = Rf_lang2(Rf_install("validEnc"), x_utf8));
161
4
    inputs[k++] = IN_UTF8;
162
4
    {
163
4
        SEXP latin1, set_enc;
164
4
        Rf_protect(latin1 = Rf_mkString("latin1"));
165
4
        Rf_protect(set_enc = Rf_lang3(Rf_install("Encoding<-"), x_str, latin1));
166
4
        Rf_protect(calls[k] = Rf_lang2(Rf_install("enc2utf8"), set_enc));
167
4
        inputs[k++] = IN_STR;
168
4
    }
169
170
    /* Raw input through decoders with BOMs and shift states */
171
4
    Rf_protect(calls[k] = iconv_call(x_list, "UTF-16", "UTF-8", sub_byte,
172
4
                                     NULL, NULL));
173
4
    inputs[k++] = IN_RAW;
174
4
    Rf_protect(calls[k] = iconv_call(x_list, "UTF-32", "UTF-8", sub_byte,
175
4
                                     NULL, NULL));
176
4
    inputs[k++] = IN_RAW;
177
4
    Rf_protect(calls[k] = iconv_call(x_list, "UTF-7", "UTF-8", sub_byte,
178
4
                                     NULL, NULL));
179
4
    inputs[k++] = IN_RAW;
180
4
    Rf_protect(calls[k] = iconv_call(x_list, "ISO-2022-JP", "UTF-8", sub_byte,
181
4
                                     NULL, NULL));
182
4
    inputs[k++] = IN_RAW;
183
4
    Rf_protect(calls[k] = iconv_call(x_list, "GB18030", "UTF-8", sub_byte,
184
4
                                     NULL, NULL));
185
4
    inputs[k++] = IN_RAW;
186
4
    Rf_protect(calls[k] = iconv_call(x_list, "UTF-8", "UTF-16", sub_byte,
187
4
                                     "toRaw", true_val));
188
4
    inputs[k++] = IN_RAW;
189
190
4
    if (k != N_CALLS)
191
0
        abort();
192
193
4
    SET_STRING_ELT(x_str, 0, Rf_mkChar("fuzz"));
194
4
    SET_STRING_ELT(x_utf8, 0, Rf_mkCharCE("fuzz", CE_UTF8));
195
4
    SET_STRING_ELT(x_latin1, 0, Rf_mkCharCE("fuzz", CE_LATIN1));
196
4
    return 0;
197
4
}
198
199
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
200
1.60k
{
201
1.60k
    if (size < 2 || size > FUZZ_MAX_INPUT)
202
6
        return 0;
203
204
1.60k
    int selected = data[0] % N_CALLS;
205
206
1.60k
    if (inputs[selected] == IN_RAW) {
207
693
        encoding_input_t input = { data + 1, size - 1 };
208
693
        if (!R_ToplevelExec(set_encoding_input, &input))
209
0
            return 0;
210
907
    } else {
211
        /* Character inputs cannot carry a NUL. */
212
907
        if (memchr(data + 1, '\0', size - 1))
213
8
            return 0;
214
215
899
        char buffer[FUZZ_MAX_INPUT];
216
899
        memcpy(buffer, data + 1, size - 1);
217
899
        buffer[size - 1] = '\0';
218
219
899
        Rboolean ok;
220
899
        switch (inputs[selected]) {
221
126
        case IN_UTF8:
222
126
            ok = fuzz_set_string_ce(x_utf8, buffer, CE_UTF8);
223
126
            break;
224
40
        case IN_LATIN1:
225
40
            ok = fuzz_set_string_ce(x_latin1, buffer, CE_LATIN1);
226
40
            break;
227
733
        default:
228
733
            ok = fuzz_set_string(x_str, buffer);
229
733
            break;
230
899
        }
231
899
        if (!ok)
232
0
            return 0;
233
899
    }
234
235
1.59k
    fuzz_eval_silent(calls[selected], R_GlobalEnv);
236
1.59k
    return 0;
237
1.60k
}