Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSSFuzz Integration #251

Merged
merged 3 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/cifuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CIFuzz
on:
push:
branches:
- master
pull_request:
permissions: {}
jobs:
Fuzzing:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'libconfig'
language: c
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'libconfig'
language: c
fuzz-seconds: 800
output-sarif: true
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Sarif
if: always() && steps.build.outcome == 'success'
uses: github/codeql-action/upload-sarif@v2
with:
# Path to SARIF file relative to the root of the repository
sarif_file: cifuzz-sarif/results.sarif
checkout_path: cifuzz-sarif
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ project(libconfig LANGUAGES C CXX VERSION ${VERSION_STRING})
option(BUILD_EXAMPLES "Enable examples" ON)
option(BUILD_SHARED_LIBS "Enable shared library" ON)
option(BUILD_TESTS "Enable tests" ON)
option(BUILD_FUZZERS "Enable fuzzers" OFF)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)

Expand All @@ -33,3 +34,7 @@ if(BUILD_TESTS)
add_subdirectory(tinytest)
add_subdirectory(tests)
endif()

if (BUILD_FUZZERS AND DEFINED ENV{LIB_FUZZING_ENGINE})
add_subdirectory(fuzz)
endif()
42 changes: 42 additions & 0 deletions fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Fuzz process is dependent upon a few environment variables provided by OSSFuzz during the build process
# For more information, see google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh-script-environment

include(ExternalProject)
add_definitions(-DNDEBUG) # Do not want assertions for fuzz-testing

if (DEFINED ENV{CFLAGS})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}")
endif()

if (DEFINED ENV{CXXFLAGS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{CXXFLAGS}")
endif()

if (DEFINED ENV{CC})
set(CMAKE_C_COMPILER "$ENV{CC}" CACHE STRING "C compiler" FORCE)
endif()

if (DEFINED ENV{CXX})
set(CMAKE_CXX_COMPILER "$ENV{CXX}" CACHE STRING "CXX compiler" FORCE)
endif()

if(CMAKE_HOST_WIN32)
set(libname "libconfig")
else()
set(libname "config")
endif()

set(CMAKE_EXE_LINKER_FLAGS "$ENV{LIB_FUZZING_ENGINE}")

add_executable(config_read_fuzzer fuzz_config_read.c fuzz_data.c)

target_link_libraries(config_read_fuzzer PRIVATE
${libname}
$ENV{LIB_FUZZING_ENGINE}
)

if (DEFINED ENV{OUT})
install(TARGETS config_read_fuzzer DESTINATION $ENV{OUT})
else()
message(WARNING "Cannot install if $OUT is not defined!")
endif()
14 changes: 14 additions & 0 deletions fuzz/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cd $SRC/libconfig

mkdir -p build
cmake -S . -B build \
-DBUILD_FUZZERS=On \
-DCMAKE_C_COMPILER_WORKS=1 \
-DCMAKE_CXX_COMPILER_WORKS=1 \
-DBUILD_SHARED_LIBS=Off \
-DBUILD_TESTS=Off \
-DBUILD_EXAMPLES=Off \
&& cmake --build build --target install

# Prepare corpora
zip -q $OUT/config_read_fuzzer_seed_corpus.zip fuzz/corpus/*
Binary file added fuzz/corpus/seed
Binary file not shown.
246 changes: 246 additions & 0 deletions fuzz/fuzz_config_read.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#include "fuzz_data.h"

#include <libconfig.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define MAX_CONFIG_SIZE 4096
#define MAX_PATH_SIZE 256
#define MIN_BUFF_SIZE sizeof(fuzz_data_t) + 2 // Room for fixed-width data and two null terminators
#define MAX_BUFF_SIZE sizeof(fuzz_data_t) + MAX_CONFIG_SIZE + MAX_PATH_SIZE + 1

// Forward-declare the libFuzzer's mutator callback.
extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);

static uint8_t dummy_input[] = {
0x0, // Lookup type
0x17, 0x0, 0x0, 0x0, // Content size
0x3, 0x0, 0x0, 0x0, // Path size
'{', '\n', '\t', 'a', ':', '\n', '{', '\n', '\t', '\t', 'b', ' ', '=', ' ', '1', ';', '\n', '\t', '}', ';', '\n', '}', ';', '\0', // config
'a', '.', 'b', '\0' // Path
};

/**
* To be called statically from the harness, this function opens /dev/null (Only
* Linux compatible)
* @return: FILE pointer to /dev/null
*/
FILE *open_null_file()
{
FILE *dev_null = fopen("/dev/null", "w");

if (dev_null == NULL)
{
abort();
}

return dev_null;
}

// Recursive function to search for a setting by name
config_setting_t *find_setting_recursive(
config_setting_t *setting,
const char *name,
const size_t name_len)
{
if (NULL == setting || NULL == name)
{
return NULL;
}

// Check if the current setting's name matches
const char *setting_name = config_setting_name(setting);
if (NULL != setting_name && strlen(setting_name) == name_len) {
if (strncmp(config_setting_name(setting), name, name_len) == 0) {
return setting;
}
}

// If it's a group, iterate over its children recursively
if (config_setting_is_group(setting) || config_setting_is_array(setting)
|| config_setting_is_list(setting))
{
int count = config_setting_length(setting);
for (int i = 0; i < count; ++i)
{
config_setting_t *child = config_setting_get_elem(setting, i);
config_setting_t *result = find_setting_recursive(child, name, name_len);
if (result != NULL)
{
return result; // Return the found setting
}
}
}

// Return NULL if not found
return NULL;
}

size_t min_size(size_t a, size_t b)
{
return (a <= b) ? a : b;
}

size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size,
size_t max_size, unsigned int seed)
{
size_t remaining_size = max_size;
config_t cfg = {0};
uint8_t* config_data = NULL;
uint8_t* path_data = NULL;

srand(seed);
config_init(&cfg);

fuzz_data_t *fuzz_data = (fuzz_data_t *) data;

// Ensure MIN_BUFF_SIZE * 2 <= size <= MAX_BUFF_SIZE
if (remaining_size < MIN_BUFF_SIZE)
{
return 2 * MIN_BUFF_SIZE;
}
if (remaining_size > MAX_BUFF_SIZE)
{
return MAX_BUFF_SIZE;
}

remaining_size -= MIN_BUFF_SIZE;

fuzz_data->lookup_type = rand() % (CONFIG_TYPE_LIST + 1);

// Ensure the content and path sizes are within bounds
if (fuzz_data->content_size + fuzz_data->path_size > remaining_size)
{
fuzz_data->content_size = rand() % remaining_size;
fuzz_data->path_size = remaining_size - fuzz_data->content_size;
}

// Extract and mutate the config
fuzz_data_content(fuzz_data, &config_data);
fuzz_data->content_size = LLVMFuzzerMutate(config_data, fuzz_data->content_size, remaining_size);
config_data[fuzz_data->content_size] = '\0'; // Null-terminate the config

if (fuzz_data->content_size > remaining_size) {
return 0;
}

remaining_size -= fuzz_data->content_size;

// Extract and mutate the path
fuzz_data->path_size = min_size(fuzz_data->path_size, remaining_size);
fuzz_data_path(fuzz_data, &path_data);
if (remaining_size > 0) {
fuzz_data->path_size = LLVMFuzzerMutate(path_data, fuzz_data->path_size, remaining_size);
}
path_data[fuzz_data->path_size] = '\0'; // Null-terminate the path

return min_size(MIN_BUFF_SIZE + fuzz_data->content_size + fuzz_data->path_size, max_size);
}

int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size)
{
static FILE *dev_null;
uint8_t data_buff[MAX_BUFF_SIZE] = {0};
uint8_t scratch_mem[sizeof(uint64_t)] = {0};
config_t cfg = {0};
uint8_t *config_ptr;
uint8_t *path_ptr;
config_setting_t *root;
int rc = -1;

fuzz_data_t *fuzz_data = (fuzz_data_t*) data_buff;

if (NULL == dev_null)
{
// Only called once per process
dev_null = open_null_file();
}

if (size < MIN_BUFF_SIZE || size > MAX_BUFF_SIZE)
{
// Not enough bytes to be a fuzz_data_t
rc = 0;
goto end;
}

// Copy the data to a buffer that can be mutated
memcpy(data_buff, data, size);

config_init(&cfg);

fuzz_data_content(fuzz_data, &config_ptr);
fuzz_data_path(fuzz_data, &path_ptr);
const char *config_data = (const char *) config_ptr;
const char *path_data = (const char *) path_ptr;

if (CONFIG_TRUE != config_read_string(&cfg, config_data)
|| NULL == (root = config_root_setting(&cfg)))
{
// Parsing failed
goto end;
}

config_setting_t *setting = find_setting_recursive(
root,
(const char*) path_ptr,
fuzz_data->path_size
);

if (NULL != setting)
{
config_setting_get_elem(setting, config_setting_length(setting) - 1);

switch (fuzz_data->lookup_type % (CONFIG_TYPE_LIST + 1)) {
case CONFIG_TYPE_FLOAT:
config_setting_lookup_float(setting, path_data,
(double*) scratch_mem);
break;
case CONFIG_TYPE_BOOL:
config_setting_lookup_bool(setting, path_data,
(int*) scratch_mem);
break;
case CONFIG_TYPE_INT:
config_setting_lookup_int(setting, path_data,
(int*) scratch_mem);
break;
case CONFIG_TYPE_INT64:
config_setting_lookup_int64(setting, path_data,
(long long *) scratch_mem);
break;
case CONFIG_TYPE_STRING: {
const char *string_ptr = NULL;
config_setting_lookup_string(setting, path_data, &string_ptr);
break;
}
default:
config_setting_lookup_const(setting, path_data);
}
}

if (NULL != (setting = config_setting_get_member(root, path_data)))
{
// This setting exists, let's overwrite it
config_setting_set_float(setting, 1.234);
}
else {
// This setting does not exist, create it
setting = config_setting_add(root, path_data, CONFIG_TYPE_FLOAT);

if (setting == NULL)
{
rc = -1;
goto end;
}
config_setting_set_float(setting, 1.234);
}

config_write(&cfg, dev_null);
config_setting_remove(root, path_data);

rc = 0;

end:
config_destroy(&cfg);
return rc;
}
18 changes: 18 additions & 0 deletions fuzz/fuzz_data.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "fuzz_data.h"

void fuzz_data_content(fuzz_data_t *fuzz_data, uint8_t **buff)
{
*buff = fuzz_data->data;

// Ensure the buffer is null terminated
(*buff)[fuzz_data->content_size] = '\0';
}

void fuzz_data_path(fuzz_data_t *fuzz_data, uint8_t **buff)
{
*buff = fuzz_data->data + fuzz_data->content_size + 1;

// Ensure the buffer is null terminated
(*buff)[fuzz_data->path_size] = '\0';
}

Loading
Loading