From ca203e53d774ca346b568ae0f0ef3bb4829cefc4 Mon Sep 17 00:00:00 2001 From: Fred Wright Date: Mon, 27 May 2024 21:00:29 -0700 Subject: [PATCH] Add tests for stpncpy() security wrapper. This adds three tests for the proper behavior of the stpncpy() security wrapper, both with and without explictly setting _FORTIFY_SOURCE. TESTED: Tested on 10.4-10.5 ppc, 10.4-10.6 i386, 10.5-10.6 ppc (i386 Rosetta), 10.5-12.x x86_64, 11.x-14.x arm64. Fails in expected cases without the corresponding fix, and passes in all cases with the fix. --- Makefile | 4 + test/test_stpncpy_chk.c | 209 +++++++++++++++++++++++++++++++++ test/test_stpncpy_chk_force1.c | 7 ++ test/test_stpncpy_chk_forced.c | 12 ++ 4 files changed, 232 insertions(+) create mode 100644 test/test_stpncpy_chk.c create mode 100644 test/test_stpncpy_chk_force1.c create mode 100644 test/test_stpncpy_chk_forced.c diff --git a/Makefile b/Makefile index a6adfea..5b97de3 100644 --- a/Makefile +++ b/Makefile @@ -303,6 +303,10 @@ test_faccessat_setuid_msg: $(TESTRUNS): $(TESTRUNPREFIX)%: $(TESTNAMEPREFIX)% $< +# The "forced" tests includes the unforced source +$(TESTNAMEPREFIX)stpncpy_chk_forced.o: $(TESTNAMEPREFIX)stpncpy_chk.c +$(TESTNAMEPREFIX)stpncpy_chk_force1.o: $(TESTNAMEPREFIX)stpncpy_chk.c + install: install-headers install-lib install-headers: diff --git a/test/test_stpncpy_chk.c b/test/test_stpncpy_chk.c new file mode 100644 index 0000000..5fcd315 --- /dev/null +++ b/test/test_stpncpy_chk.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024 Frederick H. G. Wright II + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides test cases for the stpncpy() security wrapper. + * There may or may not be compile-time warnings in some cases, but + * the final pass/fail should be as expected. + * + * Informational messages regarding the relevant compile flags are also + * included. + */ + +/* Allow deferring the stdio.h include */ +int printf(const char *format, ...); + +#define PRINT_VAR(x) printf(" %s = %lld\n", #x, (long long) x) +#define PRINT_UNDEF(x) printf(" " #x " is undefined\n") + +void +print_before(void) +{ + printf(" Before string.h include:\n"); + #ifdef _FORTIFY_SOURCE + PRINT_VAR(_FORTIFY_SOURCE); + #else + PRINT_UNDEF(_FORTIFY_SOURCE); + #endif + #ifdef _USE_FORTIFY_LEVEL + PRINT_VAR(_USE_FORTIFY_LEVEL); + #else + PRINT_UNDEF(_USE_FORTIFY_LEVEL); + #endif +} + +#include + +void +print_after(void) +{ + printf(" After string.h include:\n"); + #ifdef _FORTIFY_SOURCE + PRINT_VAR(_FORTIFY_SOURCE); + #else + PRINT_UNDEF(_FORTIFY_SOURCE); + #endif + #ifdef _USE_FORTIFY_LEVEL + PRINT_VAR(_USE_FORTIFY_LEVEL); + #else + PRINT_UNDEF(_USE_FORTIFY_LEVEL); + #endif +} + +#if defined(_USE_FORTIFY_LEVEL) && _USE_FORTIFY_LEVEL > 0 +#define CHECKS_ARE_ENABLED 1 +#else +#define CHECKS_ARE_ENABLED 0 +#endif + +#include +#include +#include +#include +#include +#include + +const char *test_str = "The Quick Brown Fox"; + +#define BUF_LEN 128 /* Generously longer than test_str */ +static char dest[BUF_LEN]; + +/* Test stpncpy() known good at compile time */ +void +test_good_stpncpy_static(void) +{ + (void) stpncpy(dest, test_str, BUF_LEN); +} + +/* Test stpncpy() known bad at compile time */ +void +test_bad_stpncpy_static(void) +{ + (void) stpncpy(dest, test_str, BUF_LEN+1); +} + +/* Test stpncpy() known good as auto buf */ +void +test_good_stpncpy_auto(void) +{ + char buf[BUF_LEN]; + + (void) stpncpy(buf, test_str, BUF_LEN); +} + +/* Test stpncpy() known bad as auto buf */ +void +test_bad_stpncpy_auto(void) +{ + char buf[BUF_LEN]; + + (void) stpncpy(buf, test_str, BUF_LEN+1); +} + +/* + * Some documentation suggests that the checks can work for cases where + * the output buffer length isn't known at compile time. Empirically, + * this does not seem to be the case, though perhaps there are tools which + * can make this work for testing. + * + * Meanwhile, we include a couple of tests for this, but disable them. + */ + +/* Test stpncpy() known good as runtime malloc() */ +void +test_good_stpncpy_runtime(void) +{ + char *buf; + + buf = malloc(BUF_LEN); + if (!buf) { + perror("malloc() failed"); + test_bad_stpncpy_static(); /* Try to provoke failure */ + return; + } + (void) stpncpy(buf, test_str, BUF_LEN); + free(buf); +} + +/* Test stpncpy() known bad as runtime malloc() */ +void +test_bad_stpncpy_runtime(void) +{ + char *buf; + + buf = malloc(BUF_LEN); + if (!buf) { + perror("malloc() failed"); + return; + } + (void) stpncpy(buf, test_str, BUF_LEN+1); + free(buf); +} + +typedef void test_func_t(void); + +/* Run test func as a subprocess, to capture abort() */ +int +run_test_func(test_func_t *func) +{ + pid_t child, done; + int status; + + child = fork(); + if (child < 0) { + perror("fork() failed"); + exit(100); + } + if (child == 0) { + (*func)(); + exit(0); + } + done = wait(&status); + if (done != child) { + fprintf(stderr, "Unexpected wait() pid, %d != %d\n", done, child); + exit(110); + } + return status; +} + +int +main(int argc, char *argv[]) +{ + int have_compile_time_checks = CHECKS_ARE_ENABLED; + int have_runtime_checks = 0; /* These don't currently work */ + + (void) argc; + + printf("Running %s\n", basename(argv[0])); + print_before(); + print_after(); + /* Forking with unflushed buffers may produce duplicate output. */ + fflush(NULL); + + if (have_compile_time_checks) { + assert(run_test_func(&test_good_stpncpy_static) == 0); + assert(run_test_func(&test_bad_stpncpy_static) != 0); + assert(run_test_func(&test_good_stpncpy_auto) == 0); + assert(run_test_func(&test_bad_stpncpy_auto) != 0); + } + if (have_runtime_checks) { + assert(run_test_func(&test_good_stpncpy_runtime) == 0); + assert(run_test_func(&test_bad_stpncpy_runtime) != 0); + } + + printf("Tests pass\n"); + return 0; +} diff --git a/test/test_stpncpy_chk_force1.c b/test/test_stpncpy_chk_force1.c new file mode 100644 index 0000000..b09ae0f --- /dev/null +++ b/test/test_stpncpy_chk_force1.c @@ -0,0 +1,7 @@ +/* + * Version of test_stpncpy_chk with enable forced to 1 (not the usual 2). + */ + +#define _FORTIFY_SOURCE 1 + +#include "test_stpncpy_chk.c" diff --git a/test/test_stpncpy_chk_forced.c b/test/test_stpncpy_chk_forced.c new file mode 100644 index 0000000..ee95a05 --- /dev/null +++ b/test/test_stpncpy_chk_forced.c @@ -0,0 +1,12 @@ +/* + * Version of test_stpncpy_chk with enable forced on. + * + * The security wrapper mechanism is unavailable on 10.4, available but + * defaulted off in 10.5, and enabled and defaulted on in 10.6+. + * Overriding the default here enables it on 10.5, with no effect on other + * OS versions. + */ + +#define _FORTIFY_SOURCE 2 + +#include "test_stpncpy_chk.c"