From 8ccdadd78af7ca415f264f32f8886b4b345314c6 Mon Sep 17 00:00:00 2001 From: Fred Wright Date: Sun, 26 May 2024 16:41:15 -0700 Subject: [PATCH 1/2] Add optional security wrapper for stpncpy(). This adds support for the optional stpncpy() security wrapper, based on the _FORTIFY_SOURCE setting. By default, it only impacts 10.6 builds, though it can be explicitly enabled on 10.5, albeit less efficiently due to the lack of the compiler builtin. The stpncpy() function is the only one with an optional security wrapper which is also optionally provided by legacy-support. Hence, this is the only addition needed to close the more general ticket. Closes: https://trac.macports.org/ticket/69878 Also fixes a minor comment formatting issue. 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. Passes all tests, including newly added tests for this feature. --- include/string.h | 54 +++++++++++++++++++++++++++++++++++--- src/stpncpy.c | 10 +++++-- src/stpncpy_chk.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 src/stpncpy_chk.c diff --git a/include/string.h b/include/string.h index cee7177..163230b 100644 --- a/include/string.h +++ b/include/string.h @@ -25,12 +25,16 @@ #include_next /* stpncpy */ -#if __MP_LEGACY_SUPPORT_STPNCPY__ +/* + * If we're building with a 10.7+ SDK, stpncpy may have already been defined as + * a macro. In that case, leave it as is. This not only leaves it as provided + * by the SDK, but also informs the decision below. + */ +#if __MP_LEGACY_SUPPORT_STPNCPY__ && !defined(stpncpy) __MP__BEGIN_DECLS -#undef stpncpy /* In case built with later SDK */ extern char *stpncpy(char *dst, const char *src, size_t n); __MP__END_DECLS -#endif +#endif /* __MP_LEGACY_SUPPORT_STPNCPY__ && !defined(stpncpy) */ /* strnlen */ #if __MP_LEGACY_SUPPORT_STRNLEN__ @@ -54,4 +58,48 @@ memmem(const void *l, size_t l_len, const void *s, size_t s_len); __MP__END_DECLS #endif +/* + * Security wrapper support + * + * Rather than pushing this off into an added secure/_string.h, we just do it + * here directly. + * If security wrappers are wanted, the SDK string.h has already included + * secure/_common.h, and _USE_FORTIFY_LEVEL has been appropriately defined. + * Otherwise, _USE_FORTIFY_LEVEL is undefined. + */ +#if defined(_USE_FORTIFY_LEVEL) && _USE_FORTIFY_LEVEL > 0 + +/* stpncpy */ +/* Note the defense against building with a 10.7+ SDK, as above. */ +#if __MP_LEGACY_SUPPORT_STPNCPY__ && !defined(stpncpy) + +/* + * GCC 4.2 for 10.5 lacks __builtin___stpncpy_chk, even though GCC 4.2 + * for 10.6 has it. In the absence of a reasonable way to check for compiler + * support directly, we rely on the OS version for the decision. Note that + * the security wrapper mechanism isn't enabled by default on 10.5, anyway, + * but this allows it to work (inefficiently) if it's enabled explicitly. + */ +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1060 +extern char *__stpncpy_chk(char *dest, const char *src, size_t len, + size_t dstlen); +#define __builtin___stpncpy_chk __stpncpy_chk +#endif /* OS <10.6 */ + +#define stpncpy(dest, src, len) \ + ((__darwin_obsz0 (dest) != (size_t) -1) \ + ? __builtin___stpncpy_chk (dest, src, len, __darwin_obsz (dest)) \ + : __inline_stpncpy_chk (dest, src, len)) + +static __inline char * +__inline_stpncpy_chk (char *__restrict __dest, const char *__restrict __src, + size_t __len) +{ + return __builtin___stpncpy_chk (__dest, __src, __len, __darwin_obsz(__dest)); +} + +#endif /* __MP_LEGACY_SUPPORT_STPNCPY__ && !defined(stpncpy) */ + +#endif /* _USE_FORTIFY_LEVEL > 0 */ + #endif /* _MACPORTS_STRING_H_ */ diff --git a/src/stpncpy.c b/src/stpncpy.c index 4f6375c..469002a 100644 --- a/src/stpncpy.c +++ b/src/stpncpy.c @@ -21,20 +21,26 @@ * @APPLE_LICENSE_HEADER_END@ */ - /* +/* * NOTICE: This file was modified in April 2024 to allow * for use as a supporting file for MacPorts legacy support library. This notice * is included in support of clause 2.2 (b) of the Apple Public License, * Version 2.0. * * The code is almost verbatim from Apple except for the removal of the - * 'restrict' qualifiers for compatibility with pre-C99 compilers. + * 'restrict' qualifiers for compatibility with pre-C99 compilers, and + * the _FORTIFY_SOURCE definition here in lieu of including it as a + * compiler flag (as the Apple build procedure does). */ /* MP support header */ #include "MacportsLegacySupport.h" #if __MP_LEGACY_SUPPORT_STPNCPY__ +/* Ensure that we don't use the wrapper macro when defining the function */ +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 0 + #include char * diff --git a/src/stpncpy_chk.c b/src/stpncpy_chk.c new file mode 100644 index 0000000..893f7aa --- /dev/null +++ b/src/stpncpy_chk.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * NOTICE: This file was modified in May 2024 to allow for use as a supporting + * file for MacPorts legacy support library. This notice is included in support + * of clause 2.2 (b) of the Apple Public License, Version 2.0. + * + * The code is almost verbatim from Apple except for: + * + * The correction of the return type. + * + * The removal of the 'restrict' qualifiers for compatibility with + * pre-C99 compilers. + * + * The addition of the missing 'const' qualifier. + * + * The _FORTIFY_SOURCE definition here in lieu of providing it as a + * compiler command-line flag (as the Apple build procedure does). + */ + +/* MP support header */ +#include "MacportsLegacySupport.h" +/* Note that the support for this mechanism is absent prior to 10.5 */ +#if __MP_LEGACY_SUPPORT_STPNCPY__ && \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 + +/* Ensure that we don't create an infinitely recursive check function */ +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 0 + +#include +#include + +extern void __chk_fail (void) __attribute__((__noreturn__)); + +char * +__stpncpy_chk (char *dest, const char *src, + size_t len, size_t dstlen) +{ + if (__builtin_expect (dstlen < len, 0)) + __chk_fail (); + + return stpncpy (dest, src, len); +} + +#endif /* __MP_LEGACY_SUPPORT_STPNCPY__ && >= 10.5*/ From 0cb05308381c3521eb0a7e56e99769ef50d633f2 Mon Sep 17 00:00:00 2001 From: Fred Wright Date: Mon, 27 May 2024 21:07:31 -0700 Subject: [PATCH 2/2] 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..1636915 100644 --- a/Makefile +++ b/Makefile @@ -303,6 +303,10 @@ test_faccessat_setuid_msg: $(TESTRUNS): $(TESTRUNPREFIX)%: $(TESTNAMEPREFIX)% $< +# The "forced" tests include 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"