Skip to content
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
26 changes: 22 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,28 @@ endif()
option(BUILD_FUZZERS "Build fuzzing targets" OFF)

if(BUILD_FUZZERS AND NOT MSVC)
add_executable(fuzz_parser fuzz/fuzz_parser.cpp)
target_link_libraries(fuzz_parser args)
target_compile_options(fuzz_parser PRIVATE -fsanitize=fuzzer,address)
target_link_options(fuzz_parser PRIVATE -fsanitize=fuzzer,address)
# Generic fuzzer discovery: every fuzz/*_fuzzer.cpp becomes a target
# named after its file, so new harnesses need no CMake edits. Each
# harness selects its own compile-time configuration (e.g.
# noexcept_mode_fuzzer.cpp defines ARGS_NOEXCEPT itself).
file(GLOB ARGS_FUZZ_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/fuzz/*_fuzzer.cpp")
foreach(fuzz_src ${ARGS_FUZZ_SOURCES})
get_filename_component(fuzz_name ${fuzz_src} NAME_WE)
add_executable(${fuzz_name} ${fuzz_src})
target_link_libraries(${fuzz_name} PRIVATE args)
if(DEFINED ENV{LIB_FUZZING_ENGINE} AND NOT "$ENV{LIB_FUZZING_ENGINE}" STREQUAL "")
# OSS-Fuzz build: sanitizer and coverage instrumentation arrive
# via CXXFLAGS and the fuzzing engine via LIB_FUZZING_ENGINE.
# Sanitizers must NOT be hardcoded here (it would break build
# matrix variants such as MSAN).
target_link_libraries(${fuzz_name} PRIVATE "$ENV{LIB_FUZZING_ENGINE}")
else()
# Local development fallback: clang's libFuzzer.
target_compile_options(${fuzz_name} PRIVATE -fsanitize=fuzzer-no-link)
target_link_options(${fuzz_name} PRIVATE -fsanitize=fuzzer)
endif()
endforeach()
endif()

option(ARGS_BUILD_EXAMPLE "Build example" ON)
Expand Down
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/bare-prefix.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/binary.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0b101
Binary file not shown.
Empty file.
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/erange.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
99999999999999999999999
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/expo.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1e5
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/float.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/hex.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x1f
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/high-bytes.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/inf.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
inf
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/int-overflow.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2147483648
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/llong-min.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-9223372036854775808
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/llong-overflow.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9223372036854775808
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/lone-minus.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/nan.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nan
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/neg-one.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-1
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/octal.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
017
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/one.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/plus-one.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+1
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/trailing-junk.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12junk
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/trailing-ws.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/unsigned-negative.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-5
1 change: 1 addition & 0 deletions fuzz/corpus/numeric_parser_fuzzer/zero.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
51 changes: 0 additions & 51 deletions fuzz/fuzz_numeric_parser.cxx

This file was deleted.

70 changes: 70 additions & 0 deletions fuzz/numeric_parser_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <string>
#include <cstdint>
#include <args.hxx>

/*
* Numeric conversion harness for args::ValueReader.
*
* Repaired for OSS-Fuzz: ValueReader::operator() throws args::ParseError
* for any input the requested type cannot represent, and the original
* harness had no exception handling, so the first malformed byte string
* aborted the whole target before later conversions could run.
*
* Each conversion is now attempted independently, with the expected
* rejection swallowed per type, so a single input reaches every numeric
* path: strtoll/strtoull-based integers (with range and narrowing
* checks), sign rejection for unsigned types, trailing-whitespace
* tolerance, and istringstream-based parsing for bool, char, and
* floating-point types. Embedded NUL bytes are preserved on purpose:
* the readers deliberately distinguish "12\0junk" from "12".
*/

// Accumulates converted values so the conversions cannot be optimized away.
unsigned long g_sink = 0;

template <typename T>
static T AttemptConversion(args::ValueReader &reader, const std::string &input)
{
T destination{};
try
{
reader("fuzz_test", input, destination);
}
catch (...)
{
// Expected rejection for values this type cannot represent.
}
return destination;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Create a string from fuzzer input, preserving embedded NULs
std::string input(reinterpret_cast<const char*>(data), size);

args::ValueReader reader;

// Character-ish types (istringstream-based paths)
g_sink += static_cast<unsigned long>(AttemptConversion<bool>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<char>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<signed char>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<unsigned char>(reader, input));

// Test signed integer types (strtoll-based path, narrowing checks)
g_sink += static_cast<unsigned long>(AttemptConversion<short>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<int>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<long>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<long long>(reader, input));

// Test unsigned integer types (strtoull-based path, sign rejection)
g_sink += static_cast<unsigned long>(AttemptConversion<unsigned short>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<unsigned int>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<unsigned long>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<unsigned long long>(reader, input));

// Test floating point types (istringstream-based path)
g_sink += static_cast<unsigned long>(AttemptConversion<float>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<double>(reader, input));
g_sink += static_cast<unsigned long>(AttemptConversion<long double>(reader, input));

return 0;
}
Loading