files,close_range: add CLOSE_RANGE_{CLOEXEC_ONLY,EXCEPT} - #2655
Open
vfsci-bot[bot] wants to merge 10 commits into
Open
vfsci-bot[bot] wants to merge 10 commits into
vfsci-bot[bot] wants to merge 10 commits into
Conversation
close_range(CLOSE_RANGE_UNSHARE) passed the range it is about to close to dup_fd() so the clone is done without that range. This only works when the last open descriptor falls into the range. A range in the middle of the table is copied like everything else. That's wasteful. Don't copy them. Descriptors in a skipped range stay open in the source fdtable. They are never copied into the new table and no reference is taken on them. Figuring out the size of the table follows the same rule. That drops the special-case it has now. This also means we stop calling ->flush() on fds from a table they were never part of. With the range at the top of the table that was already mostly the case. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
Cover a range in the middle of a full word: - the descriptors in it are gone from the clone and the ones around it are still there - the slots are handed out again, the word is not left marked full - the table the child cloned from is untouched Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
dup_fd() will be taught what to do with the range that was passed to it. It won't always be dropped. Rename it to a plain "range" from "punch_hole". close_range() keeps a pointer named drop for the one case it has today. No functional changes. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
Add FD_RANGE_EXCEPT. It turns the meaning of range around. Instead of indicating that the descriptors in the range are the ones that are left out of the copy they indicate the range that makes it into the copy. All other files are left behind. The clone only has to reach the last open descriptor inside the range. Nothing passes the flag yet. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
Turn the close_range() flags into an enum. Makes debugging a lot easier. No functional changes. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
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. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
Cover the inverted range: - with plain close the window stays and everything outside of it goes, stdio included - a window at the top, one that cannot hold a descriptor and one of a single descriptor keep just what they name, and so does the unshare form on a table that is not shared - with CLOSE_RANGE_CLOEXEC everything outside of the window is marked and the window is not, in place and in a clone, for a window in the middle, at the bottom, at the top and above the table - with CLOSE_RANGE_UNSHARE the table the child cloned from is untouched, a window near the top of the table comes back whole, one at the bottom keeps stdio, one that cannot hold a descriptor keeps nothing, and the slots left behind are handed out again from the bottom - the bounds are checked before the range is turned around The extra cases came out of walking the window positions that __range_close(), __range_cloexec() and dup_fd() tell apart: at the bottom, in the middle, at the top, above the table, a single descriptor and none, on a shared and on a private table. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
Add FD_RANGE_CLOEXEC_ONLY. When set dup_fd() leaves everything behind except for fds that are close-on-exec. Without FD_RANGE_EXCEPT the clone loses the close-on-exec descriptors in the range. With it the clone keeps the range and loses the close-on-exec descriptors everywhere else. Descriptors without the flag are carried over either way. Nothing passes the flag yet. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
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. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
Cover closing what is marked: - close-on-exec descriptors in the range go, the ones outside stay, and a range above the table closes nothing - with CLOSE_RANGE_EXCEPT it is the other way around, and the kept ones keep their flag, for a window in the middle, at the top and at the bottom - descriptors without close-on-exec are never touched, in or out of the range - a range that cannot hold an open descriptor keeps nothing, one that covers everything keeps everything, in place and in a clone - the unshare form leaves the table it was cloned from alone, with and without CLOSE_RANGE_EXCEPT - the unshare form keeps the descriptors without the flag that sit in the range it drops from, and hands the dropped slots out again - a kept range above the last descriptor without close-on-exec still comes back, so the clone is sized off the range too Also check that asking for CLOSE_RANGE_CLOEXEC at the same time is refused, whatever else is asked for, and that the bounds are still checked. The extra cases came out of the same walk with the close-on-exec mask on top: a marked and an unmarked descriptor on each side of every window position, and the size of the clone when only unmarked ones are left in the range it drops from. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Reviewed-by: Jann Horn <jannh@google.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Series: https://patchwork.kernel.org/project/linux-fsdevel/list/?series=1170443
Submitter: Christian Brauner
Version: 1
Patches: 10/10
Message-ID:
<20260921-work-file-close_range_except-v1-0-c20d0b49270d@kernel.org>Base: vfs.base.ci
Lore: https://lore.kernel.org/linux-fsdevel/20260921-work-file-close_range_except-v1-0-c20d0b49270d@kernel.org
Automated by ml2pr