mirror of
https://github.com/torvalds/linux.git
synced 2026-10-23 15:32:04 +02:00
vfs-7.4-rc1.close_range
Please consider pulling these changes from the signed vfs-7.4-rc1.close_range tag. Thanks! Christian -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlgAKCRCRxhvAZXjc ouFoAP9oJWCJRlKwbki5KP0P3O6/gnQXUz8gR07qxzWk8Gz/NgD/V3+MHkU8fX2w oTOGPuwrbbRNBTK/cmRG2kF6xxOPSw8= =jDl8 -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
4effd80841
200
fs/file.c
200
fs/file.c
|
|
@ -352,24 +352,78 @@ static inline bool fd_is_open(unsigned int fd, const struct fdtable *fdt)
|
|||
return test_bit(fd, fdt->open_fds);
|
||||
}
|
||||
|
||||
/* Bits of [range->from, range->to] that fall into word @i of a bitmap. */
|
||||
static unsigned long fd_range_word(struct fd_range *range, unsigned int i)
|
||||
{
|
||||
unsigned int first = i * BITS_PER_LONG;
|
||||
unsigned int last = first + BITS_PER_LONG - 1;
|
||||
|
||||
if (range->to < first || range->from > last)
|
||||
return 0;
|
||||
return GENMASK(min(range->to, last) - first,
|
||||
max(range->from, first) - first);
|
||||
}
|
||||
|
||||
/* Bits of word @i that dup_fd() leaves behind. */
|
||||
static unsigned long dup_fd_dropped_word(struct fdtable *fdt, unsigned int i,
|
||||
struct fd_range *range)
|
||||
{
|
||||
unsigned long dropped;
|
||||
|
||||
if (!range)
|
||||
return 0;
|
||||
dropped = fd_range_word(range, i);
|
||||
if (range->flags & FD_RANGE_EXCEPT)
|
||||
dropped = ~dropped;
|
||||
if (range->flags & FD_RANGE_CLOEXEC_ONLY)
|
||||
dropped &= fdt->close_on_exec[i];
|
||||
return dropped;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that a sane fdtable size always has to be a multiple of
|
||||
* BITS_PER_LONG, since we have bitmaps that are sized by this.
|
||||
*
|
||||
* punch_hole is optional - when close_range() is asked to unshare
|
||||
* and close, we don't need to copy descriptors in that range, so
|
||||
* a smaller cloned descriptor table might suffice if the last
|
||||
* currently opened descriptor falls into that range.
|
||||
* range is optional. When close_range() is asked to unshare dup_fd()
|
||||
* will leave any files behind according to the range and its flags. The
|
||||
* cloned table only has to reach the last open descriptor that is
|
||||
* carried over.
|
||||
*/
|
||||
static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *punch_hole)
|
||||
static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *range)
|
||||
{
|
||||
unsigned int last = find_last_bit(fdt->open_fds, fdt->max_fds);
|
||||
unsigned int i;
|
||||
|
||||
if (last == fdt->max_fds)
|
||||
return NR_OPEN_DEFAULT;
|
||||
if (punch_hole && punch_hole->to >= last && punch_hole->from <= last) {
|
||||
last = find_last_bit(fdt->open_fds, punch_hole->from);
|
||||
if (last == punch_hole->from)
|
||||
if (!range)
|
||||
return ALIGN(last + 1, BITS_PER_LONG);
|
||||
|
||||
if (range->flags & FD_RANGE_CLOEXEC_ONLY) {
|
||||
/* The close-on-exec bits decide what is dropped, walk the words. */
|
||||
i = last / BITS_PER_LONG + 1;
|
||||
while (i--) {
|
||||
unsigned long dropped = dup_fd_dropped_word(fdt, i, range);
|
||||
|
||||
if (fdt->open_fds[i] & ~dropped)
|
||||
return (i + 1) * BITS_PER_LONG;
|
||||
}
|
||||
return NR_OPEN_DEFAULT;
|
||||
}
|
||||
|
||||
if (range->flags & FD_RANGE_EXCEPT) {
|
||||
/* Only the range is carried over. */
|
||||
if (last > range->to) {
|
||||
last = find_last_bit(fdt->open_fds, range->to + 1);
|
||||
if (last > range->to)
|
||||
return NR_OPEN_DEFAULT;
|
||||
}
|
||||
if (last < range->from)
|
||||
return NR_OPEN_DEFAULT;
|
||||
} else if (last >= range->from && last <= range->to) {
|
||||
/* The last open descriptor goes, the kept ones sit below the range. */
|
||||
last = find_last_bit(fdt->open_fds, range->from);
|
||||
if (last == range->from)
|
||||
return NR_OPEN_DEFAULT;
|
||||
}
|
||||
return ALIGN(last + 1, BITS_PER_LONG);
|
||||
|
|
@ -378,13 +432,14 @@ static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *punc
|
|||
/*
|
||||
* Allocate a new descriptor table and copy contents from the passed in
|
||||
* instance. Returns a pointer to cloned table on success, ERR_PTR()
|
||||
* on failure. For 'punch_hole' see sane_fdtable_size().
|
||||
* on failure. For 'range' see sane_fdtable_size().
|
||||
*/
|
||||
struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_hole)
|
||||
struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *range)
|
||||
{
|
||||
struct files_struct *newf;
|
||||
struct file **old_fds, **new_fds;
|
||||
unsigned int open_files, i;
|
||||
unsigned int open_files, fd;
|
||||
unsigned long dropped = 0;
|
||||
struct fdtable *old_fdt, *new_fdt;
|
||||
|
||||
newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
|
||||
|
|
@ -406,7 +461,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho
|
|||
|
||||
spin_lock(&oldf->file_lock);
|
||||
old_fdt = files_fdtable(oldf);
|
||||
open_files = sane_fdtable_size(old_fdt, punch_hole);
|
||||
open_files = sane_fdtable_size(old_fdt, range);
|
||||
|
||||
/*
|
||||
* Check whether we need to allocate a larger fd array and fd set.
|
||||
|
|
@ -430,7 +485,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho
|
|||
*/
|
||||
spin_lock(&oldf->file_lock);
|
||||
old_fdt = files_fdtable(oldf);
|
||||
open_files = sane_fdtable_size(old_fdt, punch_hole);
|
||||
open_files = sane_fdtable_size(old_fdt, range);
|
||||
}
|
||||
|
||||
copy_fd_bitmaps(new_fdt, old_fdt, open_files / BITS_PER_LONG);
|
||||
|
|
@ -451,13 +506,18 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho
|
|||
*
|
||||
* Instead of trying to placate userspace racing with itself, we
|
||||
* ref the file if we see it and mark the fd slot as unused otherwise.
|
||||
* Descriptors dup_fd() is asked to leave behind get the same treatment.
|
||||
*/
|
||||
for (i = open_files; i != 0; i--) {
|
||||
for (fd = 0; fd < open_files; fd++) {
|
||||
struct file *f = rcu_dereference_raw(*old_fds++);
|
||||
if (f) {
|
||||
|
||||
if (!(fd % BITS_PER_LONG))
|
||||
dropped = dup_fd_dropped_word(old_fdt, fd / BITS_PER_LONG, range);
|
||||
if (f && !(dropped & BIT_MASK(fd))) {
|
||||
get_file(f);
|
||||
} else {
|
||||
__clear_open_fd(open_files - i, new_fdt);
|
||||
f = NULL;
|
||||
__clear_open_fd(fd, new_fdt);
|
||||
}
|
||||
rcu_assign_pointer(*new_fds++, f);
|
||||
}
|
||||
|
|
@ -759,34 +819,78 @@ static inline unsigned last_fd(struct fdtable *fdt)
|
|||
}
|
||||
|
||||
static inline void __range_cloexec(struct files_struct *cur_fds,
|
||||
unsigned int fd, unsigned int max_fd)
|
||||
struct fd_range *range)
|
||||
{
|
||||
struct fdtable *fdt;
|
||||
unsigned int last;
|
||||
|
||||
/* make sure we're using the correct maximum value */
|
||||
spin_lock(&cur_fds->file_lock);
|
||||
fdt = files_fdtable(cur_fds);
|
||||
max_fd = min(last_fd(fdt), max_fd);
|
||||
if (fd <= max_fd)
|
||||
bitmap_set(fdt->close_on_exec, fd, max_fd - fd + 1);
|
||||
/* make sure we're using the correct maximum value */
|
||||
last = last_fd(fdt);
|
||||
if (!(range->flags & FD_RANGE_EXCEPT)) {
|
||||
if (range->from <= last)
|
||||
bitmap_set(fdt->close_on_exec, range->from,
|
||||
min(range->to, last) - range->from + 1);
|
||||
} else {
|
||||
if (range->from > 0)
|
||||
bitmap_set(fdt->close_on_exec, 0,
|
||||
min(range->from - 1, last) + 1);
|
||||
if (range->to < last)
|
||||
bitmap_set(fdt->close_on_exec, range->to + 1,
|
||||
last - range->to);
|
||||
}
|
||||
spin_unlock(&cur_fds->file_lock);
|
||||
}
|
||||
|
||||
static inline void __range_close(struct files_struct *files, unsigned int fd,
|
||||
unsigned int max_fd)
|
||||
/* Next open descriptor in [fd, max_fd], or the next close-on-exec one. */
|
||||
static inline unsigned int next_open_fd(struct fdtable *fdt, unsigned int fd,
|
||||
unsigned int max_fd,
|
||||
struct fd_range *range)
|
||||
{
|
||||
if (range->flags & FD_RANGE_CLOEXEC_ONLY)
|
||||
return find_next_and_bit(fdt->open_fds, fdt->close_on_exec,
|
||||
max_fd + 1, fd);
|
||||
return find_next_bit(fdt->open_fds, max_fd + 1, fd);
|
||||
}
|
||||
|
||||
/* Next open descriptor in [fd, max_fd] that @range selects. */
|
||||
static inline unsigned int next_fd_to_close(struct fdtable *fdt,
|
||||
unsigned int fd, unsigned int max_fd,
|
||||
struct fd_range *range)
|
||||
{
|
||||
fd = next_open_fd(fdt, fd, max_fd, range);
|
||||
/* Hop over the window the range keeps. */
|
||||
if ((range->flags & FD_RANGE_EXCEPT) &&
|
||||
fd >= range->from && fd <= range->to) {
|
||||
if (range->to >= max_fd)
|
||||
return max_fd + 1;
|
||||
fd = next_open_fd(fdt, range->to + 1, max_fd, range);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static inline void __range_close(struct files_struct *files,
|
||||
struct fd_range *range)
|
||||
{
|
||||
struct file *file;
|
||||
struct fdtable *fdt;
|
||||
unsigned n;
|
||||
unsigned int fd, max_fd;
|
||||
|
||||
spin_lock(&files->file_lock);
|
||||
fdt = files_fdtable(files);
|
||||
n = last_fd(fdt);
|
||||
max_fd = min(max_fd, n);
|
||||
if (range->flags & FD_RANGE_EXCEPT) {
|
||||
/* Outside of the range means the whole table. */
|
||||
fd = 0;
|
||||
max_fd = last_fd(fdt);
|
||||
} else {
|
||||
fd = range->from;
|
||||
max_fd = min(range->to, last_fd(fdt));
|
||||
}
|
||||
|
||||
for (fd = find_next_bit(fdt->open_fds, max_fd + 1, fd);
|
||||
for (fd = next_fd_to_close(fdt, fd, max_fd, range);
|
||||
fd <= max_fd;
|
||||
fd = find_next_bit(fdt->open_fds, max_fd + 1, fd + 1)) {
|
||||
fd = next_fd_to_close(fdt, fd + 1, max_fd, range)) {
|
||||
file = file_close_fd_locked(files, fd);
|
||||
if (file) {
|
||||
spin_unlock(&files->file_lock);
|
||||
|
|
@ -814,21 +918,43 @@ static inline void __range_close(struct files_struct *files, unsigned int fd,
|
|||
* This closes a range of file descriptors. All file descriptors
|
||||
* from @fd up to and including @max_fd are closed.
|
||||
* Currently, errors to close a given file descriptor are ignored.
|
||||
*
|
||||
* With CLOSE_RANGE_EXCEPT the range names what to leave alone instead:
|
||||
* every open file descriptor outside of [@fd, @max_fd] is closed, or
|
||||
* marked close-on-exec with CLOSE_RANGE_CLOEXEC.
|
||||
*
|
||||
* With CLOSE_RANGE_CLOEXEC_ONLY only file descriptors that have
|
||||
* close-on-exec set are closed. Together with CLOSE_RANGE_EXCEPT the
|
||||
* range names the close-on-exec file descriptors to keep. To keep none
|
||||
* of them, name a range that cannot hold an open file descriptor, e.g.
|
||||
* close_range(~0U, ~0U, ...).
|
||||
*/
|
||||
SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
|
||||
unsigned int, flags)
|
||||
{
|
||||
struct task_struct *me = current;
|
||||
struct files_struct *cur_fds = me->files, *fds = NULL;
|
||||
struct fd_range range = {fd, max_fd};
|
||||
|
||||
if (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC))
|
||||
if (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC |
|
||||
CLOSE_RANGE_EXCEPT | CLOSE_RANGE_CLOEXEC_ONLY))
|
||||
return -EINVAL;
|
||||
|
||||
/* One marks close-on-exec, the other closes what is marked. */
|
||||
if (hweight32(flags & (CLOSE_RANGE_CLOEXEC |
|
||||
CLOSE_RANGE_CLOEXEC_ONLY)) > 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (fd > max_fd)
|
||||
return -EINVAL;
|
||||
|
||||
if (flags & CLOSE_RANGE_EXCEPT)
|
||||
range.flags |= FD_RANGE_EXCEPT;
|
||||
if (flags & CLOSE_RANGE_CLOEXEC_ONLY)
|
||||
range.flags |= FD_RANGE_CLOEXEC_ONLY;
|
||||
|
||||
if ((flags & CLOSE_RANGE_UNSHARE) && atomic_read(&cur_fds->count) > 1) {
|
||||
struct fd_range range = {fd, max_fd}, *punch_hole = ⦥
|
||||
struct fd_range *drop = ⦥
|
||||
|
||||
/*
|
||||
* If the caller requested all fds to be made cloexec we always
|
||||
|
|
@ -836,9 +962,9 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
|
|||
* use them.
|
||||
*/
|
||||
if (flags & CLOSE_RANGE_CLOEXEC)
|
||||
punch_hole = NULL;
|
||||
drop = NULL;
|
||||
|
||||
fds = dup_fd(cur_fds, punch_hole);
|
||||
fds = dup_fd(cur_fds, drop);
|
||||
if (IS_ERR(fds))
|
||||
return PTR_ERR(fds);
|
||||
/*
|
||||
|
|
@ -848,10 +974,12 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
|
|||
swap(cur_fds, fds);
|
||||
}
|
||||
|
||||
if (flags & CLOSE_RANGE_CLOEXEC)
|
||||
__range_cloexec(cur_fds, fd, max_fd);
|
||||
else
|
||||
__range_close(cur_fds, fd, max_fd);
|
||||
if (flags & CLOSE_RANGE_CLOEXEC) {
|
||||
__range_cloexec(cur_fds, &range);
|
||||
} else if (!fds) {
|
||||
/* If we unshared, dup_fd() already left behind what we'd close. */
|
||||
__range_close(cur_fds, &range);
|
||||
}
|
||||
|
||||
if (fds) {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -101,8 +101,17 @@ struct task_struct;
|
|||
|
||||
void put_files_struct(struct files_struct *fs);
|
||||
int unshare_files(void);
|
||||
enum fd_range_flags {
|
||||
/* Leave behind all descriptors outside of the specified range. */
|
||||
FD_RANGE_EXCEPT = (1U << 0),
|
||||
|
||||
/* Only select descriptors that have close-on-exec set. */
|
||||
FD_RANGE_CLOEXEC_ONLY = (1U << 1),
|
||||
};
|
||||
|
||||
struct fd_range {
|
||||
unsigned int from, to;
|
||||
enum fd_range_flags flags;
|
||||
};
|
||||
struct files_struct *dup_fd(struct files_struct *, struct fd_range *) __latent_entropy;
|
||||
void do_close_on_exec(struct files_struct *);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,34 @@
|
|||
#ifndef _UAPI_LINUX_CLOSE_RANGE_H
|
||||
#define _UAPI_LINUX_CLOSE_RANGE_H
|
||||
|
||||
/* Unshare the file descriptor table before closing file descriptors. */
|
||||
#define CLOSE_RANGE_UNSHARE (1U << 1)
|
||||
/*
|
||||
* A macro of one of these names defined before this header is parsed, by
|
||||
* a libc or by a program's own fallback, would replace the enumerator.
|
||||
*/
|
||||
#undef CLOSE_RANGE_UNSHARE
|
||||
#undef CLOSE_RANGE_CLOEXEC
|
||||
#undef CLOSE_RANGE_EXCEPT
|
||||
#undef CLOSE_RANGE_CLOEXEC_ONLY
|
||||
|
||||
/* Set the FD_CLOEXEC bit instead of closing the file descriptor. */
|
||||
#define CLOSE_RANGE_CLOEXEC (1U << 2)
|
||||
enum close_range_flags {
|
||||
/* Unshare the file descriptor table before closing file descriptors. */
|
||||
CLOSE_RANGE_UNSHARE = (1U << 1),
|
||||
|
||||
/* Set the FD_CLOEXEC bit instead of closing the file descriptor. */
|
||||
CLOSE_RANGE_CLOEXEC = (1U << 2),
|
||||
|
||||
/* Act on every file descriptor outside of the given range instead. */
|
||||
CLOSE_RANGE_EXCEPT = (1U << 3),
|
||||
|
||||
/* Only close file descriptors that have the FD_CLOEXEC bit set. */
|
||||
CLOSE_RANGE_CLOEXEC_ONLY = (1U << 4),
|
||||
};
|
||||
|
||||
/* Keep #ifdef working and let glibc skip its own definitions. */
|
||||
#define CLOSE_RANGE_UNSHARE CLOSE_RANGE_UNSHARE
|
||||
#define CLOSE_RANGE_CLOEXEC CLOSE_RANGE_CLOEXEC
|
||||
#define CLOSE_RANGE_EXCEPT CLOSE_RANGE_EXCEPT
|
||||
#define CLOSE_RANGE_CLOEXEC_ONLY CLOSE_RANGE_CLOEXEC_ONLY
|
||||
|
||||
#endif /* _UAPI_LINUX_CLOSE_RANGE_H */
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user