git: 2aba0eea3fff - main - include: ssp: fortify <sys/select.h>

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Sat, 13 Jul 2024 05:23:07 UTC
The branch main has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=2aba0eea3ffffce74f9d8df20e0aaf49ea6d76c3

commit 2aba0eea3ffffce74f9d8df20e0aaf49ea6d76c3
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2024-07-13 05:16:12 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2024-07-13 05:16:25 +0000

    include: ssp: fortify <sys/select.h>
    
    Notably sanity check indices passed to the FD_*() macros against the
    size of the fd_set itself.
    
    Reviewed by:    markj
    Sponsored by:   Klara, Inc.
    Sponsored by:   Stormshield
    Differential Revision:  https://reviews.freebsd.org/D45685
---
 lib/libc/tests/secure/Makefile                   |   1 +
 lib/libc/tests/secure/fortify_poll_test.c        |   1 +
 lib/libc/tests/secure/fortify_random_test.c      |   1 +
 lib/libc/tests/secure/fortify_select_test.c      | 669 +++++++++++++++++++++++
 lib/libc/tests/secure/fortify_stdio_test.c       |   1 +
 lib/libc/tests/secure/fortify_stdlib_test.c      |   1 +
 lib/libc/tests/secure/fortify_string_test.c      |   1 +
 lib/libc/tests/secure/fortify_strings_test.c     |   1 +
 lib/libc/tests/secure/fortify_uio_test.c         |   1 +
 lib/libc/tests/secure/fortify_unistd_test.c      |   1 +
 lib/libc/tests/secure/fortify_wchar_test.c       |   1 +
 lib/libc/tests/secure/generate-fortify-tests.lua |  31 ++
 sys/sys/select.h                                 |  32 +-
 13 files changed, 739 insertions(+), 3 deletions(-)

diff --git a/lib/libc/tests/secure/Makefile b/lib/libc/tests/secure/Makefile
index 7aa9212b97a8..996536beac91 100644
--- a/lib/libc/tests/secure/Makefile
+++ b/lib/libc/tests/secure/Makefile
@@ -4,6 +4,7 @@ TESTSDIR:=	${TESTSBASE}/${RELDIR:C/libc\/tests/libc/}
 
 # sys/ headers
 FORTIFY_TCATS+=	random
+FORTIFY_TCATS+=	select
 FORTIFY_TCATS+=	uio
 
 # non-sys/ headers
diff --git a/lib/libc/tests/secure/fortify_poll_test.c b/lib/libc/tests/secure/fortify_poll_test.c
index 83c0f68b0daa..47648fe54b47 100644
--- a/lib/libc/tests/secure/fortify_poll_test.c
+++ b/lib/libc/tests/secure/fortify_poll_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_random_test.c b/lib/libc/tests/secure/fortify_random_test.c
index 1eb18cfcaaf4..64c32c6c6a8f 100644
--- a/lib/libc/tests/secure/fortify_random_test.c
+++ b/lib/libc/tests/secure/fortify_random_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_select_test.c b/lib/libc/tests/secure/fortify_select_test.c
new file mode 100644
index 000000000000..ccd3f97004fc
--- /dev/null
+++ b/lib/libc/tests/secure/fortify_select_test.c
@@ -0,0 +1,669 @@
+/* @generated by `generate-fortify-tests.lua "select"` */
+
+#define	_FORTIFY_SOURCE	2
+#define	TMPFILE_SIZE	(1024 * 32)
+
+#include <sys/param.h>
+#include <sys/random.h>
+#include <sys/resource.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <atf-c.h>
+
+static FILE * __unused
+new_fp(size_t __len)
+{
+	static char fpbuf[LINE_MAX];
+	FILE *fp;
+
+	ATF_REQUIRE(__len <= sizeof(fpbuf));
+
+	memset(fpbuf, 'A', sizeof(fpbuf) - 1);
+	fpbuf[sizeof(fpbuf) - 1] = '\0';
+
+	fp = fmemopen(fpbuf, sizeof(fpbuf), "rb");
+	ATF_REQUIRE(fp != NULL);
+
+	return (fp);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Replaces stdin with a file that we can actually read from, for tests where
+ * we want a FILE * or fd that we can get data from.
+ */
+static void __unused
+replace_stdin(void)
+{
+	int fd;
+
+	fd = new_tmpfile();
+
+	(void)dup2(fd, STDIN_FILENO);
+	if (fd != STDIN_FILENO)
+		close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(FD_SET_before_end);
+ATF_TC_BODY(FD_SET_before_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE - 1;
+	const size_t __idx __unused = __len - 1;
+
+	FD_SET(__idx, &__stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_SET_end);
+ATF_TC_BODY(FD_SET_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE;
+	const size_t __idx __unused = __len - 1;
+
+	FD_SET(__idx, &__stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_SET_after_end);
+ATF_TC_BODY(FD_SET_after_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE + 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();
+	FD_SET(__idx, &__stack.__buf);
+	_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(FD_SET_heap_before_end);
+ATF_TC_BODY(FD_SET_heap_before_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE - 1;
+	const size_t __idx __unused = __len - 1;
+
+	__stack.__buf = malloc(__bufsz);
+
+	FD_SET(__idx, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_SET_heap_end);
+ATF_TC_BODY(FD_SET_heap_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE;
+	const size_t __idx __unused = __len - 1;
+
+	__stack.__buf = malloc(__bufsz);
+
+	FD_SET(__idx, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_SET_heap_after_end);
+ATF_TC_BODY(FD_SET_heap_after_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE + 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);
+
+	FD_SET(__idx, __stack.__buf);
+	_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(FD_CLR_before_end);
+ATF_TC_BODY(FD_CLR_before_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE - 1;
+	const size_t __idx __unused = __len - 1;
+
+	FD_CLR(__idx, &__stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_CLR_end);
+ATF_TC_BODY(FD_CLR_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE;
+	const size_t __idx __unused = __len - 1;
+
+	FD_CLR(__idx, &__stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_CLR_after_end);
+ATF_TC_BODY(FD_CLR_after_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE + 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();
+	FD_CLR(__idx, &__stack.__buf);
+	_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(FD_CLR_heap_before_end);
+ATF_TC_BODY(FD_CLR_heap_before_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE - 1;
+	const size_t __idx __unused = __len - 1;
+
+	__stack.__buf = malloc(__bufsz);
+
+	FD_CLR(__idx, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_CLR_heap_end);
+ATF_TC_BODY(FD_CLR_heap_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE;
+	const size_t __idx __unused = __len - 1;
+
+	__stack.__buf = malloc(__bufsz);
+
+	FD_CLR(__idx, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_CLR_heap_after_end);
+ATF_TC_BODY(FD_CLR_heap_after_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE + 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);
+
+	FD_CLR(__idx, __stack.__buf);
+	_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(FD_ISSET_before_end);
+ATF_TC_BODY(FD_ISSET_before_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE - 1;
+	const size_t __idx __unused = __len - 1;
+
+	FD_ISSET(__idx, &__stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_ISSET_end);
+ATF_TC_BODY(FD_ISSET_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE;
+	const size_t __idx __unused = __len - 1;
+
+	FD_ISSET(__idx, &__stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_ISSET_after_end);
+ATF_TC_BODY(FD_ISSET_after_end, tc)
+{
+#define BUF &__stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(__stack.__buf);
+	const size_t __len = FD_SETSIZE + 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();
+	FD_ISSET(__idx, &__stack.__buf);
+	_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(FD_ISSET_heap_before_end);
+ATF_TC_BODY(FD_ISSET_heap_before_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE - 1;
+	const size_t __idx __unused = __len - 1;
+
+	__stack.__buf = malloc(__bufsz);
+
+	FD_ISSET(__idx, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_ISSET_heap_end);
+ATF_TC_BODY(FD_ISSET_heap_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE;
+	const size_t __idx __unused = __len - 1;
+
+	__stack.__buf = malloc(__bufsz);
+
+	FD_ISSET(__idx, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC_WITHOUT_HEAD(FD_ISSET_heap_after_end);
+ATF_TC_BODY(FD_ISSET_heap_after_end, tc)
+{
+#define BUF __stack.__buf
+	struct {
+		uint8_t padding_l;
+		fd_set * __buf;
+		uint8_t padding_r;
+	} __stack;
+	const size_t __bufsz __unused = sizeof(*__stack.__buf) * (1);
+	const size_t __len = FD_SETSIZE + 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);
+
+	FD_ISSET(__idx, __stack.__buf);
+	_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, FD_SET_before_end);
+	ATF_TP_ADD_TC(tp, FD_SET_end);
+	ATF_TP_ADD_TC(tp, FD_SET_after_end);
+	ATF_TP_ADD_TC(tp, FD_SET_heap_before_end);
+	ATF_TP_ADD_TC(tp, FD_SET_heap_end);
+	ATF_TP_ADD_TC(tp, FD_SET_heap_after_end);
+	ATF_TP_ADD_TC(tp, FD_CLR_before_end);
+	ATF_TP_ADD_TC(tp, FD_CLR_end);
+	ATF_TP_ADD_TC(tp, FD_CLR_after_end);
+	ATF_TP_ADD_TC(tp, FD_CLR_heap_before_end);
+	ATF_TP_ADD_TC(tp, FD_CLR_heap_end);
+	ATF_TP_ADD_TC(tp, FD_CLR_heap_after_end);
+	ATF_TP_ADD_TC(tp, FD_ISSET_before_end);
+	ATF_TP_ADD_TC(tp, FD_ISSET_end);
+	ATF_TP_ADD_TC(tp, FD_ISSET_after_end);
+	ATF_TP_ADD_TC(tp, FD_ISSET_heap_before_end);
+	ATF_TP_ADD_TC(tp, FD_ISSET_heap_end);
+	ATF_TP_ADD_TC(tp, FD_ISSET_heap_after_end);
+	return (atf_no_error());
+}
diff --git a/lib/libc/tests/secure/fortify_stdio_test.c b/lib/libc/tests/secure/fortify_stdio_test.c
index 75f81c0a0750..61ccc8fc5592 100644
--- a/lib/libc/tests/secure/fortify_stdio_test.c
+++ b/lib/libc/tests/secure/fortify_stdio_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_stdlib_test.c b/lib/libc/tests/secure/fortify_stdlib_test.c
index 8556e1110156..5383b73d4058 100644
--- a/lib/libc/tests/secure/fortify_stdlib_test.c
+++ b/lib/libc/tests/secure/fortify_stdlib_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_string_test.c b/lib/libc/tests/secure/fortify_string_test.c
index 70f247b09e39..918445ca68a1 100644
--- a/lib/libc/tests/secure/fortify_string_test.c
+++ b/lib/libc/tests/secure/fortify_string_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_strings_test.c b/lib/libc/tests/secure/fortify_strings_test.c
index e6a8ea0291de..1cecd7033ae3 100644
--- a/lib/libc/tests/secure/fortify_strings_test.c
+++ b/lib/libc/tests/secure/fortify_strings_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_uio_test.c b/lib/libc/tests/secure/fortify_uio_test.c
index 56d7cf5d9a84..0c709ac8b945 100644
--- a/lib/libc/tests/secure/fortify_uio_test.c
+++ b/lib/libc/tests/secure/fortify_uio_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_unistd_test.c b/lib/libc/tests/secure/fortify_unistd_test.c
index 7a91d3b06c75..e2127450f565 100644
--- a/lib/libc/tests/secure/fortify_unistd_test.c
+++ b/lib/libc/tests/secure/fortify_unistd_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/fortify_wchar_test.c b/lib/libc/tests/secure/fortify_wchar_test.c
index 8c5e0782c65a..31ae02c412a7 100644
--- a/lib/libc/tests/secure/fortify_wchar_test.c
+++ b/lib/libc/tests/secure/fortify_wchar_test.c
@@ -6,6 +6,7 @@
 #include <sys/param.h>
 #include <sys/random.h>
 #include <sys/resource.h>
+#include <sys/select.h>
 #include <sys/time.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
diff --git a/lib/libc/tests/secure/generate-fortify-tests.lua b/lib/libc/tests/secure/generate-fortify-tests.lua
index 23b33acb757b..fdbc6c550551 100755
--- a/lib/libc/tests/secure/generate-fortify-tests.lua
+++ b/lib/libc/tests/secure/generate-fortify-tests.lua
@@ -64,6 +64,7 @@ local includes = {
 	"sys/param.h",
 	"sys/random.h",
 	"sys/resource.h",
+	"sys/select.h",
 	"sys/time.h",
 	"sys/uio.h",
 	"sys/wait.h",
@@ -169,6 +170,36 @@ local all_tests = {
 			exclude = excludes_stack_overflow,
 		},
 	},
+	select = {
+		-- <sys/select.h>
+		{
+			func = "FD_SET",
+			bufsize = "FD_SETSIZE",
+			buftype = "fd_set",
+			arguments = {
+				"__idx",
+				"__buf",
+			},
+		},
+		{
+			func = "FD_CLR",
+			bufsize = "FD_SETSIZE",
+			buftype = "fd_set",
+			arguments = {
+				"__idx",
+				"__buf",
+			},
+		},
+		{
+			func = "FD_ISSET",
+			bufsize = "FD_SETSIZE",
+			buftype = "fd_set",
+			arguments = {
+				"__idx",
+				"__buf",
+			},
+		},
+	},
 	uio = {
 		-- <sys/uio.h>
 		{
diff --git a/sys/sys/select.h b/sys/sys/select.h
index 1ded44197de9..9b734754b944 100644
--- a/sys/sys/select.h
+++ b/sys/sys/select.h
@@ -49,6 +49,12 @@ typedef	__fd_mask	fd_mask;
 typedef	__sigset_t	sigset_t;
 #endif
 
+#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 0
+#include <ssp/ssp.h>
+#else
+#define	__SSP_FORTIFY_LEVEL	0
+#endif
+
 /*
  * Select uses bit masks of file descriptors in longs.  These macros
  * manipulate such bit fields (the filesystem macros use chars).
@@ -75,13 +81,33 @@ typedef	struct fd_set {
 #define	fds_bits	__fds_bits
 #endif
 
+#define	__fdset_idx_(p, n)	((n) / _NFDBITS)
+#if __SSP_FORTIFY_LEVEL == 0
+#define	__fdset_idx(p, n)	__fdset_idx_(p, n)
+#else
+__ssp_inline unsigned long
+__fdset_idx(fd_set *p, unsigned long idx)
+{
+	__size_t psz = __ssp_bos0(p);
+	unsigned long sidx = __fdset_idx_(p, idx);
+
+	if (idx >= FD_SETSIZE)
+		__chk_fail();
+	if (psz / sizeof(__fd_mask) < (sidx + 1))
+		__chk_fail();
+
+	return (sidx);
+}
+#endif
+
 #define	__fdset_mask(n)	((__fd_mask)1 << ((n) % _NFDBITS))
-#define	FD_CLR(n, p)	((p)->__fds_bits[(n)/_NFDBITS] &= ~__fdset_mask(n))
+#define	FD_CLR(n, p)	((p)->__fds_bits[__fdset_idx(p, n)] &= ~__fdset_mask(n))
 #if __BSD_VISIBLE
 #define	FD_COPY(f, t)	(void)(*(t) = *(f))
 #endif
-#define	FD_ISSET(n, p)	(((p)->__fds_bits[(n)/_NFDBITS] & __fdset_mask(n)) != 0)
-#define	FD_SET(n, p)	((p)->__fds_bits[(n)/_NFDBITS] |= __fdset_mask(n))
+#define	FD_ISSET(n, p)	\
+    (((p)->__fds_bits[__fdset_idx(p, n)] & __fdset_mask(n)) != 0)
+#define	FD_SET(n, p)	((p)->__fds_bits[__fdset_idx(p, n)] |= __fdset_mask(n))
 #define	FD_ZERO(p) do {					\
 	fd_set *_p;					\
 	__size_t _n;					\