mirror of
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD.git
synced 2024-11-25 01:55:19 +01:00
libc: tests: add testing infrastructure for _FORTIFY_SOURCE
The _FORTIFY_SOURCE tests will be generated by a lua script to avoid a lot of redundancy in writing these tests. For each function that we're fortifying, the plan is to test at least the following three scenarios: - Writing up to one byte before the end of the buffer, - Writing up to the end of the buffer, - Writing one byte past the end of the buffer The buffer is shoved into a struct on the stack to guarantee a stack layout in which we have a valid byte after the buffer so that level 2 fortification will trip and we can have confidence that it wasn't some other stack/memory protection instead. The generated tests are divided roughly into which header we're attributing them to so that we can parallelize the build -- the full set is a bit over 9000 lines of C and takes 11s to build on the hardware that I'm testing on if it's a single monolothic file. Reviewed by: markj Sponsored by: Klara, Inc. Sponsored by: Stormshield Differential Revision: https://reviews.freebsd.org/D45678
This commit is contained in:
parent
4719366192
commit
020d003c86
@ -372,6 +372,8 @@
|
||||
..
|
||||
rpc
|
||||
..
|
||||
secure
|
||||
..
|
||||
setjmp
|
||||
..
|
||||
ssp
|
||||
|
@ -13,6 +13,7 @@ TESTS_SUBDIRS+= nss
|
||||
TESTS_SUBDIRS+= regex
|
||||
TESTS_SUBDIRS+= resolv
|
||||
TESTS_SUBDIRS+= rpc
|
||||
TESTS_SUBDIRS+= secure
|
||||
TESTS_SUBDIRS+= setjmp
|
||||
TESTS_SUBDIRS+= stdio
|
||||
TESTS_SUBDIRS+= stdlib
|
||||
|
20
lib/libc/tests/secure/Makefile
Normal file
20
lib/libc/tests/secure/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
.include <bsd.own.mk>
|
||||
|
||||
TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/}
|
||||
|
||||
FORTIFY_TCATS+= stdio
|
||||
FORTIFY_TCATS+= string
|
||||
FORTIFY_TCATS+= strings
|
||||
FORTIFY_TCATS+= unistd
|
||||
|
||||
# Manually run after updating the test generator.
|
||||
generate-tests: .PHONY
|
||||
.for tcat in ${FORTIFY_TCATS}
|
||||
ATF_TESTS_C+= fortify_${tcat}_test
|
||||
|
||||
generate-tests: generate-tests-${tcat}
|
||||
generate-tests-${tcat}: .PHONY
|
||||
${.CURDIR}/generate-fortify-tests.lua ${tcat} > ${.CURDIR}/fortify_${tcat}_test.c
|
||||
.endfor
|
||||
|
||||
.include <bsd.test.mk>
|
383
lib/libc/tests/secure/fortify_stdio_test.c
Normal file
383
lib/libc/tests/secure/fortify_stdio_test.c
Normal file
@ -0,0 +1,383 @@
|
||||
/* @generated by `generate-fortify-tests.lua "stdio"` */
|
||||
|
||||
#define _FORTIFY_SOURCE 2
|
||||
#define TMPFILE_SIZE (1024 * 32)
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
#include <atf-c.h>
|
||||
|
||||
/*
|
||||
* Create a new symlink to use for readlink(2) style tests, we'll just use a
|
||||
* random target name to have something interesting to look at.
|
||||
*/
|
||||
static const char * __unused
|
||||
new_symlink(size_t __len)
|
||||
{
|
||||
static const char linkname[] = "link";
|
||||
char target[MAXNAMLEN];
|
||||
int error;
|
||||
|
||||
ATF_REQUIRE(__len <= sizeof(target));
|
||||
|
||||
arc4random_buf(target, sizeof(target));
|
||||
|
||||
error = unlink(linkname);
|
||||
ATF_REQUIRE(error == 0 || errno == ENOENT);
|
||||
|
||||
error = symlink(target, linkname);
|
||||
ATF_REQUIRE(error == 0);
|
||||
|
||||
return (linkname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a tmpfile that we can use for testing read(2) and friends.
|
||||
*/
|
||||
static int __unused
|
||||
new_tmpfile(void)
|
||||
{
|
||||
char buf[1024];
|
||||
ssize_t rv;
|
||||
size_t written;
|
||||
int fd;
|
||||
|
||||
fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||||
ATF_REQUIRE(fd >= 0);
|
||||
|
||||
written = 0;
|
||||
while (written < TMPFILE_SIZE) {
|
||||
rv = write(fd, buf, sizeof(buf));
|
||||
ATF_REQUIRE(rv > 0);
|
||||
|
||||
written += rv;
|
||||
}
|
||||
|
||||
ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET));
|
||||
return (fd);
|
||||
}
|
||||
|
||||
static void
|
||||
disable_coredumps(void)
|
||||
{
|
||||
struct rlimit rl = { 0 };
|
||||
|
||||
if (setrlimit(RLIMIT_CORE, &rl) == -1)
|
||||
_exit(EX_OSERR);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(sprintf_before_end);
|
||||
ATF_TC_BODY(sprintf_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
sprintf(__stack.__buf, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(sprintf_end);
|
||||
ATF_TC_BODY(sprintf_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
sprintf(__stack.__buf, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(sprintf_heap_before_end);
|
||||
ATF_TC_BODY(sprintf_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
sprintf(__stack.__buf, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(sprintf_heap_end);
|
||||
ATF_TC_BODY(sprintf_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
sprintf(__stack.__buf, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(sprintf_heap_after_end);
|
||||
ATF_TC_BODY(sprintf_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
sprintf(__stack.__buf, "%.*s", (int)__len - 1, srcvar);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(snprintf_before_end);
|
||||
ATF_TC_BODY(snprintf_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
snprintf(__stack.__buf, __len, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(snprintf_end);
|
||||
ATF_TC_BODY(snprintf_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
snprintf(__stack.__buf, __len, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(snprintf_heap_before_end);
|
||||
ATF_TC_BODY(snprintf_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
snprintf(__stack.__buf, __len, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(snprintf_heap_end);
|
||||
ATF_TC_BODY(snprintf_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
snprintf(__stack.__buf, __len, "%.*s", (int)__len - 1, srcvar);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(snprintf_heap_after_end);
|
||||
ATF_TC_BODY(snprintf_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
char srcvar[__len + 10];
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
|
||||
snprintf(__stack.__buf, __len, "%.*s", (int)__len - 1, srcvar);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, sprintf_before_end);
|
||||
ATF_TP_ADD_TC(tp, sprintf_end);
|
||||
ATF_TP_ADD_TC(tp, sprintf_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, sprintf_heap_end);
|
||||
ATF_TP_ADD_TC(tp, sprintf_heap_after_end);
|
||||
ATF_TP_ADD_TC(tp, snprintf_before_end);
|
||||
ATF_TP_ADD_TC(tp, snprintf_end);
|
||||
ATF_TP_ADD_TC(tp, snprintf_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, snprintf_heap_end);
|
||||
ATF_TP_ADD_TC(tp, snprintf_heap_after_end);
|
||||
return (atf_no_error());
|
||||
}
|
1415
lib/libc/tests/secure/fortify_string_test.c
Normal file
1415
lib/libc/tests/secure/fortify_string_test.c
Normal file
File diff suppressed because it is too large
Load Diff
354
lib/libc/tests/secure/fortify_strings_test.c
Normal file
354
lib/libc/tests/secure/fortify_strings_test.c
Normal file
@ -0,0 +1,354 @@
|
||||
/* @generated by `generate-fortify-tests.lua "strings"` */
|
||||
|
||||
#define _FORTIFY_SOURCE 2
|
||||
#define TMPFILE_SIZE (1024 * 32)
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
#include <atf-c.h>
|
||||
|
||||
/*
|
||||
* Create a new symlink to use for readlink(2) style tests, we'll just use a
|
||||
* random target name to have something interesting to look at.
|
||||
*/
|
||||
static const char * __unused
|
||||
new_symlink(size_t __len)
|
||||
{
|
||||
static const char linkname[] = "link";
|
||||
char target[MAXNAMLEN];
|
||||
int error;
|
||||
|
||||
ATF_REQUIRE(__len <= sizeof(target));
|
||||
|
||||
arc4random_buf(target, sizeof(target));
|
||||
|
||||
error = unlink(linkname);
|
||||
ATF_REQUIRE(error == 0 || errno == ENOENT);
|
||||
|
||||
error = symlink(target, linkname);
|
||||
ATF_REQUIRE(error == 0);
|
||||
|
||||
return (linkname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a tmpfile that we can use for testing read(2) and friends.
|
||||
*/
|
||||
static int __unused
|
||||
new_tmpfile(void)
|
||||
{
|
||||
char buf[1024];
|
||||
ssize_t rv;
|
||||
size_t written;
|
||||
int fd;
|
||||
|
||||
fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||||
ATF_REQUIRE(fd >= 0);
|
||||
|
||||
written = 0;
|
||||
while (written < TMPFILE_SIZE) {
|
||||
rv = write(fd, buf, sizeof(buf));
|
||||
ATF_REQUIRE(rv > 0);
|
||||
|
||||
written += rv;
|
||||
}
|
||||
|
||||
ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET));
|
||||
return (fd);
|
||||
}
|
||||
|
||||
static void
|
||||
disable_coredumps(void)
|
||||
{
|
||||
struct rlimit rl = { 0 };
|
||||
|
||||
if (setrlimit(RLIMIT_CORE, &rl) == -1)
|
||||
_exit(EX_OSERR);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bcopy_before_end);
|
||||
ATF_TC_BODY(bcopy_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char src[__len + 10];
|
||||
|
||||
bcopy(src, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bcopy_end);
|
||||
ATF_TC_BODY(bcopy_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char src[__len + 10];
|
||||
|
||||
bcopy(src, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bcopy_heap_before_end);
|
||||
ATF_TC_BODY(bcopy_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char src[__len + 10];
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
bcopy(src, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bcopy_heap_end);
|
||||
ATF_TC_BODY(bcopy_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
char src[__len + 10];
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
bcopy(src, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bcopy_heap_after_end);
|
||||
ATF_TC_BODY(bcopy_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
char src[__len + 10];
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
bcopy(src, __stack.__buf, __len);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bzero_before_end);
|
||||
ATF_TC_BODY(bzero_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
bzero(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bzero_end);
|
||||
ATF_TC_BODY(bzero_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
bzero(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bzero_heap_before_end);
|
||||
ATF_TC_BODY(bzero_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
bzero(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bzero_heap_end);
|
||||
ATF_TC_BODY(bzero_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
bzero(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(bzero_heap_after_end);
|
||||
ATF_TC_BODY(bzero_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
bzero(__stack.__buf, __len);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, bcopy_before_end);
|
||||
ATF_TP_ADD_TC(tp, bcopy_end);
|
||||
ATF_TP_ADD_TC(tp, bcopy_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, bcopy_heap_end);
|
||||
ATF_TP_ADD_TC(tp, bcopy_heap_after_end);
|
||||
ATF_TP_ADD_TC(tp, bzero_before_end);
|
||||
ATF_TP_ADD_TC(tp, bzero_end);
|
||||
ATF_TP_ADD_TC(tp, bzero_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, bzero_heap_end);
|
||||
ATF_TP_ADD_TC(tp, bzero_heap_after_end);
|
||||
return (atf_no_error());
|
||||
}
|
505
lib/libc/tests/secure/fortify_unistd_test.c
Normal file
505
lib/libc/tests/secure/fortify_unistd_test.c
Normal file
@ -0,0 +1,505 @@
|
||||
/* @generated by `generate-fortify-tests.lua "unistd"` */
|
||||
|
||||
#define _FORTIFY_SOURCE 2
|
||||
#define TMPFILE_SIZE (1024 * 32)
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
#include <atf-c.h>
|
||||
|
||||
/*
|
||||
* Create a new symlink to use for readlink(2) style tests, we'll just use a
|
||||
* random target name to have something interesting to look at.
|
||||
*/
|
||||
static const char * __unused
|
||||
new_symlink(size_t __len)
|
||||
{
|
||||
static const char linkname[] = "link";
|
||||
char target[MAXNAMLEN];
|
||||
int error;
|
||||
|
||||
ATF_REQUIRE(__len <= sizeof(target));
|
||||
|
||||
arc4random_buf(target, sizeof(target));
|
||||
|
||||
error = unlink(linkname);
|
||||
ATF_REQUIRE(error == 0 || errno == ENOENT);
|
||||
|
||||
error = symlink(target, linkname);
|
||||
ATF_REQUIRE(error == 0);
|
||||
|
||||
return (linkname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a tmpfile that we can use for testing read(2) and friends.
|
||||
*/
|
||||
static int __unused
|
||||
new_tmpfile(void)
|
||||
{
|
||||
char buf[1024];
|
||||
ssize_t rv;
|
||||
size_t written;
|
||||
int fd;
|
||||
|
||||
fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||||
ATF_REQUIRE(fd >= 0);
|
||||
|
||||
written = 0;
|
||||
while (written < TMPFILE_SIZE) {
|
||||
rv = write(fd, buf, sizeof(buf));
|
||||
ATF_REQUIRE(rv > 0);
|
||||
|
||||
written += rv;
|
||||
}
|
||||
|
||||
ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET));
|
||||
return (fd);
|
||||
}
|
||||
|
||||
static void
|
||||
disable_coredumps(void)
|
||||
{
|
||||
struct rlimit rl = { 0 };
|
||||
|
||||
if (setrlimit(RLIMIT_CORE, &rl) == -1)
|
||||
_exit(EX_OSERR);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(getcwd_before_end);
|
||||
ATF_TC_BODY(getcwd_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[8];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 8 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
getcwd(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(getcwd_end);
|
||||
ATF_TC_BODY(getcwd_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[8];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 8;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
getcwd(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(getcwd_heap_before_end);
|
||||
ATF_TC_BODY(getcwd_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (8);
|
||||
const size_t __len = 8 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
getcwd(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(getcwd_heap_end);
|
||||
ATF_TC_BODY(getcwd_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (8);
|
||||
const size_t __len = 8;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
getcwd(__stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(getcwd_heap_after_end);
|
||||
ATF_TC_BODY(getcwd_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (8);
|
||||
const size_t __len = 8 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
|
||||
getcwd(__stack.__buf, __len);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(read_before_end);
|
||||
ATF_TC_BODY(read_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[41];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 41 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
int fd;
|
||||
|
||||
fd = new_tmpfile(); /* Cannot fail */
|
||||
|
||||
read(fd, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(read_end);
|
||||
ATF_TC_BODY(read_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[41];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 41;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
int fd;
|
||||
|
||||
fd = new_tmpfile(); /* Cannot fail */
|
||||
|
||||
read(fd, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(read_heap_before_end);
|
||||
ATF_TC_BODY(read_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (41);
|
||||
const size_t __len = 41 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
int fd;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
fd = new_tmpfile(); /* Cannot fail */
|
||||
|
||||
read(fd, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(read_heap_end);
|
||||
ATF_TC_BODY(read_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (41);
|
||||
const size_t __len = 41;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
int fd;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
fd = new_tmpfile(); /* Cannot fail */
|
||||
|
||||
read(fd, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(read_heap_after_end);
|
||||
ATF_TC_BODY(read_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (41);
|
||||
const size_t __len = 41 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
int fd;
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
fd = new_tmpfile(); /* Cannot fail */
|
||||
|
||||
read(fd, __stack.__buf, __len);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(readlink_before_end);
|
||||
ATF_TC_BODY(readlink_before_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
const char *path;
|
||||
|
||||
path = new_symlink(__len); /* Cannot fail */
|
||||
|
||||
readlink(path, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(readlink_end);
|
||||
ATF_TC_BODY(readlink_end, tc)
|
||||
{
|
||||
#define BUF &__stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char __buf[42];
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(__stack.__buf);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
const char *path;
|
||||
|
||||
path = new_symlink(__len); /* Cannot fail */
|
||||
|
||||
readlink(path, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(readlink_heap_before_end);
|
||||
ATF_TC_BODY(readlink_heap_before_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 - 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
const char *path;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
path = new_symlink(__len); /* Cannot fail */
|
||||
|
||||
readlink(path, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(readlink_heap_end);
|
||||
ATF_TC_BODY(readlink_heap_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
const char *path;
|
||||
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
path = new_symlink(__len); /* Cannot fail */
|
||||
|
||||
readlink(path, __stack.__buf, __len);
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(readlink_heap_after_end);
|
||||
ATF_TC_BODY(readlink_heap_after_end, tc)
|
||||
{
|
||||
#define BUF __stack.__buf
|
||||
struct {
|
||||
uint8_t padding_l;
|
||||
unsigned char * __buf;
|
||||
uint8_t padding_r;
|
||||
} __stack;
|
||||
const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
|
||||
const size_t __len = 42 + 1;
|
||||
const size_t __idx __unused = __len - 1;
|
||||
pid_t __child;
|
||||
int __status;
|
||||
const char *path;
|
||||
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
__stack.__buf = malloc(__bufsz);
|
||||
path = new_symlink(__len); /* Cannot fail */
|
||||
|
||||
readlink(path, __stack.__buf, __len);
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
#undef BUF
|
||||
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, getcwd_before_end);
|
||||
ATF_TP_ADD_TC(tp, getcwd_end);
|
||||
ATF_TP_ADD_TC(tp, getcwd_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, getcwd_heap_end);
|
||||
ATF_TP_ADD_TC(tp, getcwd_heap_after_end);
|
||||
ATF_TP_ADD_TC(tp, read_before_end);
|
||||
ATF_TP_ADD_TC(tp, read_end);
|
||||
ATF_TP_ADD_TC(tp, read_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, read_heap_end);
|
||||
ATF_TP_ADD_TC(tp, read_heap_after_end);
|
||||
ATF_TP_ADD_TC(tp, readlink_before_end);
|
||||
ATF_TP_ADD_TC(tp, readlink_end);
|
||||
ATF_TP_ADD_TC(tp, readlink_heap_before_end);
|
||||
ATF_TP_ADD_TC(tp, readlink_heap_end);
|
||||
ATF_TP_ADD_TC(tp, readlink_heap_after_end);
|
||||
return (atf_no_error());
|
||||
}
|
706
lib/libc/tests/secure/generate-fortify-tests.lua
Executable file
706
lib/libc/tests/secure/generate-fortify-tests.lua
Executable file
@ -0,0 +1,706 @@
|
||||
#!/usr/libexec/flua
|
||||
--
|
||||
-- SPDX-License-Identifier: BSD-2-Clause
|
||||
--
|
||||
-- Copyright (c) 2024, Klara, Inc.
|
||||
--
|
||||
-- Redistribution and use in source and binary forms, with or without
|
||||
-- modification, are permitted provided that the following conditions
|
||||
-- are met:
|
||||
-- 1. Redistributions of source code must retain the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer.
|
||||
-- 2. Redistributions in binary form must reproduce the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer in the
|
||||
-- documentation and/or other materials provided with the distribution.
|
||||
--
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
-- SUCH DAMAGE.
|
||||
--
|
||||
|
||||
-- THEORY OF OPERATION
|
||||
--
|
||||
-- generate-fortify-tests.lua is intended to test fortified functions as found
|
||||
-- mostly in the various headers in /usr/include/ssp. Each fortified function
|
||||
-- gets three basic tests:
|
||||
--
|
||||
-- 1. Write just before the end of the buffer,
|
||||
-- 2. Write right at the end of the buffer,
|
||||
-- 3. Write just after the end of the buffer.
|
||||
--
|
||||
-- Each test is actually generated twice: once with a buffer on the stack, and
|
||||
-- again with a buffer on the heap, to confirm that __builtin_object_size(3) can
|
||||
-- deduce the buffer size in both scenarios. The tests work by setting up the
|
||||
-- stack with our buffer (and some padding on either side to avoid tripping any
|
||||
-- other stack or memory protection), doing any initialization as described by
|
||||
-- the test definition, then calling the fortified function with the buffer as
|
||||
-- outlined by the test definition.
|
||||
--
|
||||
-- For the 'before' and 'at' the end tests, we're ensuring that valid writes
|
||||
-- that are on the verge of being invalid aren't accidentally being detected as
|
||||
-- invalid.
|
||||
--
|
||||
-- The 'after' test is the one that actually tests the functional benefit of
|
||||
-- _FORTIFY_SOURCE by violating a boundary that should trigger an abort. As
|
||||
-- such, this test differs more from the other two in that it has to fork() off
|
||||
-- the fortified function call so that we can monitor for a SIGABRT and
|
||||
-- pass/fail the test at function end appropriately.
|
||||
|
||||
-- Some tests, like the FD_*() macros, may define these differently. For
|
||||
-- instance, for fd sets we're varying the index we pass and not using arbitrary
|
||||
-- buffers. Other tests that don't use the length in any way may physically
|
||||
-- vary the buffer size for each test case when we'd typically vary the length
|
||||
-- we're requesting a write for.
|
||||
|
||||
local includes = {
|
||||
"sys/param.h",
|
||||
"sys/resource.h",
|
||||
"sys/time.h",
|
||||
"sys/wait.h",
|
||||
"dirent.h",
|
||||
"errno.h",
|
||||
"fcntl.h",
|
||||
"limits.h",
|
||||
"signal.h",
|
||||
"stdio.h",
|
||||
"stdlib.h",
|
||||
"string.h",
|
||||
"strings.h",
|
||||
"sysexits.h",
|
||||
"unistd.h",
|
||||
"atf-c.h",
|
||||
}
|
||||
|
||||
local tests_added = {}
|
||||
|
||||
-- Some of these will need to be excluded because clang sees the wrong size when
|
||||
-- an array is embedded inside a struct, we'll get something that looks more
|
||||
-- like __builtin_object_size(ptr, 0) than it does the correct
|
||||
-- __builtin_object_size(ptr, 1) (i.e., includes the padding after). This is
|
||||
-- almost certainly a bug in llvm.
|
||||
local function excludes_stack_overflow(disposition, is_heap)
|
||||
return (not is_heap) and disposition > 0
|
||||
end
|
||||
|
||||
local printf_stackvars = "\tchar srcvar[__len + 10];\n"
|
||||
local printf_init = [[
|
||||
memset(srcvar, 'A', sizeof(srcvar) - 1);
|
||||
srcvar[sizeof(srcvar) - 1] = '\0';
|
||||
]]
|
||||
|
||||
local string_stackvars = "\tchar src[__len];\n"
|
||||
local string_init = [[
|
||||
memset(__stack.__buf, 0, __len);
|
||||
memset(src, 'A', __len - 1);
|
||||
src[__len - 1] = '\0';
|
||||
]]
|
||||
|
||||
-- Each test entry describes how to test a given function. We need to know how
|
||||
-- to construct the buffer, we need to know the argument set we're dealing with,
|
||||
-- and we need to know what we're passing to each argument. We could be passing
|
||||
-- fixed values, or we could be passing the __buf under test.
|
||||
--
|
||||
-- definition:
|
||||
-- func: name of the function under test to call
|
||||
-- bufsize: size of buffer to generate, defaults to 42
|
||||
-- buftype: type of buffer to generate, defaults to unsigned char[]
|
||||
-- arguments: __buf, __len, or the name of a variable placed on the stack
|
||||
-- exclude: a function(disposition, is_heap) that returns true if this combo
|
||||
-- should be excluded.
|
||||
-- stackvars: extra variables to be placed on the stack, should be a string
|
||||
-- optionally formatted with tabs and newlines
|
||||
-- init: extra code to inject just before the function call for initialization
|
||||
-- of the buffer or any of the above-added stackvars; also a string
|
||||
-- uses_len: bool-ish, necessary if arguments doesn't include either __idx or
|
||||
-- or __len so that the test generator doesn't try to vary the size of the
|
||||
-- buffer instead of just manipulating __idx/__len to try and induce an
|
||||
-- overflow.
|
||||
--
|
||||
-- Most tests will just use the default bufsize/buftype, but under some
|
||||
-- circumstances it's useful to use a different type (e.g., for alignment
|
||||
-- requirements).
|
||||
local all_tests = {
|
||||
stdio = {
|
||||
-- <stdio.h>
|
||||
{
|
||||
func = "sprintf",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"\"%.*s\"",
|
||||
"(int)__len - 1", -- - 1 for NUL terminator
|
||||
"srcvar",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = printf_stackvars,
|
||||
init = printf_init,
|
||||
},
|
||||
{
|
||||
func = "snprintf",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"__len",
|
||||
"\"%.*s\"",
|
||||
"(int)__len - 1", -- - 1 for NUL terminator
|
||||
"srcvar",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = printf_stackvars,
|
||||
init = printf_init,
|
||||
},
|
||||
},
|
||||
string = {
|
||||
-- <string.h>
|
||||
{
|
||||
func = "memcpy",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = "\tchar src[__len + 10];\n",
|
||||
},
|
||||
{
|
||||
func = "memmove",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = "\tchar src[__len + 10];\n",
|
||||
},
|
||||
{
|
||||
func = "memset",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"0",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
},
|
||||
{
|
||||
func = "stpcpy",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = string_stackvars,
|
||||
init = string_init,
|
||||
uses_len = true,
|
||||
},
|
||||
{
|
||||
func = "stpncpy",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = string_stackvars,
|
||||
init = string_init,
|
||||
},
|
||||
{
|
||||
func = "strcat",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = string_stackvars,
|
||||
init = string_init,
|
||||
uses_len = true,
|
||||
},
|
||||
{
|
||||
func = "strncat",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = string_stackvars,
|
||||
init = string_init,
|
||||
},
|
||||
{
|
||||
func = "strcpy",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = string_stackvars,
|
||||
init = string_init,
|
||||
uses_len = true,
|
||||
},
|
||||
{
|
||||
func = "strncpy",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"src",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = string_stackvars,
|
||||
init = string_init,
|
||||
},
|
||||
},
|
||||
strings = {
|
||||
-- <strings.h>
|
||||
{
|
||||
func = "bcopy",
|
||||
arguments = {
|
||||
"src",
|
||||
"__buf",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = "\tchar src[__len + 10];\n",
|
||||
},
|
||||
{
|
||||
func = "bzero",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
},
|
||||
},
|
||||
unistd = {
|
||||
-- <unistd.h>
|
||||
{
|
||||
func = "getcwd",
|
||||
bufsize = "8",
|
||||
arguments = {
|
||||
"__buf",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
},
|
||||
{
|
||||
func = "read",
|
||||
bufsize = "41",
|
||||
arguments = {
|
||||
"fd",
|
||||
"__buf",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = "\tint fd;\n",
|
||||
init = [[
|
||||
fd = new_tmpfile(); /* Cannot fail */
|
||||
]],
|
||||
},
|
||||
{
|
||||
func = "readlink",
|
||||
arguments = {
|
||||
"path",
|
||||
"__buf",
|
||||
"__len",
|
||||
},
|
||||
exclude = excludes_stack_overflow,
|
||||
stackvars = "\tconst char *path;\n",
|
||||
init = [[
|
||||
path = new_symlink(__len); /* Cannot fail */
|
||||
]],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local function write_test_boilerplate(fh, name, body)
|
||||
fh:write("ATF_TC_WITHOUT_HEAD(" .. name .. ");\n")
|
||||
fh:write("ATF_TC_BODY(" .. name .. ", tc)\n")
|
||||
fh:write("{\n" .. body .. "\n}\n\n")
|
||||
return name
|
||||
end
|
||||
|
||||
local function generate_test_name(func, variant, disposition, heap)
|
||||
local basename = func
|
||||
if variant then
|
||||
basename = basename .. "_" .. variant
|
||||
end
|
||||
if heap then
|
||||
basename = basename .. "_heap"
|
||||
end
|
||||
if disposition < 0 then
|
||||
return basename .. "_before_end"
|
||||
elseif disposition == 0 then
|
||||
return basename .. "_end"
|
||||
else
|
||||
return basename .. "_after_end"
|
||||
end
|
||||
end
|
||||
|
||||
local function array_type(buftype)
|
||||
if not buftype:match("%[%]") then
|
||||
return nil
|
||||
end
|
||||
|
||||
return buftype:gsub("%[%]", "")
|
||||
end
|
||||
|
||||
local function configurable(def, idx)
|
||||
local cfgitem = def[idx]
|
||||
|
||||
if not cfgitem then
|
||||
return nil
|
||||
end
|
||||
|
||||
if type(cfgitem) == "function" then
|
||||
return cfgitem()
|
||||
end
|
||||
|
||||
return cfgitem
|
||||
end
|
||||
|
||||
local function generate_stackframe(buftype, bufsize, disposition, heap, def)
|
||||
local function len_offset(inverted, disposition)
|
||||
-- Tests that don't use __len in their arguments may use an
|
||||
-- inverted sense because we can't just specify a length that
|
||||
-- would induce an access just after the end. Instead, we have
|
||||
-- to manipulate the buffer size to be too short so that the
|
||||
-- function under test would write one too many.
|
||||
if disposition < 0 then
|
||||
return ((inverted and " + ") or " - ") .. "1"
|
||||
elseif disposition == 0 then
|
||||
return ""
|
||||
else
|
||||
return ((inverted and " - ") or " + ") .. "1"
|
||||
end
|
||||
end
|
||||
|
||||
local function test_uses_len(def)
|
||||
if def.uses_len then
|
||||
return true
|
||||
end
|
||||
|
||||
for _, arg in ipairs(def.arguments) do
|
||||
if arg:match("__len") or arg:match("__idx") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- This is perhaps a little convoluted, but we toss the buffer into a
|
||||
-- struct on the stack to guarantee that we have at least one valid
|
||||
-- byte on either side of the buffer -- a measure to make sure that
|
||||
-- we're tripping _FORTIFY_SOURCE specifically in the buffer + 1 case,
|
||||
-- rather than some other stack or memory protection.
|
||||
local vars = "\tstruct {\n"
|
||||
vars = vars .. "\t\tuint8_t padding_l;\n"
|
||||
|
||||
local uses_len = test_uses_len(def)
|
||||
local bufsize_offset = len_offset(not uses_len, disposition)
|
||||
local buftype_elem = array_type(buftype)
|
||||
local size_expr = bufsize
|
||||
|
||||
if not uses_len then
|
||||
-- If the length isn't in use, we have to vary the buffer size
|
||||
-- since the fortified function likely has some internal size
|
||||
-- constraint that it's supposed to be checking.
|
||||
size_expr = size_expr .. bufsize_offset
|
||||
end
|
||||
|
||||
if not heap and buftype_elem then
|
||||
-- Array type: size goes after identifier
|
||||
vars = vars .. "\t\t" .. buftype_elem ..
|
||||
" __buf[" .. size_expr .. "];\n"
|
||||
else
|
||||
local basic_type = buftype_elem or buftype
|
||||
|
||||
-- Heap tests obviously just put a pointer on the stack that
|
||||
-- points to our new allocation, but we leave it in the padded
|
||||
-- struct just to simplify our generator.
|
||||
if heap then
|
||||
basic_type = basic_type .. " *"
|
||||
end
|
||||
vars = vars .. "\t\t" .. basic_type .. " __buf;\n"
|
||||
end
|
||||
|
||||
-- padding_r is our just-past-the-end padding that we use to make sure
|
||||
-- that there's a valid portion after the buffer that isn't being
|
||||
-- included in our function calls. If we didn't have it, then we'd have
|
||||
-- a hard time feeling confident that an abort on the just-after tests
|
||||
-- isn't maybe from some other memory or stack protection.
|
||||
vars = vars .. "\t\tuint8_t padding_r;\n"
|
||||
vars = vars .. "\t} __stack;\n"
|
||||
|
||||
-- Not all tests will use __bufsz, but some do for, e.g., clearing
|
||||
-- memory..
|
||||
vars = vars .. "\tconst size_t __bufsz __unused = "
|
||||
if heap then
|
||||
local scalar = 1
|
||||
if buftype_elem then
|
||||
scalar = size_expr
|
||||
end
|
||||
|
||||
vars = vars .. "sizeof(*__stack.__buf) * (" .. scalar .. ");\n"
|
||||
else
|
||||
vars = vars .. "sizeof(__stack.__buf);\n"
|
||||
end
|
||||
|
||||
vars = vars .. "\tconst size_t __len = " .. bufsize ..
|
||||
bufsize_offset .. ";\n"
|
||||
vars = vars .. "\tconst size_t __idx __unused = __len - 1;\n"
|
||||
|
||||
-- For overflow testing, we need to fork() because we're expecting the
|
||||
-- test to ultimately abort()/_exit(). Then we can collect the exit
|
||||
-- status and report appropriately.
|
||||
if disposition > 0 then
|
||||
vars = vars .. "\tpid_t __child;\n"
|
||||
vars = vars .. "\tint __status;\n"
|
||||
end
|
||||
|
||||
-- Any other stackvars defined by the test get placed after everything
|
||||
-- else.
|
||||
vars = vars .. (configurable(def, "stackvars") or "")
|
||||
|
||||
return vars
|
||||
end
|
||||
|
||||
local function write_test(fh, func, disposition, heap, def)
|
||||
local testname = generate_test_name(func, def.variant, disposition, heap)
|
||||
local buftype = def.buftype or "unsigned char[]"
|
||||
local bufsize = def.bufsize or 42
|
||||
local body = ""
|
||||
|
||||
if def.exclude and def.exclude(disposition, heap) then
|
||||
return
|
||||
end
|
||||
|
||||
local function need_addr(buftype)
|
||||
return not (buftype:match("%[%]") or buftype:match("%*"))
|
||||
end
|
||||
|
||||
if heap then
|
||||
body = body .. "#define BUF __stack.__buf\n"
|
||||
else
|
||||
body = body .. "#define BUF &__stack.__buf\n"
|
||||
end
|
||||
|
||||
-- Setup the buffer
|
||||
body = body .. generate_stackframe(buftype, bufsize, disposition, heap, def) ..
|
||||
"\n"
|
||||
|
||||
-- Any early initialization goes before we would fork for the just-after
|
||||
-- tests, because they may want to skip the test based on some criteria
|
||||
-- and we can't propagate that up very easily once we're forked.
|
||||
local early_init = configurable(def, "early_init")
|
||||
body = body .. (early_init or "")
|
||||
if early_init then
|
||||
body = body .. "\n"
|
||||
end
|
||||
|
||||
-- Fork off, iff we're testing some access past the end of the buffer.
|
||||
if disposition > 0 then
|
||||
body = body .. [[
|
||||
__child = fork();
|
||||
ATF_REQUIRE(__child >= 0);
|
||||
if (__child > 0)
|
||||
goto monitor;
|
||||
|
||||
/* Child */
|
||||
disable_coredumps();
|
||||
]]
|
||||
end
|
||||
|
||||
local bufvar = "__stack.__buf"
|
||||
if heap then
|
||||
-- Buffer needs to be initialized because it's a heap allocation.
|
||||
body = body .. "\t" .. bufvar .. " = malloc(__bufsz);\n"
|
||||
end
|
||||
|
||||
-- Non-early init happens just after the fork in the child, not the
|
||||
-- monitor. This is used to setup any other buffers we may need, for
|
||||
-- instance.
|
||||
local extra_init = configurable(def, "init")
|
||||
body = body .. (extra_init or "")
|
||||
|
||||
if heap or extra_init then
|
||||
body = body .. "\n"
|
||||
end
|
||||
|
||||
-- Setup the function call with arguments as described in the test
|
||||
-- definition.
|
||||
body = body .. "\t" .. func .. "("
|
||||
|
||||
for idx, arg in ipairs(def.arguments) do
|
||||
if idx > 1 then
|
||||
body = body .. ", "
|
||||
end
|
||||
|
||||
if arg == "__buf" then
|
||||
if not heap and need_addr(buftype) then
|
||||
body = body .. "&"
|
||||
end
|
||||
|
||||
body = body .. bufvar
|
||||
else
|
||||
local argname = arg
|
||||
|
||||
if def.value_of then
|
||||
argname = argname or def.value_of(arg)
|
||||
end
|
||||
|
||||
body = body .. argname
|
||||
end
|
||||
end
|
||||
|
||||
body = body .. ");\n"
|
||||
|
||||
-- Monitor stuff follows, for OOB access.
|
||||
if disposition <= 0 then
|
||||
goto skip
|
||||
end
|
||||
|
||||
body = body .. [[
|
||||
_exit(EX_SOFTWARE); /* Should have aborted. */
|
||||
|
||||
monitor:
|
||||
while (waitpid(__child, &__status, 0) != __child) {
|
||||
ATF_REQUIRE_EQ(EINTR, errno);
|
||||
}
|
||||
|
||||
if (!WIFSIGNALED(__status)) {
|
||||
switch (WEXITSTATUS(__status)) {
|
||||
case EX_SOFTWARE:
|
||||
atf_tc_fail("FORTIFY_SOURCE failed to abort");
|
||||
break;
|
||||
case EX_OSERR:
|
||||
atf_tc_fail("setrlimit(2) failed");
|
||||
break;
|
||||
default:
|
||||
atf_tc_fail("child exited with status %d",
|
||||
WEXITSTATUS(__status));
|
||||
}
|
||||
} else {
|
||||
ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
|
||||
}
|
||||
]]
|
||||
|
||||
::skip::
|
||||
body = body .. "#undef BUF\n"
|
||||
return write_test_boilerplate(fh, testname, body)
|
||||
end
|
||||
|
||||
-- main()
|
||||
local tests
|
||||
local tcat = assert(arg[1], "usage: generate-fortify-tests.lua <category>")
|
||||
for k, defs in pairs(all_tests) do
|
||||
if k == tcat then
|
||||
tests = defs
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert(tests, "category " .. tcat .. " not found")
|
||||
|
||||
local fh = io.stdout
|
||||
fh:write("/* @" .. "generated" .. " by `generate-fortify-tests.lua \"" ..
|
||||
tcat .. "\"` */\n\n")
|
||||
fh:write("#define _FORTIFY_SOURCE 2\n")
|
||||
fh:write("#define TMPFILE_SIZE (1024 * 32)\n")
|
||||
|
||||
fh:write("\n")
|
||||
for _, inc in ipairs(includes) do
|
||||
fh:write("#include <" .. inc .. ">\n")
|
||||
end
|
||||
|
||||
fh:write([[
|
||||
|
||||
/*
|
||||
* Create a new symlink to use for readlink(2) style tests, we'll just use a
|
||||
* random target name to have something interesting to look at.
|
||||
*/
|
||||
static const char * __unused
|
||||
new_symlink(size_t __len)
|
||||
{
|
||||
static const char linkname[] = "link";
|
||||
char target[MAXNAMLEN];
|
||||
int error;
|
||||
|
||||
ATF_REQUIRE(__len <= sizeof(target));
|
||||
|
||||
arc4random_buf(target, sizeof(target));
|
||||
|
||||
error = unlink(linkname);
|
||||
ATF_REQUIRE(error == 0 || errno == ENOENT);
|
||||
|
||||
error = symlink(target, linkname);
|
||||
ATF_REQUIRE(error == 0);
|
||||
|
||||
return (linkname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a tmpfile that we can use for testing read(2) and friends.
|
||||
*/
|
||||
static int __unused
|
||||
new_tmpfile(void)
|
||||
{
|
||||
char buf[1024];
|
||||
ssize_t rv;
|
||||
size_t written;
|
||||
int fd;
|
||||
|
||||
fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||||
ATF_REQUIRE(fd >= 0);
|
||||
|
||||
written = 0;
|
||||
while (written < TMPFILE_SIZE) {
|
||||
rv = write(fd, buf, sizeof(buf));
|
||||
ATF_REQUIRE(rv > 0);
|
||||
|
||||
written += rv;
|
||||
}
|
||||
|
||||
ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET));
|
||||
return (fd);
|
||||
}
|
||||
|
||||
static void
|
||||
disable_coredumps(void)
|
||||
{
|
||||
struct rlimit rl = { 0 };
|
||||
|
||||
if (setrlimit(RLIMIT_CORE, &rl) == -1)
|
||||
_exit(EX_OSERR);
|
||||
}
|
||||
|
||||
]])
|
||||
|
||||
for _, def in pairs(tests) do
|
||||
local func = def.func
|
||||
local function write_tests(heap)
|
||||
-- Dispositions here are relative to the buffer size prescribed
|
||||
-- by the test definition.
|
||||
local dispositions = def.dispositions or { -1, 0, 1 }
|
||||
|
||||
for _, disposition in ipairs(dispositions) do
|
||||
tests_added[#tests_added + 1] = write_test(fh, func, disposition, heap, def)
|
||||
end
|
||||
end
|
||||
|
||||
write_tests(false)
|
||||
write_tests(true)
|
||||
end
|
||||
|
||||
fh:write("ATF_TP_ADD_TCS(tp)\n")
|
||||
fh:write("{\n")
|
||||
for idx = 1, #tests_added do
|
||||
fh:write("\tATF_TP_ADD_TC(tp, " .. tests_added[idx] .. ");\n")
|
||||
end
|
||||
fh:write("\treturn (atf_no_error());\n")
|
||||
fh:write("}\n")
|
Loading…
Reference in New Issue
Block a user