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
80 changes: 79 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,72 @@ cmake_minimum_required(VERSION 3.10)
project(args LANGUAGES CXX VERSION 6.4.16 DESCRIPTION "A flexible single-header C++11 argument parsing library that is designed to appear somewhat similar to Python's argparse" HOMEPAGE_URL "https://github.com/Taywee/args")

include(GNUInstallDirs)
include(CheckCXXCompilerFlag)

option(ARGS_ENABLE_SECURITY_HARDENING "Enable additional compiler/linker hardening flags" ON)
option(ARGS_ENABLE_HARDENING_WERROR "Treat hardening warnings as errors" OFF)

# ============================================
# SECURITY HARDENING COMPILER FLAGS
# ============================================
function(args_add_compile_flag_if_supported flag)
string(MAKE_C_IDENTIFIER "${flag}" flag_id)
set(flag_var "ARGS_HAS_${flag_id}")
check_cxx_compiler_flag("${flag}" ${flag_var})
if(${flag_var})
add_compile_options("${flag}")
endif()
endfunction()

if(ARGS_ENABLE_SECURITY_HARDENING)
if(MSVC)
# MSVC security-oriented defaults
add_compile_options(
/W4
/sdl
/GS
/guard:cf
)
add_link_options(/GUARD:CF)
if(ARGS_ENABLE_HARDENING_WERROR)
add_compile_options(/WX)
endif()
else()
# GCC/Clang hardening flags (only when supported)
args_add_compile_flag_if_supported(-Wall)
args_add_compile_flag_if_supported(-Wextra)
args_add_compile_flag_if_supported(-Wpedantic)
args_add_compile_flag_if_supported(-Wconversion)
args_add_compile_flag_if_supported(-Wsign-conversion)
args_add_compile_flag_if_supported(-Wnull-dereference)
args_add_compile_flag_if_supported(-Wformat=2)
args_add_compile_flag_if_supported(-Wshadow)
args_add_compile_flag_if_supported(-Wcast-align)
args_add_compile_flag_if_supported(-Wunused)

# -Wunsafe-buffer-usage (clang) flags all raw pointer arithmetic,
# including the standard `argv + 1, argv + argc` idiom. It produces
# false positives on bounded argv handling, so it is not enabled.

if(ARGS_ENABLE_HARDENING_WERROR)
args_add_compile_flag_if_supported(-Werror)
endif()

# Sanitizers for debugging (optional, off by default)
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)

if(ENABLE_ASAN)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()

if(ENABLE_UBSAN)
add_compile_options(-fsanitize=undefined)
add_link_options(-fsanitize=undefined)
endif()
endif()
endif()

string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}")

Expand All @@ -32,6 +98,18 @@ if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(ARGS_MAIN_PROJECT ON)
endif()

# ============================================
# FUZZING TARGET (if libFuzzer available)
# ============================================
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)
endif()

option(ARGS_BUILD_EXAMPLE "Build example" ON)
option(ARGS_BUILD_UNITTESTS "Build unittests" ON)

Expand Down Expand Up @@ -139,4 +217,4 @@ set(CPACK_RPM_COMPONENT_INSTALL ON)
set(CPACK_NSIS_COMPONENT_INSTALL ON)
set(CPACK_DEBIAN_COMPRESSION_TYPE "xz")

include(CPack)
include(CPack)
8 changes: 5 additions & 3 deletions args.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -3139,13 +3139,15 @@ namespace args
size_t erase_end = 0;
if (SafeAdd<size_t>(next_idx, static_cast<size_t>(1), erase_end))
{
curArgs.erase(curArgs.begin() + idx,
curArgs.begin() + erase_end);
typedef std::vector<std::string>::difference_type diff_t;
curArgs.erase(curArgs.begin() + static_cast<diff_t>(idx),
curArgs.begin() + static_cast<diff_t>(erase_end));
}
} else
{
// Safe erase of single '=' token at the end
curArgs.erase(curArgs.begin() + idx);
typedef std::vector<std::string>::difference_type diff_t;
curArgs.erase(curArgs.begin() + static_cast<diff_t>(idx));
}
// Do not increment idx - next element slides into current position
} else
Expand Down
16 changes: 16 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## OSS-Fuzz Integration
This fuzzer is compatible with OSS-Fuzz. To integrate:

- Add to OSS-Fuzz project list
- Build with -fsanitize=fuzzer,address,undefined
- Run for 24+ hours on cluster

## Expected Findings
The fuzzer should NOT find:

- Buffer overflows
- Integer overflows (already fixed with SafeMultiply)
- Use-after-free (RAII prevents)
- Null dereferences

If it does find issues, they represent critical bugs.
72 changes: 72 additions & 0 deletions fuzz/fuzz_parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* libFuzzer integration for args.hxx
*
* Build with: cmake -DBUILD_FUZZERS=ON ..
* Run with: ./fuzz_parser corpus/
*
* This fuzzer tests argument parsing with malformed/untrusted input
* to detect crashes, overflows, and undefined behavior.
*/

#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <sstream>

// Include the args library
#include "../args.hxx"

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

// Simulate command-line arguments
std::vector<std::string> args;

// Parse input as space-separated arguments (simulate argv)
std::istringstream iss(input);
std::string arg;
while (iss >> arg) {
args.push_back(arg);
}

if (args.empty()) {
return 0; // Nothing to parse
}

// Build argv-style array
std::vector<char*> argv;
for (auto& a : args) {
argv.push_back(const_cast<char*>(a.c_str()));
}

// Create argument parser
args::ArgumentParser parser("Fuzzer test", "Fuzzing argument parsing");

// Add various flag types to test
args::HelpFlag help(parser, "help", "Display help", {'h', "help"});
args::Flag verbose(parser, "verbose", "Enable verbose", {'v', "verbose"});
args::ValueFlag<std::string> name(parser, "name", "User name", {'n', "name"});
args::ValueFlag<int> count(parser, "count", "Count value", {'c', "count"});
args::Positional<std::string> positional(parser, "input", "Input file");

try {
// Parse the arguments - this is what we're fuzzing
parser.ParseArgs(static_cast<int>(argv.size()), argv.data());
} catch (const args::Help&) {
// Help requested - normal behavior
return 0;
} catch (const args::ParseError& e) {
// Parse error - expected for malformed input
return 0;
} catch (const args::ValidationError& e) {
// Validation error - also expected
return 0;
} catch (const std::exception& e) {
// Any other exception is a potential bug
__builtin_trap(); // Signal fuzzer this is a crash
}

return 0; // Success
}
Loading