From 429ade9becf96ff41ac72da0226cba06d32e0c8f Mon Sep 17 00:00:00 2001 From: netliomax25-code Date: Wed, 27 May 2026 21:59:36 +0530 Subject: [PATCH] Harden CLI parsing and enable unsafe-buffer diagnostics --- CMakeLists.txt | 7 ++++--- args.hxx | 9 ++++++++- examples/gitlike.cxx | 13 ++++++++++++- test/parse_cli_args.cxx | 26 ++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 test/parse_cli_args.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b9e250..e25c2df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,9 +67,10 @@ if(ARGS_ENABLE_SECURITY_HARDENING) 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. + # -Wunsafe-buffer-usage (clang) flags all raw pointer arithmetic. + # The library now avoids raw argv range arithmetic in security-sensitive + # code paths, so this warning is useful for catching unsafe buffer usage. + args_add_compile_flag_if_supported(-Wunsafe-buffer-usage) if(ARGS_ENABLE_HARDENING_WERROR) args_add_compile_flag_if_supported(-Werror) diff --git a/args.hxx b/args.hxx index 86a8672..654d882 100644 --- a/args.hxx +++ b/args.hxx @@ -3504,7 +3504,14 @@ namespace args std::vector args; if (argc > 1 && argv != nullptr) { - args.assign(argv + 1, argv + argc); + args.reserve(static_cast(argc - 1)); + for (int idx = 1; idx < argc; ++idx) + { + if (argv[idx] != nullptr) + { + args.emplace_back(argv[idx]); + } + } } return ParseArgs(args) == std::end(args); diff --git a/examples/gitlike.cxx b/examples/gitlike.cxx index f3caaf2..e39a912 100644 --- a/examples/gitlike.cxx +++ b/examples/gitlike.cxx @@ -18,7 +18,18 @@ int main(int argc, char **argv) {"init", Init}, {"add", Add}}; - const std::vector args(argv + 1, argv + argc); + std::vector args; + if (argc > 1) + { + args.reserve(static_cast(argc - 1)); + for (int idx = 1; idx < argc; ++idx) + { + if (argv[idx] != nullptr) + { + args.emplace_back(argv[idx]); + } + } + } args::ArgumentParser parser("This is a git-like program", "Valid commands are init and add"); args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); parser.Prog(argv[0]); diff --git a/test/parse_cli_args.cxx b/test/parse_cli_args.cxx new file mode 100644 index 0000000..ea0aa1e --- /dev/null +++ b/test/parse_cli_args.cxx @@ -0,0 +1,26 @@ +/* Copyright (c) Taylor Richberger + * This code is released under the license described in the LICENSE file + */ + +#include "test_common.hxx" + +#include + +#include "test_helpers.hxx" + +int main() +{ + args::ArgumentParser parser("This is a test program."); + args::ValueFlag foo(parser, "FOO", "test flag", {'f', "foo"}); + args::Flag bar(parser, "BAR", "test flag", {'b', "bar"}); + + const char *argv[] = {"prog", "-f", "test", "--bar"}; + const bool result = parser.ParseCLI(static_cast(sizeof(argv) / sizeof(argv[0])), argv); + + test::require(result); + test::require(foo); + test::require(*foo == "test"); + test::require(bar); + + return 0; +}