/src/r-oss-fuzz/harnesses/datetime.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for R's date/time parsing. |
3 | | * |
4 | | * Passes fuzzed input as a character string through strptime, as.Date, |
5 | | * as.POSIXct, and as.POSIXlt -- exercising R_strptime (Rstrptime.h) and |
6 | | * src/main/datetime.c: ~2000 lines of locale-dependent parsing, timezone |
7 | | * handling, leap seconds, and DST transitions. |
8 | | * |
9 | | * Adapted from r-afl's datetime harness. |
10 | | */ |
11 | | |
12 | | #include <stdint.h> |
13 | | #include <string.h> |
14 | | |
15 | | #include "common.h" |
16 | | |
17 | 1.84k | #define FUZZ_MAX_INPUT (1024 * 64) |
18 | | |
19 | | /* |
20 | | * strptime format strings, each exercising a different path in |
21 | | * Rstrptime.h's strptime_internal (numeric fields, locale month/weekday |
22 | | * names, ISO 8601 zone offset, locale preferred representations). |
23 | | */ |
24 | 18.4k | #define N_FORMATS 6 |
25 | | static const char *strptime_formats[N_FORMATS] = { |
26 | | "%Y-%m-%d %H:%M:%S", |
27 | | "%d/%b/%Y", |
28 | | "%Y-%m-%dT%H:%M:%S%z", |
29 | | "%c", |
30 | | "%x %X", |
31 | | "%a %d %b %Y", |
32 | | }; |
33 | | |
34 | 18.4k | #define N_CALLS (N_FORMATS + 3) |
35 | | |
36 | | static SEXP x_str; |
37 | | static SEXP calls[N_CALLS]; |
38 | | |
39 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
40 | 2 | { |
41 | 2 | fuzz_init_r(); |
42 | | |
43 | 2 | SEXP sym_strptime = Rf_install("strptime"); |
44 | 2 | SEXP sym_as_Date = Rf_install("as.Date"); |
45 | 2 | SEXP sym_as_POSIXct = Rf_install("as.POSIXct"); |
46 | 2 | SEXP sym_as_POSIXlt = Rf_install("as.POSIXlt"); |
47 | | |
48 | | /* Reusable input container -- each iteration swaps its CHARSXP. */ |
49 | 2 | Rf_protect(x_str = Rf_allocVector(STRSXP, 1)); |
50 | | |
51 | 2 | SEXP tz_utc; |
52 | 2 | Rf_protect(tz_utc = Rf_mkString("UTC")); |
53 | | |
54 | | /* strptime(x, format=fmt, tz="UTC") for each fixed format */ |
55 | 14 | for (int i = 0; i < N_FORMATS; i++) { |
56 | 12 | SEXP fmt; |
57 | 12 | Rf_protect(fmt = Rf_mkString(strptime_formats[i])); |
58 | 12 | Rf_protect(calls[i] = Rf_lang4(sym_strptime, x_str, fmt, tz_utc)); |
59 | 12 | SET_TAG(CDDR(calls[i]), Rf_install("format")); |
60 | 12 | SET_TAG(CDR(CDDR(calls[i])), Rf_install("tz")); |
61 | 12 | } |
62 | | |
63 | | /* as.Date(x, format="%Y-%m-%d") */ |
64 | 2 | { |
65 | 2 | SEXP date_fmt; |
66 | 2 | Rf_protect(date_fmt = Rf_mkString("%Y-%m-%d")); |
67 | 2 | Rf_protect(calls[N_FORMATS] = Rf_lang3(sym_as_Date, x_str, date_fmt)); |
68 | 2 | SET_TAG(CDDR(calls[N_FORMATS]), Rf_install("format")); |
69 | 2 | } |
70 | | |
71 | | /* as.POSIXct(x, tz="UTC") */ |
72 | 2 | Rf_protect(calls[N_FORMATS + 1] = Rf_lang3(sym_as_POSIXct, x_str, tz_utc)); |
73 | 2 | SET_TAG(CDDR(calls[N_FORMATS + 1]), Rf_install("tz")); |
74 | | |
75 | | /* as.POSIXlt(x, tz="UTC") */ |
76 | 2 | Rf_protect(calls[N_FORMATS + 2] = Rf_lang3(sym_as_POSIXlt, x_str, tz_utc)); |
77 | 2 | SET_TAG(CDDR(calls[N_FORMATS + 2]), Rf_install("tz")); |
78 | | |
79 | | /* Warmup: prime datetime internals. */ |
80 | 2 | { |
81 | 2 | int error = 0; |
82 | 2 | SET_STRING_ELT(x_str, 0, Rf_mkChar("2024-01-15 12:30:00")); |
83 | 20 | for (int i = 0; i < N_CALLS; i++) { |
84 | 18 | R_tryEval(calls[i], R_GlobalEnv, &error); |
85 | 18 | error = 0; |
86 | 18 | } |
87 | 2 | } |
88 | | |
89 | 2 | return 0; |
90 | 2 | } |
91 | | |
92 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
93 | 1.84k | { |
94 | 1.84k | if (size == 0 || size > FUZZ_MAX_INPUT) |
95 | 7 | return 0; |
96 | | |
97 | 1.84k | char buf[FUZZ_MAX_INPUT + 1]; |
98 | 1.84k | memcpy(buf, data, size); |
99 | 1.84k | buf[size] = '\0'; |
100 | | |
101 | 1.84k | if (!fuzz_set_string(x_str, buf)) |
102 | 0 | return 0; |
103 | | |
104 | 1.84k | fuzz_eval_data_t ed = { .env = R_GlobalEnv }; |
105 | 18.4k | for (int i = 0; i < N_CALLS; i++) { |
106 | 16.5k | ed.call = calls[i]; |
107 | 16.5k | R_ToplevelExec(fuzz_do_eval, &ed); |
108 | 16.5k | } |
109 | | |
110 | 1.84k | return 0; |
111 | 1.84k | } |