aboutsummaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-19 17:11:41 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-19 17:11:41 -1000
commit4effd80841b3c26ab33e30409e810b35a0dc0385 (patch)
tree9d6b0339b09fe4b578271424eb35aa5522faac5b /tools/testing
parent550a7e28d801d35dd23aa2cdd30b7a59e193e080 (diff)
parentc73c0e243836d5dfd1c617f73b6003b1ad40b2d1 (diff)
Merge tag 'vfs-7.4-rc1.close_range' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull close_range updates from Christian Brauner: "This adds two new close_range() flags. CLOSE_RANGE_EXCEPT makes close_range() operate on all file descriptors outside of [fd, max_fd] instead of the ones inside. Alone it closes everything but the range. With CLOSE_RANGE_CLOEXEC it marks everything but the range close-on-exec. With CLOSE_RANGE_UNSHARE dup_fd() never takes a reference on what is dropped. So a task between clone(CLONE_FILES | CLONE_VM | CLONE_VFORK) and execve() that has the descriptors for the child in one window can shed the rest of the shared table in one call and nothing outside of the window is referenced by the child at any point. CLOSE_RANGE_CLOEXEC_ONLY closes the close-on-exec descriptors in the range and leaves everything else alone. Whatever the caller deliberately passes down, stdio, LISTEN_FDS, an inherited pipe, stays where it is. Combined with CLOSE_RANGE_EXCEPT it names the handful of close-on-exec descriptors that have to survive, the executable or an error pipe, and closes every other close-on-exec descriptor. Along the way dup_fd() stops copying a range in the middle of the table only to close it again. close_range(CLOSE_RANGE_UNSHARE) only avoided the copy when the range sat at the end of the table. Descriptors in a skipped range are never copied and no reference is taken on them, so ->flush() isn't called anymore for files from a table they were never part of. The flags are turned into an enum and selftests cover the window positions, the shared and the private table and the unshare form" * tag 'vfs-7.4-rc1.close_range' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: selftests/core: test CLOSE_RANGE_CLOEXEC_ONLY file: add CLOSE_RANGE_CLOEXEC_ONLY file: let dup_fd() drop only close-on-exec descriptors selftests/core: test CLOSE_RANGE_EXCEPT file: add CLOSE_RANGE_EXCEPT close_range: turn the flags into an enum file: let dup_fd() drop everything outside of the range file: rename dup_fd()'s punch_hole to range selftests/core: test the hole CLOSE_RANGE_UNSHARE leaves behind file: let dup_fd() leave the punched hole behind
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/core/close_range_test.c951
1 files changed, 951 insertions, 0 deletions
diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c
index f14eca63f20c..20ecb65e529b 100644
--- a/tools/testing/selftests/core/close_range_test.c
+++ b/tools/testing/selftests/core/close_range_test.c
@@ -36,6 +36,14 @@ static inline int sys_close_range(unsigned int fd, unsigned int max_fd,
return syscall(__NR_close_range, fd, max_fd, flags);
}
+static void clear_cloexec(const int *fds, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ fcntl(fds[i], F_SETFD, 0);
+}
+
TEST(core_close_range)
{
int i, ret;
@@ -236,6 +244,50 @@ TEST(close_range_unshare_capped)
EXPECT_EQ(0, WEXITSTATUS(status));
}
+TEST(close_range_unshare_hole)
+{
+ int i, status;
+ pid_t pid;
+ struct __clone_args args = {
+ .flags = CLONE_FILES,
+ .exit_signal = SIGCHLD,
+ };
+
+ /* Fill the first two words of the table. */
+ for (i = 3; i < 128; i++)
+ ASSERT_GE(dup2(0, i), 0);
+
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ /* Punch a hole into the second word, behind a full first one. */
+ if (sys_close_range(70, 80, CLOSE_RANGE_UNSHARE))
+ exit(EXIT_FAILURE);
+
+ for (i = 3; i < 128; i++) {
+ bool closed = i >= 70 && i <= 80;
+
+ if (closed == (fcntl(i, F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ /* A stale full bit on word 1 would hand out 128, not 70. */
+ if (dup(0) != 70)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* The shared table the child unshared from is untouched. */
+ for (i = 3; i < 128; i++)
+ EXPECT_NE(-1, fcntl(i, F_GETFD));
+}
+
TEST(close_range_cloexec)
{
int i, ret;
@@ -593,6 +645,905 @@ TEST(close_range_cloexec_unshare_syzbot)
EXPECT_EQ(close(fd3), 0);
}
+TEST(close_range_except)
+{
+ int i, ret, status;
+ pid_t pid;
+ int open_fds[101];
+ struct __clone_args args = {
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY);
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything keeps everything. */
+ ret = sys_close_range(0, UINT_MAX, CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+
+ /* The bounds are checked before the range is turned around. */
+ EXPECT_EQ(-1, sys_close_range(open_fds[20], open_fds[10],
+ CLOSE_RANGE_EXCEPT));
+ EXPECT_EQ(EINVAL, errno);
+
+ /* Everything above open_fds[50] goes. */
+ ASSERT_EQ(0, sys_close_range(0, open_fds[50], CLOSE_RANGE_EXCEPT));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(i <= 50, fcntl(open_fds[i], F_GETFD) != -1);
+
+ /* A window in the middle takes stdio with it, so do that in a fork. */
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i <= 50; i++) {
+ bool kept = i >= 10 && i <= 20;
+
+ if (kept != (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ if (fcntl(STDERR_FILENO, F_GETFD) != -1)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* The fork had a table of its own. */
+ for (i = 0; i <= 50; i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+}
+
+TEST(close_range_except_cloexec)
+{
+ int i, ret;
+ int open_fds[101];
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY);
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool inside = i >= 10 && i <= 20;
+ int flags = fcntl(open_fds[i], F_GETFD);
+
+ EXPECT_NE(-1, flags);
+ EXPECT_EQ(inside ? 0 : FD_CLOEXEC, flags & FD_CLOEXEC);
+ }
+
+ /* stdio sits outside of the window too. */
+ EXPECT_EQ(FD_CLOEXEC, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC);
+
+ /* A window that starts at 0 marks only what lies above it. */
+ clear_cloexec(open_fds, ARRAY_SIZE(open_fds));
+ ASSERT_EQ(0, fcntl(STDERR_FILENO, F_SETFD, 0));
+ ASSERT_EQ(0, sys_close_range(0, open_fds[20],
+ CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(i <= 20 ? 0 : FD_CLOEXEC,
+ fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC);
+ EXPECT_EQ(0, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC);
+
+ /* One at the top marks only what lies below it, stdio included. */
+ clear_cloexec(open_fds, ARRAY_SIZE(open_fds));
+ ASSERT_EQ(0, sys_close_range(open_fds[80], UINT_MAX,
+ CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(i < 80 ? FD_CLOEXEC : 0,
+ fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC);
+
+ /* One that cannot hold a descriptor marks everything. */
+ clear_cloexec(open_fds, ARRAY_SIZE(open_fds));
+ ASSERT_EQ(0, fcntl(STDERR_FILENO, F_SETFD, 0));
+ ASSERT_EQ(0, sys_close_range(UINT_MAX, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(FD_CLOEXEC, fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC);
+}
+
+TEST(close_range_except_cloexec_unshare)
+{
+ int i, ret, status;
+ pid_t pid;
+ int open_fds[101];
+ struct __clone_args args = {
+ .flags = CLONE_FILES,
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY);
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything marks nothing. */
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(0, fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC);
+
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC |
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool inside = i >= 10 && i <= 20;
+ int flags = fcntl(open_fds[i], F_GETFD);
+
+ if (flags == -1)
+ exit(EXIT_FAILURE);
+ if ((flags & FD_CLOEXEC) != (inside ? 0 : FD_CLOEXEC))
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* The shared table the child unshared from is untouched. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(0, fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC);
+}
+
+TEST(close_range_except_bounds)
+{
+ int i, c, ret, status;
+ pid_t pid;
+ int open_fds[101];
+ struct __clone_args args = {
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY);
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything keeps everything. */
+ ret = sys_close_range(0, UINT_MAX, CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT");
+ }
+ ASSERT_EQ(0, ret);
+
+ struct {
+ unsigned int fd, max_fd, flags;
+ } cases[] = {
+ /* A window at the top drops everything below it. */
+ { open_fds[50], UINT_MAX, CLOSE_RANGE_EXCEPT },
+ /* One that cannot hold a descriptor keeps nothing. */
+ { UINT_MAX, UINT_MAX, CLOSE_RANGE_EXCEPT },
+ /* One of a single descriptor keeps just that. */
+ { open_fds[30], open_fds[30], CLOSE_RANGE_EXCEPT },
+ /* The unshare form on a table that is not shared acts in place. */
+ { open_fds[10], open_fds[20],
+ CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT },
+ };
+
+ /* Each of them takes stdio with it, so do that in a fork. */
+ for (c = 0; c < ARRAY_SIZE(cases); c++) {
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(cases[c].fd, cases[c].max_fd,
+ cases[c].flags);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ unsigned int fd = open_fds[i];
+ bool kept = fd >= cases[c].fd &&
+ fd <= cases[c].max_fd;
+
+ if (kept != (fcntl(fd, F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ if (fcntl(STDERR_FILENO, F_GETFD) != -1)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+ }
+
+ /* Each fork had a table of its own. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+}
+
+TEST(close_range_except_unshare)
+{
+ int i, ret, status;
+ pid_t pid;
+ int open_fds[200];
+ struct __clone_args args = {
+ .flags = CLONE_FILES,
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ /* Odd slots are close-on-exec, which makes no difference here. */
+ fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0));
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything keeps everything. */
+ ret = sys_close_range(0, UINT_MAX, CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT");
+ }
+ ASSERT_EQ(0, ret);
+
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ /* The window sits near the top, so the clone is sized off its end. */
+ ret = sys_close_range(open_fds[150], open_fds[160],
+ CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool kept = i >= 150 && i <= 160;
+
+ if (kept != (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ if (fcntl(STDERR_FILENO, F_GETFD) != -1)
+ exit(EXIT_FAILURE);
+
+ /* What was left behind is handed out again, from the bottom. */
+ if (dup(open_fds[150]) != 0)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* A window at the bottom keeps just that, stdio included. */
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(0, open_fds[10],
+ CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ if ((i <= 10) != (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ if (fcntl(STDERR_FILENO, F_GETFD) == -1)
+ exit(EXIT_FAILURE);
+
+ /* The first slot left behind is the next one handed out. */
+ if (dup(0) != open_fds[10] + 1)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* A window that cannot hold a descriptor keeps nothing. */
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(UINT_MAX, UINT_MAX,
+ CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ if (fcntl(open_fds[i], F_GETFD) != -1)
+ exit(EXIT_FAILURE);
+
+ if (fcntl(STDERR_FILENO, F_GETFD) != -1)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* The shared table the child unshared from is untouched. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+}
+
+TEST(close_range_cloexec_only)
+{
+ int i, ret;
+ int open_fds[101];
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ /* Odd slots are close-on-exec, even ones are not. */
+ fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0));
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_CLOEXEC_ONLY);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool closed = i % 2 && i >= 10 && i <= 20;
+
+ EXPECT_EQ(!closed, fcntl(open_fds[i], F_GETFD) != -1);
+ }
+
+ /* A range above the table closes nothing. */
+ ASSERT_EQ(0, sys_close_range(UINT_MAX, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool closed = i % 2 && i >= 10 && i <= 20;
+
+ EXPECT_EQ(!closed, fcntl(open_fds[i], F_GETFD) != -1);
+ }
+
+ /* Do what an exec would do to the rest. */
+ ASSERT_EQ(0, sys_close_range(0, UINT_MAX, CLOSE_RANGE_CLOEXEC_ONLY));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(!(i % 2), fcntl(open_fds[i], F_GETFD) != -1);
+}
+
+TEST(close_range_cloexec_only_except)
+{
+ int i, ret;
+ int open_fds[101];
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0));
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool kept = !(i % 2) || (i >= 10 && i <= 20);
+ int flags = i % 2 ? FD_CLOEXEC : 0;
+
+ /* The kept ones keep their flag, so exec still drops them. */
+ EXPECT_EQ(kept ? flags : -1, fcntl(open_fds[i], F_GETFD));
+ }
+
+ /* A range that cannot hold an open descriptor keeps nothing. */
+ ASSERT_EQ(0, sys_close_range(UINT_MAX, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT));
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_EQ(!(i % 2), fcntl(open_fds[i], F_GETFD) != -1);
+}
+
+TEST(close_range_cloexec_only_except_bounds)
+{
+ int i, c, ret, status;
+ pid_t pid;
+ int open_fds[101];
+ struct __clone_args args = {
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0));
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything keeps everything. */
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ struct {
+ unsigned int fd, max_fd;
+ } cases[] = {
+ /* A window at the top keeps the marked ones in it. */
+ { open_fds[80], UINT_MAX },
+ /* One at the bottom keeps the marked ones in it. */
+ { 0, open_fds[20] },
+ /* One that cannot hold a descriptor keeps none of them. */
+ { UINT_MAX, UINT_MAX },
+ };
+
+ for (c = 0; c < ARRAY_SIZE(cases); c++) {
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(cases[c].fd, cases[c].max_fd,
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ unsigned int fd = open_fds[i];
+ bool kept = !(i % 2) || (fd >= cases[c].fd &&
+ fd <= cases[c].max_fd);
+ int flags = i % 2 ? FD_CLOEXEC : 0;
+
+ if (fcntl(fd, F_GETFD) != (kept ? flags : -1))
+ exit(EXIT_FAILURE);
+ }
+
+ /* stdio is neither marked nor gone. */
+ if (fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+ }
+
+ /* Each fork had a table of its own. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+}
+
+TEST(close_range_cloexec_only_unshare)
+{
+ int i, ret, status;
+ pid_t pid;
+ int open_fds[101];
+ struct __clone_args args = {
+ .flags = CLONE_FILES,
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0));
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything keeps everything. */
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ ASSERT_NE(-1, fcntl(open_fds[i], F_GETFD));
+
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC_ONLY);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool closed = i % 2 && i >= 10 && i <= 20;
+
+ if (closed == (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* A range at the top keeps the descriptors without the flag in it. */
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(open_fds[50], UINT_MAX,
+ CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC_ONLY);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool closed = i % 2 && i >= 50;
+
+ if (closed == (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ /* The first slot left behind is the next one handed out. */
+ if (dup(0) != open_fds[51])
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* The shared table the child unshared from is untouched. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+}
+
+TEST(close_range_cloexec_only_except_unshare)
+{
+ int i, ret, status;
+ pid_t pid;
+ int open_fds[101];
+ struct __clone_args args = {
+ .flags = CLONE_FILES,
+ .exit_signal = SIGCHLD,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0));
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ /* A range covering everything keeps everything. */
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ ASSERT_NE(-1, fcntl(open_fds[i], F_GETFD));
+
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(open_fds[10], open_fds[20],
+ CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool kept = !(i % 2) || (i >= 10 && i <= 20);
+ int flags = i % 2 ? FD_CLOEXEC : 0;
+
+ if (fcntl(open_fds[i], F_GETFD) != (kept ? flags : -1))
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* A window that cannot hold a descriptor keeps none of the marked. */
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(UINT_MAX, UINT_MAX,
+ CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ if ((i % 2) == (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+
+ if (fcntl(STDERR_FILENO, F_GETFD) == -1)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* One that covers everything keeps everything, in a clone too. */
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ if (fcntl(open_fds[i], F_GETFD) != (i % 2 ? FD_CLOEXEC : 0))
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ /* The shared table the child unshared from is untouched. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++)
+ EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD));
+}
+
+TEST(close_range_cloexec_only_except_unshare_sizing)
+{
+ int i, ret, status;
+ pid_t pid;
+ int open_fds[200];
+ struct __clone_args args = {
+ .flags = CLONE_FILES,
+ .exit_signal = SIGCHLD,
+ };
+
+ /* All close-on-exec, so the kept range alone sizes the clone. */
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0) {
+ if (errno == ENOENT)
+ SKIP(return, "Skipping test since /dev/null does not exist");
+ }
+
+ open_fds[i] = fd;
+ }
+
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ pid = sys_clone3(&args, sizeof(args));
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = sys_close_range(open_fds[150], open_fds[160],
+ CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT);
+ if (ret)
+ exit(EXIT_FAILURE);
+
+ for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+ bool kept = i >= 150 && i <= 160;
+
+ if (kept != (fcntl(open_fds[i], F_GETFD) != -1))
+ exit(EXIT_FAILURE);
+ }
+
+ /* Nothing set close-on-exec on stdio. */
+ if (fcntl(STDERR_FILENO, F_GETFD) == -1)
+ exit(EXIT_FAILURE);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+TEST(close_range_cloexec_only_einval)
+{
+ int ret;
+
+ /* A range covering everything keeps everything, so this only probes. */
+ ret = sys_close_range(0, UINT_MAX,
+ CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT);
+ if (ret < 0) {
+ if (errno == ENOSYS)
+ SKIP(return, "close_range() syscall not supported");
+ if (errno == EINVAL)
+ SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY");
+ }
+ ASSERT_EQ(0, ret);
+
+ EXPECT_EQ(-1, sys_close_range(3, UINT_MAX, CLOSE_RANGE_CLOEXEC |
+ CLOSE_RANGE_CLOEXEC_ONLY));
+ EXPECT_EQ(EINVAL, errno);
+
+ /* The other flags do not make the pair acceptable. */
+ EXPECT_EQ(-1, sys_close_range(3, UINT_MAX, CLOSE_RANGE_UNSHARE |
+ CLOSE_RANGE_CLOEXEC |
+ CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT));
+ EXPECT_EQ(EINVAL, errno);
+
+ /* The bounds are checked with the new flag too. */
+ EXPECT_EQ(-1, sys_close_range(4, 3, CLOSE_RANGE_CLOEXEC_ONLY |
+ CLOSE_RANGE_EXCEPT));
+ EXPECT_EQ(EINVAL, errno);
+}
+
TEST(close_range_bitmap_corruption)
{
pid_t pid;