Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add implementation and tests for stpncpy() security wrapper #87

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
54 changes: 51 additions & 3 deletions include/string.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@
#include_next <string.h>

/* 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__
Expand All @@ -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_ */
10 changes: 8 additions & 2 deletions src/stpncpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <string.h>

char *
Expand Down
67 changes: 67 additions & 0 deletions src/stpncpy_chk.c
Original file line number Diff line number Diff line change
@@ -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 <stdlib.h>
#include <string.h>

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*/
209 changes: 209 additions & 0 deletions test/test_stpncpy_chk.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright (c) 2024 Frederick H. G. Wright II <[email protected]>
*
* 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 <string.h>

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 <assert.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

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;
}
7 changes: 7 additions & 0 deletions test/test_stpncpy_chk_force1.c
Original file line number Diff line number Diff line change
@@ -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"
Loading