diff --git a/tests/test_invariant_argconfig.c b/tests/test_invariant_argconfig.c new file mode 100644 index 0000000000..fc8c251082 --- /dev/null +++ b/tests/test_invariant_argconfig.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include "../util/argconfig.h" + +START_TEST(test_argconfig_buffer_boundary) +{ + // Invariant: Help text generation must not overflow buffers regardless of option name/meta length + + struct argconfig_commandline_options options[] = { + {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 'a', "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", CFG_STRING, NULL, required_argument, "long option"}, + {"x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x" "x", 'b', "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy", CFG_STRING, NULL, required_argument, "boundary"}, + {"normal", 'n', "arg", CFG_STRING, NULL, required_argument, "valid"}, + {NULL} + }; + + char help_buffer[4096]; + memset(help_buffer, 0xAA, sizeof(help_buffer)); + help_buffer[sizeof(help_buffer) - 1] = '\0'; + + int result = argconfig_parse(1, (char *[]){"test"}, "test", options); + + // Verify no buffer corruption: canary bytes beyond reasonable help text size should be untouched + int corruption_detected = 0; + for (size_t i = 3000; i < sizeof(help_buffer) - 1; i++) { + if (help_buffer[i] != (char)0xAA) { + corruption_detected = 1; + break; + } + } + + ck_assert_int_eq(corruption_detected, 0); +} +END_TEST + +Suite *security_suite(void) +{ + Suite *s; + TCase *tc_core; + + s = suite_create("Security"); + tc_core = tcase_create("Core"); + + tcase_add_test(tc_core, test_argconfig_buffer_boundary); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) +{ + int number_failed; + Suite *s; + SRunner *sr; + + s = security_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} \ No newline at end of file diff --git a/util/argconfig.c b/util/argconfig.c index b21af124d1..3c887eb507 100644 --- a/util/argconfig.c +++ b/util/argconfig.c @@ -98,25 +98,30 @@ static void show_option(const struct argconfig_commandline_options *option) { char buffer[0x1000]; char *b = buffer; + int rem = sizeof(buffer); + int n; - b += sprintf(b, " [ "); +#define BPRINTF(fmt, ...) do { n = snprintf(b, rem, fmt, ##__VA_ARGS__); if (n > 0 && n < rem) { b += n; rem -= n; } } while (0) + + BPRINTF(" [ "); if (option->option) { - b += sprintf(b, " --%s", option->option); + BPRINTF(" --%s", option->option); if (option->argument_type == optional_argument) - b += sprintf(b, "[=<%s>]", option->meta ? option->meta : "arg"); + BPRINTF("[=<%s>]", option->meta ? option->meta : "arg"); if (option->argument_type == required_argument) - b += sprintf(b, "=<%s>", option->meta ? option->meta : "arg"); + BPRINTF("=<%s>", option->meta ? option->meta : "arg"); if (option->short_option) - b += sprintf(b, ","); + BPRINTF(","); } if (option->short_option) { - b += sprintf(b, " -%c", option->short_option); + BPRINTF(" -%c", option->short_option); if (option->argument_type == optional_argument) - b += sprintf(b, " [<%s>]", option->meta ? option->meta : "arg"); + BPRINTF(" [<%s>]", option->meta ? option->meta : "arg"); if (option->argument_type == required_argument) - b += sprintf(b, " <%s>", option->meta ? option->meta : "arg"); + BPRINTF(" <%s>", option->meta ? option->meta : "arg"); } - b += sprintf(b, " ] "); + BPRINTF(" ] "); +#undef BPRINTF fprintf(stderr, "%s", buffer); if (option->help) {