667 lines · c
1// SPDX-License-Identifier: GPL-2.02 3#define _GNU_SOURCE4#include <errno.h>5#include <fcntl.h>6#include <linux/kernel.h>7#include <limits.h>8#include <stdbool.h>9#include <stdio.h>10#include <stdlib.h>11#include <string.h>12#include <syscall.h>13#include <unistd.h>14#include <sys/resource.h>15#include <linux/close_range.h>16 17#include "../kselftest_harness.h"18#include "../clone3/clone3_selftests.h"19 20 21#ifndef F_LINUX_SPECIFIC_BASE22#define F_LINUX_SPECIFIC_BASE 102423#endif24 25#ifndef F_DUPFD_QUERY26#define F_DUPFD_QUERY (F_LINUX_SPECIFIC_BASE + 3)27#endif28 29#ifndef F_CREATED_QUERY30#define F_CREATED_QUERY (F_LINUX_SPECIFIC_BASE + 4)31#endif32 33static inline int sys_close_range(unsigned int fd, unsigned int max_fd,34 unsigned int flags)35{36 return syscall(__NR_close_range, fd, max_fd, flags);37}38 39TEST(core_close_range)40{41 int i, ret;42 int open_fds[101];43 44 for (i = 0; i < ARRAY_SIZE(open_fds); i++) {45 int fd;46 47 fd = open("/dev/null", O_RDONLY | O_CLOEXEC);48 ASSERT_GE(fd, 0) {49 if (errno == ENOENT)50 SKIP(return, "Skipping test since /dev/null does not exist");51 }52 53 open_fds[i] = fd;54 }55 56 EXPECT_EQ(-1, sys_close_range(open_fds[0], open_fds[100], -1)) {57 if (errno == ENOSYS)58 SKIP(return, "close_range() syscall not supported");59 }60 61 for (i = 0; i < 100; i++) {62 ret = fcntl(open_fds[i], F_DUPFD_QUERY, open_fds[i + 1]);63 if (ret < 0) {64 EXPECT_EQ(errno, EINVAL);65 } else {66 EXPECT_EQ(ret, 0);67 }68 }69 70 EXPECT_EQ(0, sys_close_range(open_fds[0], open_fds[50], 0));71 72 for (i = 0; i <= 50; i++)73 EXPECT_EQ(-1, fcntl(open_fds[i], F_GETFL));74 75 for (i = 51; i <= 100; i++)76 EXPECT_GT(fcntl(open_fds[i], F_GETFL), -1);77 78 /* create a couple of gaps */79 close(57);80 close(78);81 close(81);82 close(82);83 close(84);84 close(90);85 86 EXPECT_EQ(0, sys_close_range(open_fds[51], open_fds[92], 0));87 88 for (i = 51; i <= 92; i++)89 EXPECT_EQ(-1, fcntl(open_fds[i], F_GETFL));90 91 for (i = 93; i <= 100; i++)92 EXPECT_GT(fcntl(open_fds[i], F_GETFL), -1);93 94 /* test that the kernel caps and still closes all fds */95 EXPECT_EQ(0, sys_close_range(open_fds[93], open_fds[99], 0));96 97 for (i = 93; i <= 99; i++)98 EXPECT_EQ(-1, fcntl(open_fds[i], F_GETFL));99 100 EXPECT_GT(fcntl(open_fds[i], F_GETFL), -1);101 102 EXPECT_EQ(0, sys_close_range(open_fds[100], open_fds[100], 0));103 104 EXPECT_EQ(-1, fcntl(open_fds[100], F_GETFL));105}106 107TEST(close_range_unshare)108{109 int i, ret, status;110 pid_t pid;111 int open_fds[101];112 struct __clone_args args = {113 .flags = CLONE_FILES,114 .exit_signal = SIGCHLD,115 };116 117 for (i = 0; i < ARRAY_SIZE(open_fds); i++) {118 int fd;119 120 fd = open("/dev/null", O_RDONLY | O_CLOEXEC);121 ASSERT_GE(fd, 0) {122 if (errno == ENOENT)123 SKIP(return, "Skipping test since /dev/null does not exist");124 }125 126 open_fds[i] = fd;127 }128 129 pid = sys_clone3(&args, sizeof(args));130 ASSERT_GE(pid, 0);131 132 if (pid == 0) {133 ret = sys_close_range(open_fds[0], open_fds[50],134 CLOSE_RANGE_UNSHARE);135 if (ret)136 exit(EXIT_FAILURE);137 138 for (i = 0; i <= 50; i++)139 if (fcntl(open_fds[i], F_GETFL) != -1)140 exit(EXIT_FAILURE);141 142 for (i = 51; i <= 100; i++)143 if (fcntl(open_fds[i], F_GETFL) == -1)144 exit(EXIT_FAILURE);145 146 /* create a couple of gaps */147 close(57);148 close(78);149 close(81);150 close(82);151 close(84);152 close(90);153 154 ret = sys_close_range(open_fds[51], open_fds[92],155 CLOSE_RANGE_UNSHARE);156 if (ret)157 exit(EXIT_FAILURE);158 159 for (i = 51; i <= 92; i++)160 if (fcntl(open_fds[i], F_GETFL) != -1)161 exit(EXIT_FAILURE);162 163 for (i = 93; i <= 100; i++)164 if (fcntl(open_fds[i], F_GETFL) == -1)165 exit(EXIT_FAILURE);166 167 /* test that the kernel caps and still closes all fds */168 ret = sys_close_range(open_fds[93], open_fds[99],169 CLOSE_RANGE_UNSHARE);170 if (ret)171 exit(EXIT_FAILURE);172 173 for (i = 93; i <= 99; i++)174 if (fcntl(open_fds[i], F_GETFL) != -1)175 exit(EXIT_FAILURE);176 177 if (fcntl(open_fds[100], F_GETFL) == -1)178 exit(EXIT_FAILURE);179 180 ret = sys_close_range(open_fds[100], open_fds[100],181 CLOSE_RANGE_UNSHARE);182 if (ret)183 exit(EXIT_FAILURE);184 185 if (fcntl(open_fds[100], F_GETFL) != -1)186 exit(EXIT_FAILURE);187 188 exit(EXIT_SUCCESS);189 }190 191 EXPECT_EQ(waitpid(pid, &status, 0), pid);192 EXPECT_EQ(true, WIFEXITED(status));193 EXPECT_EQ(0, WEXITSTATUS(status));194}195 196TEST(close_range_unshare_capped)197{198 int i, ret, status;199 pid_t pid;200 int open_fds[101];201 struct __clone_args args = {202 .flags = CLONE_FILES,203 .exit_signal = SIGCHLD,204 };205 206 for (i = 0; i < ARRAY_SIZE(open_fds); i++) {207 int fd;208 209 fd = open("/dev/null", O_RDONLY | O_CLOEXEC);210 ASSERT_GE(fd, 0) {211 if (errno == ENOENT)212 SKIP(return, "Skipping test since /dev/null does not exist");213 }214 215 open_fds[i] = fd;216 }217 218 pid = sys_clone3(&args, sizeof(args));219 ASSERT_GE(pid, 0);220 221 if (pid == 0) {222 ret = sys_close_range(open_fds[0], UINT_MAX,223 CLOSE_RANGE_UNSHARE);224 if (ret)225 exit(EXIT_FAILURE);226 227 for (i = 0; i <= 100; i++)228 if (fcntl(open_fds[i], F_GETFL) != -1)229 exit(EXIT_FAILURE);230 231 exit(EXIT_SUCCESS);232 }233 234 EXPECT_EQ(waitpid(pid, &status, 0), pid);235 EXPECT_EQ(true, WIFEXITED(status));236 EXPECT_EQ(0, WEXITSTATUS(status));237}238 239TEST(close_range_cloexec)240{241 int i, ret;242 int open_fds[101];243 struct rlimit rlimit;244 245 for (i = 0; i < ARRAY_SIZE(open_fds); i++) {246 int fd;247 248 fd = open("/dev/null", O_RDONLY);249 ASSERT_GE(fd, 0) {250 if (errno == ENOENT)251 SKIP(return, "Skipping test since /dev/null does not exist");252 }253 254 open_fds[i] = fd;255 }256 257 ret = sys_close_range(1000, 1000, CLOSE_RANGE_CLOEXEC);258 if (ret < 0) {259 if (errno == ENOSYS)260 SKIP(return, "close_range() syscall not supported");261 if (errno == EINVAL)262 SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC");263 }264 265 /* Ensure the FD_CLOEXEC bit is set also with a resource limit in place. */266 ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlimit));267 rlimit.rlim_cur = 25;268 ASSERT_EQ(0, setrlimit(RLIMIT_NOFILE, &rlimit));269 270 /* Set close-on-exec for two ranges: [0-50] and [75-100]. */271 ret = sys_close_range(open_fds[0], open_fds[50], CLOSE_RANGE_CLOEXEC);272 ASSERT_EQ(0, ret);273 ret = sys_close_range(open_fds[75], open_fds[100], CLOSE_RANGE_CLOEXEC);274 ASSERT_EQ(0, ret);275 276 for (i = 0; i <= 50; i++) {277 int flags = fcntl(open_fds[i], F_GETFD);278 279 EXPECT_GT(flags, -1);280 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);281 }282 283 for (i = 51; i <= 74; i++) {284 int flags = fcntl(open_fds[i], F_GETFD);285 286 EXPECT_GT(flags, -1);287 EXPECT_EQ(flags & FD_CLOEXEC, 0);288 }289 290 for (i = 75; i <= 100; i++) {291 int flags = fcntl(open_fds[i], F_GETFD);292 293 EXPECT_GT(flags, -1);294 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);295 }296 297 /* Test a common pattern. */298 ret = sys_close_range(3, UINT_MAX, CLOSE_RANGE_CLOEXEC);299 for (i = 0; i <= 100; i++) {300 int flags = fcntl(open_fds[i], F_GETFD);301 302 EXPECT_GT(flags, -1);303 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);304 }305}306 307TEST(close_range_cloexec_unshare)308{309 int i, ret;310 int open_fds[101];311 struct rlimit rlimit;312 313 for (i = 0; i < ARRAY_SIZE(open_fds); i++) {314 int fd;315 316 fd = open("/dev/null", O_RDONLY);317 ASSERT_GE(fd, 0) {318 if (errno == ENOENT)319 SKIP(return, "Skipping test since /dev/null does not exist");320 }321 322 open_fds[i] = fd;323 }324 325 ret = sys_close_range(1000, 1000, CLOSE_RANGE_CLOEXEC);326 if (ret < 0) {327 if (errno == ENOSYS)328 SKIP(return, "close_range() syscall not supported");329 if (errno == EINVAL)330 SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC");331 }332 333 /* Ensure the FD_CLOEXEC bit is set also with a resource limit in place. */334 ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlimit));335 rlimit.rlim_cur = 25;336 ASSERT_EQ(0, setrlimit(RLIMIT_NOFILE, &rlimit));337 338 /* Set close-on-exec for two ranges: [0-50] and [75-100]. */339 ret = sys_close_range(open_fds[0], open_fds[50],340 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);341 ASSERT_EQ(0, ret);342 ret = sys_close_range(open_fds[75], open_fds[100],343 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);344 ASSERT_EQ(0, ret);345 346 for (i = 0; i <= 50; i++) {347 int flags = fcntl(open_fds[i], F_GETFD);348 349 EXPECT_GT(flags, -1);350 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);351 }352 353 for (i = 51; i <= 74; i++) {354 int flags = fcntl(open_fds[i], F_GETFD);355 356 EXPECT_GT(flags, -1);357 EXPECT_EQ(flags & FD_CLOEXEC, 0);358 }359 360 for (i = 75; i <= 100; i++) {361 int flags = fcntl(open_fds[i], F_GETFD);362 363 EXPECT_GT(flags, -1);364 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);365 }366 367 /* Test a common pattern. */368 ret = sys_close_range(3, UINT_MAX,369 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);370 for (i = 0; i <= 100; i++) {371 int flags = fcntl(open_fds[i], F_GETFD);372 373 EXPECT_GT(flags, -1);374 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);375 }376}377 378/*379 * Regression test for syzbot+96cfd2b22b3213646a93@syzkaller.appspotmail.com380 */381TEST(close_range_cloexec_syzbot)382{383 int fd1, fd2, fd3, fd4, flags, ret, status;384 pid_t pid;385 struct __clone_args args = {386 .flags = CLONE_FILES,387 .exit_signal = SIGCHLD,388 };389 390 /* Create a huge gap in the fd table. */391 fd1 = open("/dev/null", O_RDWR);392 EXPECT_GT(fd1, 0);393 394 fd2 = dup2(fd1, 1000);395 EXPECT_GT(fd2, 0);396 397 flags = fcntl(fd1, F_DUPFD_QUERY, fd2);398 if (flags < 0) {399 EXPECT_EQ(errno, EINVAL);400 } else {401 EXPECT_EQ(flags, 1);402 }403 404 pid = sys_clone3(&args, sizeof(args));405 ASSERT_GE(pid, 0);406 407 if (pid == 0) {408 ret = sys_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);409 if (ret)410 exit(EXIT_FAILURE);411 412 /*413 * We now have a private file descriptor table and all414 * our open fds should still be open but made415 * close-on-exec.416 */417 flags = fcntl(fd1, F_GETFD);418 EXPECT_GT(flags, -1);419 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);420 421 flags = fcntl(fd2, F_GETFD);422 EXPECT_GT(flags, -1);423 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);424 425 fd3 = dup2(fd1, 42);426 EXPECT_GT(fd3, 0);427 428 flags = fcntl(fd1, F_DUPFD_QUERY, fd3);429 if (flags < 0) {430 EXPECT_EQ(errno, EINVAL);431 } else {432 EXPECT_EQ(flags, 1);433 }434 435 436 437 /*438 * Duplicating the file descriptor must remove the439 * FD_CLOEXEC flag.440 */441 flags = fcntl(fd3, F_GETFD);442 EXPECT_GT(flags, -1);443 EXPECT_EQ(flags & FD_CLOEXEC, 0);444 445 exit(EXIT_SUCCESS);446 }447 448 EXPECT_EQ(waitpid(pid, &status, 0), pid);449 EXPECT_EQ(true, WIFEXITED(status));450 EXPECT_EQ(0, WEXITSTATUS(status));451 452 /*453 * We had a shared file descriptor table before along with requesting454 * close-on-exec so the original fds must not be close-on-exec.455 */456 flags = fcntl(fd1, F_GETFD);457 EXPECT_GT(flags, -1);458 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);459 460 flags = fcntl(fd2, F_GETFD);461 EXPECT_GT(flags, -1);462 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);463 464 fd3 = dup2(fd1, 42);465 EXPECT_GT(fd3, 0);466 467 flags = fcntl(fd1, F_DUPFD_QUERY, fd3);468 if (flags < 0) {469 EXPECT_EQ(errno, EINVAL);470 } else {471 EXPECT_EQ(flags, 1);472 }473 474 fd4 = open("/dev/null", O_RDWR);475 EXPECT_GT(fd4, 0);476 477 /* Same inode, different file pointers. */478 flags = fcntl(fd1, F_DUPFD_QUERY, fd4);479 if (flags < 0) {480 EXPECT_EQ(errno, EINVAL);481 } else {482 EXPECT_EQ(flags, 0);483 }484 485 flags = fcntl(fd3, F_GETFD);486 EXPECT_GT(flags, -1);487 EXPECT_EQ(flags & FD_CLOEXEC, 0);488 489 EXPECT_EQ(close(fd1), 0);490 EXPECT_EQ(close(fd2), 0);491 EXPECT_EQ(close(fd3), 0);492 EXPECT_EQ(close(fd4), 0);493}494 495/*496 * Regression test for syzbot+96cfd2b22b3213646a93@syzkaller.appspotmail.com497 */498TEST(close_range_cloexec_unshare_syzbot)499{500 int i, fd1, fd2, fd3, flags, ret, status;501 pid_t pid;502 struct __clone_args args = {503 .flags = CLONE_FILES,504 .exit_signal = SIGCHLD,505 };506 507 /*508 * Create a huge gap in the fd table. When we now call509 * CLOSE_RANGE_UNSHARE with a shared fd table and and with ~0U as upper510 * bound the kernel will only copy up to fd1 file descriptors into the511 * new fd table. If the kernel is buggy and doesn't handle512 * CLOSE_RANGE_CLOEXEC correctly it will not have copied all file513 * descriptors and we will oops!514 *515 * On a buggy kernel this should immediately oops. But let's loop just516 * to be sure.517 */518 fd1 = open("/dev/null", O_RDWR);519 EXPECT_GT(fd1, 0);520 521 fd2 = dup2(fd1, 1000);522 EXPECT_GT(fd2, 0);523 524 for (i = 0; i < 100; i++) {525 526 pid = sys_clone3(&args, sizeof(args));527 ASSERT_GE(pid, 0);528 529 if (pid == 0) {530 ret = sys_close_range(3, ~0U, CLOSE_RANGE_UNSHARE |531 CLOSE_RANGE_CLOEXEC);532 if (ret)533 exit(EXIT_FAILURE);534 535 /*536 * We now have a private file descriptor table and all537 * our open fds should still be open but made538 * close-on-exec.539 */540 flags = fcntl(fd1, F_GETFD);541 EXPECT_GT(flags, -1);542 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);543 544 flags = fcntl(fd2, F_GETFD);545 EXPECT_GT(flags, -1);546 EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);547 548 fd3 = dup2(fd1, 42);549 EXPECT_GT(fd3, 0);550 551 /*552 * Duplicating the file descriptor must remove the553 * FD_CLOEXEC flag.554 */555 flags = fcntl(fd3, F_GETFD);556 EXPECT_GT(flags, -1);557 EXPECT_EQ(flags & FD_CLOEXEC, 0);558 559 EXPECT_EQ(close(fd1), 0);560 EXPECT_EQ(close(fd2), 0);561 EXPECT_EQ(close(fd3), 0);562 563 exit(EXIT_SUCCESS);564 }565 566 EXPECT_EQ(waitpid(pid, &status, 0), pid);567 EXPECT_EQ(true, WIFEXITED(status));568 EXPECT_EQ(0, WEXITSTATUS(status));569 }570 571 /*572 * We created a private file descriptor table before along with573 * requesting close-on-exec so the original fds must not be574 * close-on-exec.575 */576 flags = fcntl(fd1, F_GETFD);577 EXPECT_GT(flags, -1);578 EXPECT_EQ(flags & FD_CLOEXEC, 0);579 580 flags = fcntl(fd2, F_GETFD);581 EXPECT_GT(flags, -1);582 EXPECT_EQ(flags & FD_CLOEXEC, 0);583 584 fd3 = dup2(fd1, 42);585 EXPECT_GT(fd3, 0);586 587 flags = fcntl(fd3, F_GETFD);588 EXPECT_GT(flags, -1);589 EXPECT_EQ(flags & FD_CLOEXEC, 0);590 591 EXPECT_EQ(close(fd1), 0);592 EXPECT_EQ(close(fd2), 0);593 EXPECT_EQ(close(fd3), 0);594}595 596TEST(close_range_bitmap_corruption)597{598 pid_t pid;599 int status;600 struct __clone_args args = {601 .flags = CLONE_FILES,602 .exit_signal = SIGCHLD,603 };604 605 /* get the first 128 descriptors open */606 for (int i = 2; i < 128; i++)607 EXPECT_GE(dup2(0, i), 0);608 609 /* get descriptor table shared */610 pid = sys_clone3(&args, sizeof(args));611 ASSERT_GE(pid, 0);612 613 if (pid == 0) {614 /* unshare and truncate descriptor table down to 64 */615 if (sys_close_range(64, ~0U, CLOSE_RANGE_UNSHARE))616 exit(EXIT_FAILURE);617 618 ASSERT_EQ(fcntl(64, F_GETFD), -1);619 /* ... and verify that the range 64..127 is not620 stuck "fully used" according to secondary bitmap */621 EXPECT_EQ(dup(0), 64)622 exit(EXIT_FAILURE);623 exit(EXIT_SUCCESS);624 }625 626 EXPECT_EQ(waitpid(pid, &status, 0), pid);627 EXPECT_EQ(true, WIFEXITED(status));628 EXPECT_EQ(0, WEXITSTATUS(status));629}630 631TEST(fcntl_created)632{633 for (int i = 0; i < 101; i++) {634 int fd;635 char path[PATH_MAX];636 637 fd = open("/dev/null", O_RDONLY | O_CLOEXEC);638 ASSERT_GE(fd, 0) {639 if (errno == ENOENT)640 SKIP(return,641 "Skipping test since /dev/null does not exist");642 }643 644 /* We didn't create "/dev/null". */645 EXPECT_EQ(fcntl(fd, F_CREATED_QUERY, 0), 0);646 close(fd);647 648 sprintf(path, "aaaa_%d", i);649 fd = open(path, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);650 ASSERT_GE(fd, 0);651 652 /* We created "aaaa_%d". */653 EXPECT_EQ(fcntl(fd, F_CREATED_QUERY, 0), 1);654 close(fd);655 656 fd = open(path, O_RDONLY | O_CLOEXEC);657 ASSERT_GE(fd, 0);658 659 /* We're opening it again, so no positive creation check. */660 EXPECT_EQ(fcntl(fd, F_CREATED_QUERY, 0), 0);661 close(fd);662 unlink(path);663 }664}665 666TEST_HARNESS_MAIN667