Skip to content

Commit 620f864

Browse files
kaoudisShaun Mirani
authored andcommitted
fuzzer: add several new CURLOPT types and the ability to generate them
Plus bump the size of FUZZ_CURLOPT_TRACKER_SPACE so that the main fuzz harness uses memory for tracking CURLOPTs correctly. Co-authored-by: Shaun Mirani <[email protected]>
1 parent 2adcb39 commit 620f864

File tree

5 files changed

+185
-45
lines changed

5 files changed

+185
-45
lines changed

corpus.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ class BaseType(object):
4848
TYPE_MAIL_AUTH = 39
4949
TYPE_HTTP_VERSION = 40
5050
TYPE_DOH_URL = 41
51+
TYPE_LOGIN_OPTIONS = 42
52+
TYPE_XOAUTH2_BEARER = 43
53+
TYPE_USERPWD = 44
54+
TYPE_USERAGENT = 45
55+
TYPE_NETRC = 46
56+
TYPE_SSH_HOST_PUBLIC_KEY_SHA256 = 47
57+
TYPE_POST = 48
58+
TYPE_WS_OPTIONS = 49
59+
TYPE_CONNECT_ONLY = 50
60+
TYPE_HSTS = 51
61+
TYPE_HTTPPOSTBODY = 52 # https://curl.se/libcurl/c/CURLOPT_HTTPPOST.html
5162

5263
TYPEMAP = {
5364
TYPE_URL: "CURLOPT_URL",
@@ -91,6 +102,17 @@ class BaseType(object):
91102
TYPE_MAIL_AUTH: "CURLOPT_MAIL_AUTH",
92103
TYPE_HTTP_VERSION: "CURLOPT_HTTP_VERSION",
93104
TYPE_DOH_URL: "CURLOPT_DOH_URL",
105+
TYPE_LOGIN_OPTIONS: "CURLOPT_LOGIN_OPTIONS",
106+
TYPE_XOAUTH2_BEARER: "CURLOPT_XOAUTH2_BEARER",
107+
TYPE_USERPWD: "CURLOPT_USERPWD",
108+
TYPE_USERAGENT: "CURLOPT_USERAGENT",
109+
TYPE_NETRC: "CURLOPT_NETRC",
110+
TYPE_SSH_HOST_PUBLIC_KEY_SHA256: "CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256",
111+
TYPE_POST: "CURLOPT_POST",
112+
TYPE_WS_OPTIONS: "CURLOPT_WS_OPTIONS",
113+
TYPE_CONNECT_ONLY: "CURLOPT_CONNECT_ONLY",
114+
TYPE_HSTS: "CURLOPT_HSTS",
115+
TYPE_HTTPPOSTBODY: "CURLOPT_HTTPPOST",
94116
}
95117

96118

curl_fuzzer.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
8989
curl_easy_setopt(fuzz.easy, CURLOPT_MIMEPOST, fuzz.mime);
9090
}
9191

92+
if (fuzz.httppost != NULL) {
93+
curl_easy_setopt(fuzz.easy, CURLOPT_HTTPPOST, fuzz.httppost);
94+
}
95+
9296
/* Run the transfer. */
9397
fuzz_handle_transfer(&fuzz);
9498

@@ -252,6 +256,20 @@ void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz)
252256
curl_easy_cleanup(fuzz->easy);
253257
fuzz->easy = NULL;
254258
}
259+
260+
/* When you have passed the struct curl_httppost pointer to curl_easy_setopt
261+
* (using the CURLOPT_HTTPPOST option), you must not free the list until after
262+
* you have called curl_easy_cleanup for the curl handle.
263+
* https://curl.se/libcurl/c/curl_formadd.html */
264+
if (fuzz->httppost != NULL) {
265+
curl_formfree(fuzz->httppost);
266+
fuzz->httppost = NULL;
267+
}
268+
269+
// free after httppost and last_post_part.
270+
if (fuzz->post_body != NULL) {
271+
fuzz_free((void **)&fuzz->post_body);
272+
}
255273
}
256274

257275
/**

curl_fuzzer.h

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,47 +26,58 @@
2626
/**
2727
* TLV types.
2828
*/
29-
#define TLV_TYPE_URL 1
30-
#define TLV_TYPE_RESPONSE0 2
31-
#define TLV_TYPE_USERNAME 3
32-
#define TLV_TYPE_PASSWORD 4
33-
#define TLV_TYPE_POSTFIELDS 5
34-
#define TLV_TYPE_HEADER 6
35-
#define TLV_TYPE_COOKIE 7
36-
#define TLV_TYPE_UPLOAD1 8
37-
#define TLV_TYPE_RANGE 9
38-
#define TLV_TYPE_CUSTOMREQUEST 10
39-
#define TLV_TYPE_MAIL_RECIPIENT 11
40-
#define TLV_TYPE_MAIL_FROM 12
41-
#define TLV_TYPE_MIME_PART 13
42-
#define TLV_TYPE_MIME_PART_NAME 14
43-
#define TLV_TYPE_MIME_PART_DATA 15
44-
#define TLV_TYPE_HTTPAUTH 16
45-
#define TLV_TYPE_RESPONSE1 17
46-
#define TLV_TYPE_RESPONSE2 18
47-
#define TLV_TYPE_RESPONSE3 19
48-
#define TLV_TYPE_RESPONSE4 20
49-
#define TLV_TYPE_RESPONSE5 21
50-
#define TLV_TYPE_RESPONSE6 22
51-
#define TLV_TYPE_RESPONSE7 23
52-
#define TLV_TYPE_RESPONSE8 24
53-
#define TLV_TYPE_RESPONSE9 25
54-
#define TLV_TYPE_RESPONSE10 26
55-
#define TLV_TYPE_OPTHEADER 27
56-
#define TLV_TYPE_NOBODY 28
57-
#define TLV_TYPE_FOLLOWLOCATION 29
58-
#define TLV_TYPE_ACCEPTENCODING 30
59-
#define TLV_TYPE_SECOND_RESPONSE0 31
60-
#define TLV_TYPE_SECOND_RESPONSE1 32
61-
#define TLV_TYPE_WILDCARDMATCH 33
62-
#define TLV_TYPE_RTSP_REQUEST 34
63-
#define TLV_TYPE_RTSP_SESSION_ID 35
64-
#define TLV_TYPE_RTSP_STREAM_URI 36
65-
#define TLV_TYPE_RTSP_TRANSPORT 37
66-
#define TLV_TYPE_RTSP_CLIENT_CSEQ 38
67-
#define TLV_TYPE_MAIL_AUTH 39
68-
#define TLV_TYPE_HTTP_VERSION 40
69-
#define TLV_TYPE_DOH_URL 41
29+
#define TLV_TYPE_URL 1
30+
#define TLV_TYPE_RESPONSE0 2
31+
#define TLV_TYPE_USERNAME 3
32+
#define TLV_TYPE_PASSWORD 4
33+
#define TLV_TYPE_POSTFIELDS 5
34+
#define TLV_TYPE_HEADER 6
35+
#define TLV_TYPE_COOKIE 7
36+
#define TLV_TYPE_UPLOAD1 8
37+
#define TLV_TYPE_RANGE 9
38+
#define TLV_TYPE_CUSTOMREQUEST 10
39+
#define TLV_TYPE_MAIL_RECIPIENT 11
40+
#define TLV_TYPE_MAIL_FROM 12
41+
#define TLV_TYPE_MIME_PART 13
42+
#define TLV_TYPE_MIME_PART_NAME 14
43+
#define TLV_TYPE_MIME_PART_DATA 15
44+
#define TLV_TYPE_HTTPAUTH 16
45+
#define TLV_TYPE_RESPONSE1 17
46+
#define TLV_TYPE_RESPONSE2 18
47+
#define TLV_TYPE_RESPONSE3 19
48+
#define TLV_TYPE_RESPONSE4 20
49+
#define TLV_TYPE_RESPONSE5 21
50+
#define TLV_TYPE_RESPONSE6 22
51+
#define TLV_TYPE_RESPONSE7 23
52+
#define TLV_TYPE_RESPONSE8 24
53+
#define TLV_TYPE_RESPONSE9 25
54+
#define TLV_TYPE_RESPONSE10 26
55+
#define TLV_TYPE_OPTHEADER 27
56+
#define TLV_TYPE_NOBODY 28
57+
#define TLV_TYPE_FOLLOWLOCATION 29
58+
#define TLV_TYPE_ACCEPTENCODING 30
59+
#define TLV_TYPE_SECOND_RESPONSE0 31
60+
#define TLV_TYPE_SECOND_RESPONSE1 32
61+
#define TLV_TYPE_WILDCARDMATCH 33
62+
#define TLV_TYPE_RTSP_REQUEST 34
63+
#define TLV_TYPE_RTSP_SESSION_ID 35
64+
#define TLV_TYPE_RTSP_STREAM_URI 36
65+
#define TLV_TYPE_RTSP_TRANSPORT 37
66+
#define TLV_TYPE_RTSP_CLIENT_CSEQ 38
67+
#define TLV_TYPE_MAIL_AUTH 39
68+
#define TLV_TYPE_HTTP_VERSION 40
69+
#define TLV_TYPE_DOH_URL 41
70+
#define TLV_TYPE_LOGIN_OPTIONS 42
71+
#define TLV_TYPE_XOAUTH2_BEARER 43
72+
#define TLV_TYPE_USERPWD 44
73+
#define TLV_TYPE_USERAGENT 45
74+
#define TLV_TYPE_NETRC 46
75+
#define TLV_TYPE_SSH_HOST_PUBLIC_KEY_SHA256 47
76+
#define TLV_TYPE_POST 48
77+
#define TLV_TYPE_WS_OPTIONS 49
78+
#define TLV_TYPE_CONNECT_ONLY 50
79+
#define TLV_TYPE_HSTS 51
80+
#define TLV_TYPE_HTTPPOSTBODY 52
7081

7182
/**
7283
* TLV function return codes.
@@ -81,6 +92,9 @@
8192
/* Maximum write size in bytes to stop unbounded writes (50MB) */
8293
#define MAXIMUM_WRITE_LENGTH 52428800
8394

95+
/* convenience string for HTTPPOST body name */
96+
#define FUZZ_HTTPPOST_NAME "test"
97+
8498
/* Cookie-jar path. */
8599
#define FUZZ_COOKIE_JAR_PATH "/dev/null"
86100

@@ -91,7 +105,7 @@
91105
#define TLV_MAX_NUM_CURLOPT_HEADER 2000
92106

93107
/* Space variable for all CURLOPTs. */
94-
#define FUZZ_CURLOPT_TRACKER_SPACE 300
108+
#define FUZZ_CURLOPT_TRACKER_SPACE 500
95109

96110
/* Number of connections allowed to be opened */
97111
#define FUZZ_NUM_CONNECTIONS 2
@@ -211,6 +225,11 @@ typedef struct fuzz_data
211225
curl_mime *mime;
212226
curl_mimepart *part;
213227

228+
/* httppost data */
229+
struct curl_httppost *httppost;
230+
struct curl_httppost *last_post_part;
231+
char *post_body;
232+
214233
/* Server socket managers. Primarily socket manager 0 is used, but some
215234
protocols (FTP) use two sockets. */
216235
FUZZ_SOCKET_MANAGER sockman[FUZZ_NUM_CONNECTIONS];
@@ -248,7 +267,7 @@ int fuzz_get_next_tlv(FUZZ_DATA *fuzz, TLV *tlv);
248267
int fuzz_get_tlv_comn(FUZZ_DATA *fuzz, TLV *tlv);
249268
int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv);
250269
char *fuzz_tlv_to_string(TLV *tlv);
251-
270+
void fuzz_setup_http_post(FUZZ_DATA *fuzz, TLV *tlv);
252271
int fuzz_add_mime_part(TLV *src_tlv, curl_mimepart *part);
253272
int fuzz_parse_mime_tlv(curl_mimepart *part, TLV *tlv);
254273
int fuzz_handle_transfer(FUZZ_DATA *fuzz);

curl_fuzzer_tlv.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv)
162162

163163
/* This TLV may have sub TLVs. */
164164
fuzz_add_mime_part(tlv, fuzz->part);
165+
165166
break;
166167

167168
case TLV_TYPE_POSTFIELDS:
@@ -170,6 +171,12 @@ int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv)
170171
FSET_OPTION(fuzz, CURLOPT_POSTFIELDS, fuzz->postfields);
171172
break;
172173

174+
case TLV_TYPE_HTTPPOSTBODY:
175+
FCHECK_OPTION_UNSET(fuzz, CURLOPT_HTTPPOST);
176+
fuzz_setup_http_post(fuzz, tlv);
177+
FSET_OPTION(fuzz, CURLOPT_HTTPPOST, fuzz->httppost);
178+
break;
179+
173180
/* Define a set of u32 options. */
174181
FU32TLV(fuzz, TLV_TYPE_HTTPAUTH, CURLOPT_HTTPAUTH);
175182
FU32TLV(fuzz, TLV_TYPE_OPTHEADER, CURLOPT_HEADER);
@@ -179,6 +186,10 @@ int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv)
179186
FU32TLV(fuzz, TLV_TYPE_RTSP_REQUEST, CURLOPT_RTSP_REQUEST);
180187
FU32TLV(fuzz, TLV_TYPE_RTSP_CLIENT_CSEQ, CURLOPT_RTSP_CLIENT_CSEQ);
181188
FU32TLV(fuzz, TLV_TYPE_HTTP_VERSION, CURLOPT_HTTP_VERSION);
189+
FU32TLV(fuzz, TLV_TYPE_NETRC, CURLOPT_NETRC);
190+
FU32TLV(fuzz, TLV_TYPE_WS_OPTIONS, CURLOPT_WS_OPTIONS);
191+
FU32TLV(fuzz, TLV_TYPE_CONNECT_ONLY, CURLOPT_CONNECT_ONLY);
192+
FU32TLV(fuzz, TLV_TYPE_POST, CURLOPT_POST);
182193

183194
/* Define a set of singleton TLVs - they can only have their value set once
184195
and all follow the same pattern. */
@@ -195,6 +206,12 @@ int fuzz_parse_tlv(FUZZ_DATA *fuzz, TLV *tlv)
195206
FSINGLETONTLV(fuzz, TLV_TYPE_RTSP_STREAM_URI, CURLOPT_RTSP_STREAM_URI);
196207
FSINGLETONTLV(fuzz, TLV_TYPE_RTSP_TRANSPORT, CURLOPT_RTSP_TRANSPORT);
197208
FSINGLETONTLV(fuzz, TLV_TYPE_MAIL_AUTH, CURLOPT_MAIL_AUTH);
209+
FSINGLETONTLV(fuzz, TLV_TYPE_LOGIN_OPTIONS, CURLOPT_LOGIN_OPTIONS);
210+
FSINGLETONTLV(fuzz, TLV_TYPE_XOAUTH2_BEARER, CURLOPT_XOAUTH2_BEARER);
211+
FSINGLETONTLV(fuzz, TLV_TYPE_USERPWD, CURLOPT_USERPWD);
212+
FSINGLETONTLV(fuzz, TLV_TYPE_USERAGENT, CURLOPT_USERAGENT);
213+
FSINGLETONTLV(fuzz, TLV_TYPE_SSH_HOST_PUBLIC_KEY_SHA256, CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256);
214+
FSINGLETONTLV(fuzz, TLV_TYPE_HSTS, CURLOPT_HSTS);
198215

199216
default:
200217
/* The fuzzer generates lots of unknown TLVs - we don't want these in the
@@ -231,6 +248,32 @@ char *fuzz_tlv_to_string(TLV *tlv)
231248
return tlvstr;
232249
}
233250

251+
/* set up for CURLOPT_HTTPPOST, an alternative API to CURLOPT_MIMEPOST */
252+
void fuzz_setup_http_post(FUZZ_DATA *fuzz, TLV *tlv)
253+
{
254+
if (fuzz->httppost == NULL) {
255+
struct curl_httppost *post = NULL;
256+
struct curl_httppost *last = NULL;
257+
258+
fuzz->post_body = fuzz_tlv_to_string(tlv);
259+
260+
/* This is just one of several possible entrypoints to
261+
* the HTTPPOST API. see https://curl.se/libcurl/c/curl_formadd.html
262+
* for lots of others which could be added here.
263+
*/
264+
curl_formadd(&post, &last,
265+
CURLFORM_COPYNAME, FUZZ_HTTPPOST_NAME,
266+
CURLFORM_PTRCONTENTS, fuzz->post_body,
267+
CURLFORM_CONTENTLEN, (curl_off_t) strlen(fuzz->post_body),
268+
CURLFORM_END);
269+
270+
fuzz->last_post_part = last;
271+
fuzz->httppost = post;
272+
}
273+
274+
return;
275+
}
276+
234277
/**
235278
* Extract the values from the TLV.
236279
*/

generate_corpus.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import sys
99
import corpus
10+
from corpus_curl_opt_http_auth import CurlOptHttpAuth
1011
log = logging.getLogger(__name__)
1112

1213

@@ -44,6 +45,7 @@ def generate_corpus(options):
4445
enc.maybe_write_string(enc.TYPE_USERNAME, options.username)
4546
enc.maybe_write_string(enc.TYPE_PASSWORD, options.password)
4647
enc.maybe_write_string(enc.TYPE_POSTFIELDS, options.postfields)
48+
enc.maybe_write_string(enc.TYPE_HTTPPOSTBODY, options.postbody)
4749
enc.maybe_write_string(enc.TYPE_COOKIE, options.cookie)
4850
enc.maybe_write_string(enc.TYPE_RANGE, options.range)
4951
enc.maybe_write_string(enc.TYPE_CUSTOMREQUEST, options.customrequest)
@@ -53,15 +55,40 @@ def generate_corpus(options):
5355
enc.maybe_write_string(enc.TYPE_RTSP_STREAM_URI, options.rtspstreamuri)
5456
enc.maybe_write_string(enc.TYPE_RTSP_TRANSPORT, options.rtsptransport)
5557
enc.maybe_write_string(enc.TYPE_MAIL_AUTH, options.mailauth)
58+
enc.maybe_write_string(enc.TYPE_LOGIN_OPTIONS, options.loginoptions)
59+
enc.maybe_write_string(enc.TYPE_XOAUTH2_BEARER, options.bearertoken)
60+
enc.maybe_write_string(enc.TYPE_USERPWD, options.user_and_pass)
61+
enc.maybe_write_string(enc.TYPE_USERAGENT, options.useragent)
62+
enc.maybe_write_string(enc.TYPE_SSH_HOST_PUBLIC_KEY_SHA256, options.hostpksha256)
63+
enc.maybe_write_string(enc.TYPE_HSTS, options.hsts)
5664

57-
enc.maybe_write_u32(enc.TYPE_HTTPAUTH, options.httpauth)
5865
enc.maybe_write_u32(enc.TYPE_OPTHEADER, options.optheader)
5966
enc.maybe_write_u32(enc.TYPE_NOBODY, options.nobody)
6067
enc.maybe_write_u32(enc.TYPE_FOLLOWLOCATION, options.followlocation)
6168
enc.maybe_write_u32(enc.TYPE_WILDCARDMATCH, options.wildcardmatch)
6269
enc.maybe_write_u32(enc.TYPE_RTSP_REQUEST, options.rtsprequest)
6370
enc.maybe_write_u32(enc.TYPE_RTSP_CLIENT_CSEQ, options.rtspclientcseq)
6471
enc.maybe_write_u32(enc.TYPE_HTTP_VERSION, options.httpversion)
72+
enc.maybe_write_u32(enc.TYPE_NETRC, options.netrclevel)
73+
enc.maybe_write_u32(enc.TYPE_CONNECT_ONLY, options.connectonly)
74+
75+
if options.httpauth:
76+
# translate a string HTTP auth name to an unsigned long bitmask
77+
# value in the format CURLOPT_HTTPAUTH expects
78+
log.debug(f"Mapping provided CURLOPT_HTTPAUTH='{options.httpauth}' "
79+
f"to {CurlOptHttpAuth[options.httpauth].value}L (ulong)")
80+
http_auth_value = CurlOptHttpAuth[options.httpauth].value
81+
enc.maybe_write_u32(enc.TYPE_HTTPAUTH, http_auth_value)
82+
83+
if options.wsoptions:
84+
# can only be 1 or unset currently.
85+
# https://curl.se/libcurl/c/CURLOPT_WS_OPTIONS.html
86+
enc.write_u32(enc.TYPE_WS_OPTIONS, 1)
87+
88+
if options.post:
89+
# can only be set to 1 or unset
90+
# https://curl.se/libcurl/c/CURLOPT_POST.html
91+
enc.write_u32(enc.TYPE_POST, 1)
6592

6693
# Write the first upload to the file.
6794
if options.upload1:
@@ -96,14 +123,15 @@ def get_options():
96123
parser.add_argument("--username")
97124
parser.add_argument("--password")
98125
parser.add_argument("--postfields")
126+
parser.add_argument("--postbody", type=str)
99127
parser.add_argument("--header", action="append")
100128
parser.add_argument("--cookie")
101129
parser.add_argument("--range")
102130
parser.add_argument("--customrequest")
103131
parser.add_argument("--mailfrom")
104132
parser.add_argument("--mailrecipient", action="append")
105133
parser.add_argument("--mimepart", action="append")
106-
parser.add_argument("--httpauth", type=int)
134+
parser.add_argument("--httpauth", type=str)
107135
parser.add_argument("--optheader", type=int)
108136
parser.add_argument("--nobody", type=int)
109137
parser.add_argument("--followlocation", type=int)
@@ -116,6 +144,16 @@ def get_options():
116144
parser.add_argument("--rtspclientcseq", type=int)
117145
parser.add_argument("--mailauth")
118146
parser.add_argument("--httpversion", type=int)
147+
parser.add_argument("--loginoptions", type=str)
148+
parser.add_argument("--bearertoken", type=str)
149+
parser.add_argument("--user_and_pass", type=str)
150+
parser.add_argument("--useragent", type=str)
151+
parser.add_argument("--netrclevel", type=int)
152+
parser.add_argument("--hostpksha256", type=str)
153+
parser.add_argument("--wsoptions", action='store_true')
154+
parser.add_argument("--connectonly", type=int)
155+
parser.add_argument("--post", action='store_true')
156+
parser.add_argument("--hsts")
119157

120158
upload1 = parser.add_mutually_exclusive_group()
121159
upload1.add_argument("--upload1")

0 commit comments

Comments
 (0)