From: Alice Ryhl <aliceryhl@google.com>
To: viro@zeniv.linux.org.uk
Cc: a.hindborg@samsung.com, alex.gaynor@gmail.com,
aliceryhl@google.com, arve@android.com, benno.lossin@proton.me,
bjorn3_gh@protonmail.com, boqun.feng@gmail.com,
brauner@kernel.org, cmllamas@google.com,
dan.j.williams@intel.com, dxu@dxuuu.xyz, gary@garyguo.net,
gregkh@linuxfoundation.org, joel@joelfernandes.org,
keescook@chromium.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org, maco@android.com,
ojeda@kernel.org, peterz@infradead.org,
rust-for-linux@vger.kernel.org, surenb@google.com,
tglx@linutronix.de, tkjos@android.com, tmgross@umich.edu,
wedsonaf@gmail.com, willy@infradead.org, yakoyoku@gmail.com
Subject: Re: [PATCH v6 3/8] rust: file: add Rust abstraction for `struct file`
Date: Mon, 27 May 2024 16:03:56 +0000 [thread overview]
Message-ID: <20240527160356.3909000-1-aliceryhl@google.com> (raw)
In-Reply-To: <20240524213245.GT2118490@ZenIV>
Al Viro <viro@zeniv.linux.org.uk> writes:
> > > You obviously are aware of this but I'm just spelling it out. Iirc,
> > > there will practically only ever be one light refcount per file.
> > >
> > > For a light refcount to be used we know that the file descriptor table
> > > isn't shared with any other task. So there are no threads that could
> > > concurrently access the file descriptor table. We also know that the
> > > file descriptor table cannot become shared while we're in system call
> > > context because the caller can't create new threads and they can't
> > > unshare the file descriptor table.
> > >
> > > So there's only one fdget() caller (Yes, they could call fdget()
> > > multiple times and then have to do fdput() multiple times but that's a
> > > level of weirdness that we don't need to worry about.).
> >
> > Hmm. Is it not the case that different processes with different file
> > descriptor tables could reference the same underlying `struct file` and
> > both use light refcounts to do so, as long as each fd table is not
> > shared? So there could be multiple light refcounts to the same `struct
> > file` at the same time on different threads.
>
> Relevant rules:
>
> * Each file pointer in any descriptor table contributes to refcount
> of file.
>
> * All assignments to task->files are done by the task itself or,
> during task creation, by its parent The latter happens before the task
> runs for the first time. The former is done with task_lock(current)
> held.
>
> * current->files is always stable. The object it points to
> is guaranteed to stay alive at least until you explicitly change
> current->files.
> * task->files is stable while you are holding task_lock(task).
> The object it points to is guaranteed to stay alive until you release
> task_lock(task).
> * task->files MAY be fetched (racily) without either of the
> above, but it should not be dereferenced - the memory may be freed
> and reused right after you've fetched the pointer.
>
> * descriptor tables are refcounted by table->count.
> * descriptor table is created with ->count equal to 1 and
> destroyed when its ->count reaches 0.
> * each task with task->files == table contributes to table->count.
> * before the task dies, its ->files becomes NULL (see exit_files()).
> * when task is born (see copy_process() and copy_files())) the parent
> is responsible for setting the value of task->files and making sure that
> refcounts are correct; that's the only case where one is allowed to acquire
> an extra reference to existing table (handling of clone(2) with COPY_FILES).
>
> * the only descriptor table one may modify is that pointed to
> by current->files. Any access to other threads' descriptor tables is
> read-only.
>
> * struct fd is fundamentally thread-local. It should never be
> passed around, put into shared data structures, etc.
>
> * if you have done fdget(N), the matching fdput() MUST be done
> before the caller modifies the Nth slot of its descriptor table,
> spawns children that would share the descriptor table.
>
> * fdget() MAY borrow a reference from caller's descriptor table.
> That can be done if current->files->count is equal to 1.
> In that case we can be certain that the file reference we fetched from
> our descriptor table will remain unchanged (and thus contributing to refcount
> of file) until fdput(). Indeed,
> + at the time of fdget() no other thread has task->files pointing
> to our table (otherwise ->count would be greater than 1).
> + our thread will remain the sole owner of descriptor table at
> least until fdput(). Indeed, the first additional thread with task->files
> pointing to our table would have to have been spawned by us and we are
> forbidden to do that (rules for fdget() use)
> + no other thread could modify our descriptor table (they would
> have to share it first).
> + we are allowed to modify our table, but we are forbidden to touch
> the slot we'd copied from (rules for fdget() use).
>
> In other words, if current->files->count is equal to 1 at fdget() time
> we can skip incrementing refcount. Matching fdput() would need to
> skip decrement, of course. Note that we must record that (borrowed
> vs. cloned) in struct fd - the condition cannot be rechecked at fdput()
> time, since the table that had been shared at fdget() time might no longer
> be shared by the time of fdput().
This is great! It matches my understanding. I didn't know the details
about current->files and task->files.
You should copy this to the kernel documentation somewhere. :)
> > And this does *not* apply to `fdget_pos`, which checks the refcount of
> > the `struct file` instead of the refcount of the fd table.
>
> False. fdget_pos() is identical to fdget() as far as file refcount
> handling goes. The part that is different is that grabbing ->f_pos_lock
> is sensitive to file refcount in some cases. This is orthogonal to
> "does this struct fd contribute to file refcount".
Sorry, I see now that I didn't phrase that quite right. What I meant is
that there are ways of sharing a `struct file` reference during an fdget
scope that are not dangerous, but where it *would be* dangerous if it
was an fdget_pos scope instead. Specifically, the reason they are
dangerous is that they can lead to a data race on the file position if
the fdget_pos scope did not take the f_pos_lock mutex.
For example, during an `fdget(N)` scope, you can always do a `get_file`
and then send it to another process and `fd_install` it into that other
process. There's no way that this could result in the deletion of the
Nth entry of `current->files`.
However, during an `fdget_pos(N)` scope, then it is *not* the case that
it's always okay to send a `get_file` reference to another thread and
`fd_install` it. Because after the remote process returns from the
syscall in which we `fd_install`ed the file, the remote process could
proceed to call another syscall that in turn modifies the file position.
And if the original `fdget_pos(N)` scope modifies the file position
after sending the `get_file` reference, then that could be a data race
on f_pos.
> Again, "light" references are tied to thread; they can only be created
> if we are guaranteed that descriptor table's slot they came from will
> remain unchanged for as long as the reference is used.
>
> And yes, there may be several light references to the same file - both
> in different processes that do not share descriptor table *and* in the
> same thread, if e.g. sendfile(in_fd, out_fd, ...) is called with
> in_fd == out_fd.
Thanks for confirming this!
I hope this reply along with my reply to Christian Brauner also
addresses your other thread. Let me know if it doesn't.
Alice
next prev parent reply other threads:[~2024-05-27 16:04 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-05-17 9:30 [PATCH v6 0/8] File abstractions needed by Rust Binder Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 1/8] rust: types: add `NotThreadSafe` Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 2/8] rust: task: add `Task::current_raw` Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 3/8] rust: file: add Rust abstraction for `struct file` Alice Ryhl
2024-05-24 16:12 ` Christian Brauner
2024-05-24 19:17 ` Alice Ryhl
2024-05-24 21:32 ` Al Viro
2024-05-27 16:03 ` Alice Ryhl [this message]
2024-05-28 19:36 ` Al Viro
2024-05-28 20:29 ` Alice Ryhl
2024-05-28 20:59 ` Al Viro
2024-05-24 22:56 ` Al Viro
2024-05-25 0:33 ` Al Viro
2024-05-25 15:40 ` Al Viro
2024-05-25 11:53 ` Christian Brauner
2024-05-27 16:05 ` Alice Ryhl
2024-05-29 8:17 ` Christian Brauner
2024-05-29 12:58 ` Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 4/8] rust: cred: add Rust abstraction for `struct cred` Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 5/8] rust: security: add abstraction for secctx Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 6/8] rust: file: add `FileDescriptorReservation` Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 7/8] rust: file: add `Kuid` wrapper Alice Ryhl
2024-05-17 9:30 ` [PATCH v6 8/8] rust: file: add abstraction for `poll_table` Alice Ryhl
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240527160356.3909000-1-aliceryhl@google.com \
--to=aliceryhl@google.com \
--cc=a.hindborg@samsung.com \
--cc=alex.gaynor@gmail.com \
--cc=arve@android.com \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=brauner@kernel.org \
--cc=cmllamas@google.com \
--cc=dan.j.williams@intel.com \
--cc=dxu@dxuuu.xyz \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=joel@joelfernandes.org \
--cc=keescook@chromium.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maco@android.com \
--cc=ojeda@kernel.org \
--cc=peterz@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=surenb@google.com \
--cc=tglx@linutronix.de \
--cc=tkjos@android.com \
--cc=tmgross@umich.edu \
--cc=viro@zeniv.linux.org.uk \
--cc=wedsonaf@gmail.com \
--cc=willy@infradead.org \
--cc=yakoyoku@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®