| Age | Commit message (Collapse) | Author |
|
CLOSE_RANGE_CLOEXEC marks a range close-on-exec. Nothing happens to
these fds unless an exec happens. Add CLOSE_RANGE_CLOEXEC_ONLY which
closes the close-on-exec file descriptors in the range.
When combined with CLOSE_RANGE_EXCEPT it names the close-on-exec file
descriptors that are supposed to survive. Every close-on-exec fd outside
of the range is closed. File descriptors without that flag are left
alone. Whatever the caller deliberately passes down, stdio, LISTEN_FDS,
an inherited pipe, remains where it is.
The handful of close-on-exec descriptors the caller still needs for the
exec such as the executable, an error pipe, sit in a specific range.
That is what a task between clone(CLONE_FILES) and execve() actually
wants:
clone(CLONE_FILES | CLONE_VM | CLONE_VFORK)
child: close_range(lo, hi, CLOSE_RANGE_UNSHARE |
CLOSE_RANGE_CLOEXEC_ONLY |
CLOSE_RANGE_EXCEPT)
child: rearrange descriptors in the now private table
child: execve()
Between the clone and the close_range() the child holds no reference of
its own on any file because copy_files() only bumps the fdtable
refcount. So a close() in the parent takes effect immediately. The
unshare also never takes a reference on the fds it leaves behind either.
The range is expressed in the parent's numbering. So a caller that
cannot name the file descriptors it keeps contiguously picks a range
wide enough to cover them, unshares, and tidies up with a second
close_range() on the table it now owns alone. That one is cheap. To keep
nothing, name a range that cannot hold an open descriptor, e.g.
close_range(~0U, ~0U, ...).
CLOSE_RANGE_CLOEXEC and CLOSE_RANGE_CLOEXEC_ONLY are mutually exclusive.
Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-9-c20d0b49270d@kernel.org
Reviewed-by: Jann Horn <jannh@google.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
close_range() operates on [fd, max_fd]. Add CLOSE_RANGE_EXCEPT. This new
flag instructs it to operate on all file descriptors outside of the
range instead.
Without any other flags that means it closes everything except the file
descriptors in the specified range. Together with CLOSE_RANGE_CLOEXEC it
marks every file descriptor except the ones in the range as
close-on-exec. With CLOSE_RANGE_UNSHARE dup_fd() never takes a reference
on what is dropped.
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:
close_range(lo, hi, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT)
Nothing outside of the window is referenced by the child at any point.
Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-6-c20d0b49270d@kernel.org
Reviewed-by: Jann Horn <jannh@google.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
Turn the close_range() flags into an enum. Makes debugging a lot easier.
No functional changes.
Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-5-c20d0b49270d@kernel.org
Reviewed-by: Jann Horn <jannh@google.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
|
|
When the flag CLOSE_RANGE_CLOEXEC is set, close_range doesn't
immediately close the files but it sets the close-on-exec bit.
It is useful for e.g. container runtimes that usually install a
seccomp profile "as late as possible" before execv'ing the container
process itself. The container runtime could either do:
1 2
- install_seccomp_profile(); - close_range(MIN_FD, MAX_INT, 0);
- close_range(MIN_FD, MAX_INT, 0); - install_seccomp_profile();
- execve(...); - execve(...);
Both alternative have some disadvantages.
In the first variant the seccomp_profile cannot block the close_range
syscall, as well as opendir/read/close/... for the fallback on older
kernels.
In the second variant, close_range() can be used only on the fds
that are not going to be needed by the runtime anymore, and it must be
potentially called multiple times to account for the different ranges
that must be closed.
Using close_range(..., ..., CLOSE_RANGE_CLOEXEC) solves these issues.
The runtime is able to use the existing open fds, the seccomp profile
can block close_range() and the syscalls used for its fallback.
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Link: https://lore.kernel.org/r/20201118104746.873084-2-gscrivan@redhat.com
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
|
|
One of the use-cases of close_range() is to drop file descriptors just before
execve(). This would usually be expressed in the sequence:
unshare(CLONE_FILES);
close_range(3, ~0U);
as pointed out by Linus it might be desirable to have this be a part of
close_range() itself under a new flag CLOSE_RANGE_UNSHARE.
This expands {dup,unshare)_fd() to take a max_fds argument that indicates the
maximum number of file descriptors to copy from the old struct files. When the
user requests that all file descriptors are supposed to be closed via
close_range(min, max) then we can cap via unshare_fd(min) and hence don't need
to do any of the heavy fput() work for everything above min.
The patch makes it so that if CLOSE_RANGE_UNSHARE is requested and we do in
fact currently share our file descriptor table we create a new private copy.
We then close all fds in the requested range and finally after we're done we
install the new fd table.
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
|