Skip to content

Commit eb7dee0

Browse files
committed
Initial commit of structured fuzzing support using libprotobuf-mutator.
1 parent bcb8456 commit eb7dee0

File tree

6 files changed

+1267
-43
lines changed

6 files changed

+1267
-43
lines changed

CMakeLists.txt

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,44 @@ else()
88
endif()
99

1010
include(ExternalProject)
11+
find_package(Protobuf REQUIRED)
12+
13+
set(CURL_FUZZER_PROTO ${CMAKE_CURRENT_SOURCE_DIR}/schemas/curl_fuzzer.proto)
14+
protobuf_generate_cpp(CURL_FUZZER_PROTO_SRCS CURL_FUZZER_PROTO_HDRS
15+
${CURL_FUZZER_PROTO})
16+
17+
add_library(curl_fuzzer_proto STATIC ${CURL_FUZZER_PROTO_SRCS})
18+
target_include_directories(curl_fuzzer_proto PUBLIC
19+
${CMAKE_CURRENT_BINARY_DIR}
20+
${PROTOBUF_INCLUDE_DIRS})
21+
target_link_libraries(curl_fuzzer_proto PUBLIC ${PROTOBUF_LIBRARIES})
22+
23+
# Install libprotobuf-mutator
24+
#
25+
# renovate: datasource=github-tags depName=google/libprotobuf-mutator
26+
set(LIB_PROTO_MUTATOR_TAG master)
27+
set(LIB_PROTO_MUTATOR_INSTALL_DIR ${CMAKE_BINARY_DIR}/libprotobuf-mutator-install)
28+
set(LIB_PROTO_MUTATOR_INCLUDE_DIR ${LIB_PROTO_MUTATOR_INSTALL_DIR}/include)
29+
set(LIB_PROTO_MUTATOR_STATIC_LIB ${LIB_PROTO_MUTATOR_INSTALL_DIR}/lib/libprotobuf-mutator.a)
30+
set(LIB_PROTO_MUTATOR_FUZZER_LIB ${LIB_PROTO_MUTATOR_INSTALL_DIR}/lib/libprotobuf-mutator-libfuzzer.a)
31+
32+
ExternalProject_Add(libprotobuf_mutator_external
33+
GIT_REPOSITORY https://github.com/google/libprotobuf-mutator.git
34+
GIT_TAG ${LIB_PROTO_MUTATOR_TAG}
35+
PREFIX ${CMAKE_BINARY_DIR}/libprotobuf-mutator
36+
CMAKE_ARGS
37+
-DCMAKE_INSTALL_PREFIX=${LIB_PROTO_MUTATOR_INSTALL_DIR}
38+
-DBUILD_SHARED_LIBS=OFF
39+
-DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=OFF
40+
-DLIB_PROTO_MUTATOR_WITH_LIBFUZZER=ON
41+
-DLIB_PROTO_MUTATOR_EXAMPLES=OFF
42+
-DLIB_PROTO_MUTATOR_TESTING=OFF
43+
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
44+
BUILD_BYPRODUCTS ${LIB_PROTO_MUTATOR_STATIC_LIB} ${LIB_PROTO_MUTATOR_FUZZER_LIB}
45+
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install
46+
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
47+
DOWNLOAD_NO_PROGRESS 1
48+
)
1149

1250
# Install zlib
1351
#
@@ -223,7 +261,8 @@ add_custom_target(deps
223261
zstd_external
224262
libidn2_external
225263
${GDB_DEP}
226-
openldap_external
264+
openldap_external
265+
libprotobuf_mutator_external
227266
)
228267

229268
# Now for the main dependencies!
@@ -336,7 +375,11 @@ else()
336375
endif()
337376

338377
# Common sources and flags
339-
set(COMMON_SOURCES curl_fuzzer.cc curl_fuzzer_tlv.cc curl_fuzzer_callback.cc)
378+
set(COMMON_SOURCES
379+
curl_fuzzer.cc
380+
curl_fuzzer_tlv.cc
381+
curl_fuzzer_callback.cc
382+
curl_fuzzer_scenario.cc)
340383
set(COMMON_FLAGS -g -DCURL_DISABLE_DEPRECATION)
341384
set(COMMON_LINK_LIBS
342385
${CURL_LIB_DIR}/libcurl.a
@@ -348,19 +391,22 @@ set(COMMON_LINK_LIBS
348391
${OPENLDAP_STATIC_LIB_LDAP}
349392
${OPENLDAP_STATIC_LIB_LBER}
350393
${LIB_FUZZING_ENGINE}
394+
${LIB_PROTO_MUTATOR_FUZZER_LIB}
395+
${LIB_PROTO_MUTATOR_STATIC_LIB}
396+
curl_fuzzer_proto
351397
pthread
352398
m
353399
)
354400
set(COMMON_LINK_OPTIONS ${LIB_FUZZING_ENGINE_FLAG})
355401

356402
# Ensure that curl and its dependencies are built before the fuzzers
357-
set(FUZZ_DEPS curl_external ${CURL_DEPS} ${LIB_FUZZING_ENGINE_DEP})
403+
set(FUZZ_DEPS curl_external ${CURL_DEPS} ${LIB_FUZZING_ENGINE_DEP} curl_fuzzer_proto libprotobuf_mutator_external)
358404

359405
# Helper macro to define a fuzzer target
360406
macro(curl_add_fuzzer _name _proto)
361407
add_executable(${_name} ${COMMON_SOURCES})
362408
target_compile_options(${_name} PRIVATE ${COMMON_FLAGS} -DFUZZ_PROTOCOLS_${_proto})
363-
target_include_directories(${_name} PRIVATE ${CURL_INCLUDE_DIRS})
409+
target_include_directories(${_name} PRIVATE ${CURL_INCLUDE_DIRS} ${LIB_PROTO_MUTATOR_INCLUDE_DIR} ${LIB_PROTO_MUTATOR_INCLUDE_DIR}/libprotobuf-mutator)
364410
target_link_libraries(${_name} PRIVATE ${COMMON_LINK_LIBS})
365411
target_link_options(${_name} PRIVATE ${COMMON_LINK_OPTIONS})
366412
add_dependencies(${_name} ${FUZZ_DEPS})
@@ -387,7 +433,7 @@ curl_add_fuzzer(curl_fuzzer_ws WS)
387433
# BUFQ fuzzer
388434
add_executable(curl_fuzzer_bufq fuzz_bufq.cc)
389435
target_compile_options(curl_fuzzer_bufq PRIVATE ${COMMON_FLAGS})
390-
target_include_directories(curl_fuzzer_bufq PRIVATE ${CURL_INCLUDE_DIRS})
436+
target_include_directories(curl_fuzzer_bufq PRIVATE ${CURL_INCLUDE_DIRS} ${LIB_PROTO_MUTATOR_INCLUDE_DIR} ${LIB_PROTO_MUTATOR_INCLUDE_DIR}/libprotobuf-mutator)
391437
target_link_libraries(curl_fuzzer_bufq PRIVATE ${COMMON_LINK_LIBS})
392438
target_link_options(curl_fuzzer_bufq PRIVATE ${COMMON_LINK_OPTIONS})
393439
add_dependencies(curl_fuzzer_bufq ${FUZZ_DEPS})

curl_fuzzer.cc

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,56 +26,40 @@
2626
#include <unistd.h>
2727
#include <curl/curl.h>
2828
#include "curl_fuzzer.h"
29+
#include "curl_fuzzer_scenario.h"
2930

30-
/**
31-
* Fuzzing entry point. This function is passed a buffer containing a test
32-
* case. This test case should drive the CURL API into making a request.
33-
*/
34-
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
31+
#include <libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h>
32+
33+
DEFINE_PROTO_FUZZER(const curl::fuzzer::proto::Scenario &scenario)
34+
{
35+
CurlFuzzerRunScenario(scenario);
36+
}
37+
38+
int CurlFuzzerRunScenario(const curl::fuzzer::proto::Scenario &scenario)
3539
{
3640
int rc = 0;
37-
int tlv_rc;
3841
FUZZ_DATA fuzz;
39-
TLV tlv;
4042

4143
/* Ignore SIGPIPE errors. We'll handle the errors ourselves. */
4244
signal(SIGPIPE, SIG_IGN);
4345

4446
/* Have to set all fields to zero before getting to the terminate function */
4547
memset(&fuzz, 0, sizeof(FUZZ_DATA));
4648

47-
if(size < sizeof(TLV_RAW)) {
48-
/* Not enough data for a single TLV - don't continue */
49-
goto EXIT_LABEL;
50-
}
51-
5249
/* Try to initialize the fuzz data */
53-
FTRY(fuzz_initialize_fuzz_data(&fuzz, data, size));
50+
FTRY(fuzz_initialize_fuzz_data(&fuzz));
5451

55-
for(tlv_rc = fuzz_get_first_tlv(&fuzz, &tlv);
56-
tlv_rc == 0;
57-
tlv_rc = fuzz_get_next_tlv(&fuzz, &tlv)) {
58-
59-
/* Have the TLV in hand. Parse the TLV. */
60-
rc = fuzz_parse_tlv(&fuzz, &tlv);
61-
62-
if(rc != 0) {
63-
/* Failed to parse the TLV. Can't continue. */
64-
goto EXIT_LABEL;
65-
}
66-
}
67-
68-
if(tlv_rc != TLV_RC_NO_MORE_TLVS) {
69-
/* A TLV call failed. Can't continue. */
52+
rc = curl_fuzzer::ApplyScenario(scenario, &fuzz);
53+
if(rc != 0) {
7054
goto EXIT_LABEL;
7155
}
7256

7357
/* Set up the standard easy options. */
7458
FTRY(fuzz_set_easy_options(&fuzz));
7559

7660
/**
77-
* Add in more curl options that have been accumulated over possibly
78-
* multiple TLVs.
61+
* Add in more curl options that have been accumulated over the scenario
62+
* execution.
7963
*/
8064
if(fuzz.header_list != NULL) {
8165
curl_easy_setopt(fuzz.easy, CURLOPT_HTTPHEADER, fuzz.header_list);
@@ -104,6 +88,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
10488
return 0;
10589
}
10690

91+
10792
/**
10893
* Utility function to convert 4 bytes to a u32 predictably.
10994
*/
@@ -127,24 +112,25 @@ uint16_t to_u16(const uint8_t b[2])
127112
/**
128113
* Initialize the local fuzz data structure.
129114
*/
130-
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz,
131-
const uint8_t *data,
132-
size_t data_len)
115+
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz)
133116
{
134117
int rc = 0;
135118
int ii;
136119

137120
/* Initialize the fuzz data. */
138121
memset(fuzz, 0, sizeof(FUZZ_DATA));
122+
fuzz->scenario_state = NULL;
123+
fuzz->scenario_state_destructor = NULL;
139124

140125
/* Create an easy handle. This will have all of the settings configured on
141126
it. */
142127
fuzz->easy = curl_easy_init();
143128
FCHECK(fuzz->easy != NULL);
144129

145130
/* Set up the state parser */
146-
fuzz->state.data = data;
147-
fuzz->state.data_len = data_len;
131+
fuzz->state.data = NULL;
132+
fuzz->state.data_len = 0;
133+
fuzz->state.data_pos = 0;
148134

149135
/* Set up the state of the server sockets. */
150136
for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) {
@@ -275,6 +261,12 @@ void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz)
275261
fuzz->easy = NULL;
276262
}
277263

264+
if(fuzz->scenario_state_destructor != NULL && fuzz->scenario_state != NULL) {
265+
fuzz->scenario_state_destructor(fuzz->scenario_state);
266+
fuzz->scenario_state = NULL;
267+
fuzz->scenario_state_destructor = NULL;
268+
}
269+
278270
/* When you have passed the struct curl_httppost pointer to curl_easy_setopt
279271
* (using the CURLOPT_HTTPPOST option), you must not free the list until after
280272
* you have called curl_easy_cleanup for the curl handle.

curl_fuzzer.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#ifndef CURL_FUZZER_H
2+
#define CURL_FUZZER_H
3+
14
/***************************************************************************
25
* _ _ ____ _
36
* Project ___| | | | _ \| |
@@ -23,6 +26,14 @@
2326
#include <curl/curl.h>
2427
#include "testinput.h"
2528

29+
namespace curl {
30+
namespace fuzzer {
31+
namespace proto {
32+
class Scenario;
33+
} // namespace proto
34+
} // namespace fuzzer
35+
} // namespace curl
36+
2637
/**
2738
* TLV types.
2839
*/
@@ -433,14 +444,16 @@ typedef struct fuzz_data
433444
/* Verbose mode. */
434445
int verbose;
435446

447+
/* Optional structured scenario ownership hook. */
448+
void *scenario_state;
449+
void (*scenario_state_destructor)(void *state);
450+
436451
} FUZZ_DATA;
437452

438453
/* Function prototypes */
439454
uint32_t to_u32(const uint8_t b[4]);
440455
uint16_t to_u16(const uint8_t b[2]);
441-
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz,
442-
const uint8_t *data,
443-
size_t data_len);
456+
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz);
444457
int fuzz_set_easy_options(FUZZ_DATA *fuzz);
445458
void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz);
446459
void fuzz_free(void **ptr);
@@ -474,6 +487,7 @@ int fuzz_select(int nfds,
474487
fd_set *exceptfds,
475488
struct timeval *timeout);
476489
int fuzz_set_allowed_protocols(FUZZ_DATA *fuzz);
490+
int CurlFuzzerRunScenario(const curl::fuzzer::proto::Scenario &scenario);
477491

478492
/* Macros */
479493
#define FTRY(FUNC) \
@@ -532,3 +546,5 @@ int fuzz_set_allowed_protocols(FUZZ_DATA *fuzz);
532546
}
533547

534548
#define FUZZ_MAX(A, B) ((A) > (B) ? (A) : (B))
549+
550+
#endif /* CURL_FUZZER_H */

0 commit comments

Comments
 (0)