From f52611adab4186e948e561d3a0035c41b666e496 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Thu, 21 May 2026 18:18:35 +0530 Subject: [PATCH 1/5] Systematic memory safety hardening --- CMakeLists.txt | 58 +++++++++++++++++++++++++++- fuzz/README.md | 16 ++++++++ fuzz/fuzz_parser.cpp | 72 +++++++++++++++++++++++++++++++++++ safe_arithmetic.hxx | 61 +++++++++++++++++++++++++++++ test/test_safe_arithmetic.cxx | 36 ++++++++++++++++++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 fuzz/README.md create mode 100644 fuzz/fuzz_parser.cpp create mode 100644 safe_arithmetic.hxx create mode 100644 test/test_safe_arithmetic.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 92727a0..d11df3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,50 @@ project(args LANGUAGES CXX VERSION 6.4.16 DESCRIPTION "A flexible single-header include(GNUInstallDirs) +# ============================================ +# SECURITY HARDENING COMPILER FLAGS +# ============================================ +if(MSVC) + # MSVC security flags + add_compile_options( + /W4 # High warning level + /WX # Treat warnings as errors + /sdl # Security Development Lifecycle checks + /GS # Buffer security check + /guard:cf # Control Flow Guard + ) + add_link_options(/GUARD:CF) +else() + # GCC/Clang security flags + add_compile_options( + -Wall -Wextra # All warnings + -Wpedantic # Strict standard compliance + -Werror # Warnings as errors + -Wconversion # Implicit conversion warnings + -Wsign-conversion # Sign conversion warnings + -Wunsafe-buffer-usage # Buffer usage warnings (Clang) + -Wnull-dereference # Null dereference detection + -Wformat=2 # Format string attacks + -Wshadow # Variable shadowing + -Wcast-align # Potential undefined behavior + -Wunused # Dead code detection + ) + + # 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() + string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") set(ARGS_MAIN_PROJECT OFF) @@ -32,6 +76,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) @@ -139,4 +195,4 @@ set(CPACK_RPM_COMPONENT_INSTALL ON) set(CPACK_NSIS_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") -include(CPack) +include(CPack) \ No newline at end of file diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..b25a2f0 --- /dev/null +++ b/fuzz/README.md @@ -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. \ No newline at end of file diff --git a/fuzz/fuzz_parser.cpp b/fuzz/fuzz_parser.cpp new file mode 100644 index 0000000..3c477c5 --- /dev/null +++ b/fuzz/fuzz_parser.cpp @@ -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 +#include +#include +#include +#include + +// 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(data), size); + + // Simulate command-line arguments + std::vector 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 argv; + for (auto& a : args) { + argv.push_back(const_cast(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 name(parser, "name", "User name", {'n', "name"}); + args::ValueFlag count(parser, "count", "Count value", {'c', "count"}); + args::Positional positional(parser, "input", "Input file"); + + try { + // Parse the arguments - this is what we're fuzzing + parser.ParseArgs(static_cast(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 +} \ No newline at end of file diff --git a/safe_arithmetic.hxx b/safe_arithmetic.hxx new file mode 100644 index 0000000..b4405fb --- /dev/null +++ b/safe_arithmetic.hxx @@ -0,0 +1,61 @@ +// Centralized checked arithmetic helpers for safe allocation and arithmetic +// Systematic hardening against integer overflows and undersized allocations +// Usage: if (!SafeMul(a, b, &out)) { /* handle overflow */ } + +#ifndef ARGS_SAFE_ARITHMETIC_HXX +#define ARGS_SAFE_ARITHMETIC_HXX + +#include +#include + +namespace args { + +// Safe multiplication: returns false on overflow, true on success +// out is only set if multiplication succeeds +// T must be unsigned integral type + +template +bool SafeMul(T a, T b, T* out) { + static_assert(std::is_integral::value && std::is_unsigned::value, "SafeMul requires unsigned integral type"); + if (b != 0 && a > std::numeric_limits::max() / b) { + return false; + } + *out = a * b; + return true; +} + +// Safe addition: returns false on overflow, true on success +template +bool SafeAdd(T a, T b, T* out) { + static_assert(std::is_integral::value && std::is_unsigned::value, "SafeAdd requires unsigned integral type"); + if (a > std::numeric_limits::max() - b) { + return false; + } + *out = a + b; + return true; +} + +// Safe subtraction: returns false on underflow, true on success +template +bool SafeSub(T a, T b, T* out) { + static_assert(std::is_integral::value && std::is_unsigned::value, "SafeSub requires unsigned integral type"); + if (a < b) { + return false; + } + *out = a - b; + return true; +} + +// Safe cast: returns false if value cannot be represented in To type +template +bool SafeCast(From value, To* out) { + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + return false; + } + *out = static_cast(value); + return true; +} + +} // namespace args + +#endif // ARGS_SAFE_ARITHMETIC_HXX \ No newline at end of file diff --git a/test/test_safe_arithmetic.cxx b/test/test_safe_arithmetic.cxx new file mode 100644 index 0000000..94d8232 --- /dev/null +++ b/test/test_safe_arithmetic.cxx @@ -0,0 +1,36 @@ +// Regression tests for checked arithmetic helpers +// Compile with: g++ -std=c++11 -o test_safe_arithmetic test_safe_arithmetic.cpp + +#include "safe_arithmetic.hxx" +#include +#include +#include + +using namespace args; + +int main() { + unsigned int out; + // SafeMul tests + assert(SafeMul(10u, 20u, &out) && out == 200u); + assert(!SafeMul(std::numeric_limits::max(), 2u, &out)); + assert(!SafeMul(std::numeric_limits::max(), std::numeric_limits::max(), &out)); + assert(SafeMul(0u, 100u, &out) && out == 0u); + + // SafeAdd tests + assert(SafeAdd(1u, 2u, &out) && out == 3u); + assert(!SafeAdd(std::numeric_limits::max(), 1u, &out)); + assert(SafeAdd(0u, 0u, &out) && out == 0u); + + // SafeSub tests + assert(SafeSub(5u, 3u, &out) && out == 2u); + assert(!SafeSub(3u, 5u, &out)); + assert(SafeSub(0u, 0u, &out) && out == 0u); + + // SafeCast tests + unsigned short out16; + assert(SafeCast(100u, &out16) && out16 == 100u); + assert(!SafeCast(std::numeric_limits::max(), &out16)); + + std::cout << "All safe_arithmetic tests passed.\n"; + return 0; +} \ No newline at end of file From 614989f6f0a3ea350c86ce98f5f3ca1407fddc21 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Thu, 21 May 2026 19:06:00 +0530 Subject: [PATCH 2/5] updated --- CMakeLists.txt | 96 +++++++++++++++++++++-------------- test/test_safe_arithmetic.cxx | 5 ++ 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d11df3f..4d3c700 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,48 +24,70 @@ 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 # ============================================ -if(MSVC) - # MSVC security flags - add_compile_options( - /W4 # High warning level - /WX # Treat warnings as errors - /sdl # Security Development Lifecycle checks - /GS # Buffer security check - /guard:cf # Control Flow Guard - ) - add_link_options(/GUARD:CF) -else() - # GCC/Clang security flags - add_compile_options( - -Wall -Wextra # All warnings - -Wpedantic # Strict standard compliance - -Werror # Warnings as errors - -Wconversion # Implicit conversion warnings - -Wsign-conversion # Sign conversion warnings - -Wunsafe-buffer-usage # Buffer usage warnings (Clang) - -Wnull-dereference # Null dereference detection - -Wformat=2 # Format string attacks - -Wshadow # Variable shadowing - -Wcast-align # Potential undefined behavior - -Wunused # Dead code detection - ) - - # 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) +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() - - if(ENABLE_UBSAN) - add_compile_options(-fsanitize=undefined) - add_link_options(-fsanitize=undefined) +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) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + args_add_compile_flag_if_supported(-Wunsafe-buffer-usage) + endif() + + 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() diff --git a/test/test_safe_arithmetic.cxx b/test/test_safe_arithmetic.cxx index 94d8232..6534d94 100644 --- a/test/test_safe_arithmetic.cxx +++ b/test/test_safe_arithmetic.cxx @@ -31,6 +31,11 @@ int main() { assert(SafeCast(100u, &out16) && out16 == 100u); assert(!SafeCast(std::numeric_limits::max(), &out16)); +#ifdef NDEBUG + (void)out; + (void)out16; +#endif + std::cout << "All safe_arithmetic tests passed.\n"; return 0; } \ No newline at end of file From 00d2a8b38d946789359b73f8514e865062d86ba5 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Fri, 22 May 2026 10:00:45 +0530 Subject: [PATCH 3/5] Remove redundant safe_arithmetic.hxx and test_safe_arithmetic.cxx These duplicated functionality already provided by args.hxx (SafeAdd/SafeMultiply/SafeSub/SafeNeg in namespace args) and already covered by test/safe_arithmetic.cxx. The duplicate header used an unsigned-only `value < min()` check that triggers -Wtype-limits under -Werror on gcc/clang, breaking the ubuntu and macos CMake jobs. Meson and Windows were unaffected because meson.build did not list the duplicate test and MSVC does not emit that warning. --- safe_arithmetic.hxx | 61 ----------------------------------- test/test_safe_arithmetic.cxx | 41 ----------------------- 2 files changed, 102 deletions(-) delete mode 100644 safe_arithmetic.hxx delete mode 100644 test/test_safe_arithmetic.cxx diff --git a/safe_arithmetic.hxx b/safe_arithmetic.hxx deleted file mode 100644 index b4405fb..0000000 --- a/safe_arithmetic.hxx +++ /dev/null @@ -1,61 +0,0 @@ -// Centralized checked arithmetic helpers for safe allocation and arithmetic -// Systematic hardening against integer overflows and undersized allocations -// Usage: if (!SafeMul(a, b, &out)) { /* handle overflow */ } - -#ifndef ARGS_SAFE_ARITHMETIC_HXX -#define ARGS_SAFE_ARITHMETIC_HXX - -#include -#include - -namespace args { - -// Safe multiplication: returns false on overflow, true on success -// out is only set if multiplication succeeds -// T must be unsigned integral type - -template -bool SafeMul(T a, T b, T* out) { - static_assert(std::is_integral::value && std::is_unsigned::value, "SafeMul requires unsigned integral type"); - if (b != 0 && a > std::numeric_limits::max() / b) { - return false; - } - *out = a * b; - return true; -} - -// Safe addition: returns false on overflow, true on success -template -bool SafeAdd(T a, T b, T* out) { - static_assert(std::is_integral::value && std::is_unsigned::value, "SafeAdd requires unsigned integral type"); - if (a > std::numeric_limits::max() - b) { - return false; - } - *out = a + b; - return true; -} - -// Safe subtraction: returns false on underflow, true on success -template -bool SafeSub(T a, T b, T* out) { - static_assert(std::is_integral::value && std::is_unsigned::value, "SafeSub requires unsigned integral type"); - if (a < b) { - return false; - } - *out = a - b; - return true; -} - -// Safe cast: returns false if value cannot be represented in To type -template -bool SafeCast(From value, To* out) { - if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { - return false; - } - *out = static_cast(value); - return true; -} - -} // namespace args - -#endif // ARGS_SAFE_ARITHMETIC_HXX \ No newline at end of file diff --git a/test/test_safe_arithmetic.cxx b/test/test_safe_arithmetic.cxx deleted file mode 100644 index 6534d94..0000000 --- a/test/test_safe_arithmetic.cxx +++ /dev/null @@ -1,41 +0,0 @@ -// Regression tests for checked arithmetic helpers -// Compile with: g++ -std=c++11 -o test_safe_arithmetic test_safe_arithmetic.cpp - -#include "safe_arithmetic.hxx" -#include -#include -#include - -using namespace args; - -int main() { - unsigned int out; - // SafeMul tests - assert(SafeMul(10u, 20u, &out) && out == 200u); - assert(!SafeMul(std::numeric_limits::max(), 2u, &out)); - assert(!SafeMul(std::numeric_limits::max(), std::numeric_limits::max(), &out)); - assert(SafeMul(0u, 100u, &out) && out == 0u); - - // SafeAdd tests - assert(SafeAdd(1u, 2u, &out) && out == 3u); - assert(!SafeAdd(std::numeric_limits::max(), 1u, &out)); - assert(SafeAdd(0u, 0u, &out) && out == 0u); - - // SafeSub tests - assert(SafeSub(5u, 3u, &out) && out == 2u); - assert(!SafeSub(3u, 5u, &out)); - assert(SafeSub(0u, 0u, &out) && out == 0u); - - // SafeCast tests - unsigned short out16; - assert(SafeCast(100u, &out16) && out16 == 100u); - assert(!SafeCast(std::numeric_limits::max(), &out16)); - -#ifdef NDEBUG - (void)out; - (void)out16; -#endif - - std::cout << "All safe_arithmetic tests passed.\n"; - return 0; -} \ No newline at end of file From 8107136f4b0093c9b31418f68ebd1b7917c09202 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Fri, 22 May 2026 10:22:40 +0530 Subject: [PATCH 4/5] Fix -Wsign-conversion in completion arg-rewriting vector::erase(begin() + idx) implicitly converts size_t -> ptrdiff_t. Under the branch's new directory-wide -Wsign-conversion + -Werror, this aborts the build of every test TU that instantiates Parse(). Add an explicit static_cast to the vector's difference_type. --- args.hxx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/args.hxx b/args.hxx index 85671f9..6fa5758 100644 --- a/args.hxx +++ b/args.hxx @@ -3139,13 +3139,15 @@ namespace args size_t erase_end = 0; if (SafeAdd(next_idx, static_cast(1), erase_end)) { - curArgs.erase(curArgs.begin() + idx, - curArgs.begin() + erase_end); + typedef std::vector::difference_type diff_t; + curArgs.erase(curArgs.begin() + static_cast(idx), + curArgs.begin() + static_cast(erase_end)); } } else { // Safe erase of single '=' token at the end - curArgs.erase(curArgs.begin() + idx); + typedef std::vector::difference_type diff_t; + curArgs.erase(curArgs.begin() + static_cast(idx)); } // Do not increment idx - next element slides into current position } else From 3f5d4d99ec07a64778a90a9abc49b77d07ec79f0 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Fri, 22 May 2026 10:53:58 +0530 Subject: [PATCH 5/5] Drop -Wunsafe-buffer-usage from clang hardening flags This warning fires on every raw pointer arithmetic operation, including the standard `argv + 1, argv + argc` idiom used in args.hxx and the gitlike example. Combined with the test target's -Werror, it broke all test TUs on macos/clang. There is no clean replacement for argv handling short of std::span (C++20), so leave the flag off for this C++11 codebase. --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d3c700..9b9e250 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,9 +67,9 @@ if(ARGS_ENABLE_SECURITY_HARDENING) args_add_compile_flag_if_supported(-Wcast-align) args_add_compile_flag_if_supported(-Wunused) - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - args_add_compile_flag_if_supported(-Wunsafe-buffer-usage) - endif() + # -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)