All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Gary Guo" <gary@garyguo.net>
To: "Georgios Androutsopoulos" <georgeandrout13@gmail.com>,
	"Alexander Viro" <viro@zeniv.linux.org.uk>,
	"Christian Brauner" <brauner@kernel.org>,
	"Miguel Ojeda" <ojeda@kernel.org>
Cc: "Jan Kara" <jack@suse.cz>, "Boqun Feng" <boqun@kernel.org>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Onur Özkan" <work@onurozkan.dev>,
	linux-fsdevel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH] rust: file: handle fd table teardown in file descriptor APIs
Date: Sun, 20 Sep 2026 21:48:54 +0100	[thread overview]
Message-ID: <DLKFTVSN56P2.2NHCFW6NIUOAH@garyguo.net> (raw)
In-Reply-To: <20260920200154.1194983-1-georgeandrout13@gmail.com>

On Sun Sep 20, 2026 at 9:01 PM BST, Georgios Androutsopoulos wrote:
> Several Rust file descriptor APIs rely on `current->files` being
> available. However, `exit_files()` clears it while execution may still
> continue on the same task.
>
> This affects `LocalFile::fget()` and the
> `FileDescriptorReservation` operations that call
> `get_unused_fd_flags()`, `fd_install()`, and `put_unused_fd()`.
> `FileDescriptorReservation` cannot cross task boundaries, but remaining
> on the same task does not guarantee that `current->files` is still
> available when these operations are performed.
>
> Guard the affected operations against a missing `current->files`.
> `LocalFile::fget()` returns `EBADF` and
> `FileDescriptorReservation::get_unused_fd_flags()` returns `EMFILE`.
> For the infallible `fd_install()` and drop paths, warn once and avoid
> calling the corresponding C helper when the fd table is already gone.
>
> This prevents NULL dereferences through these safe Rust APIs after
> `exit_files()`.

I suppose we could add these checks to C side instead, although perhaps one may
say "its bad caller code and not worth checking"?

So having these checks on Rust side is okay to me.

>
> Fixes: 851849824bb5 ("rust: file: add Rust abstraction for `struct file`")
> Fixes: 5da9857b127e ("rust: file: add `FileDescriptorReservation`")
> Closes: https://github.com/Rust-for-Linux/linux/issues/1256
> Signed-off-by: Georgios Androutsopoulos <georgeandrout13@gmail.com>
> ---
>  rust/kernel/fs/file.rs | 69 ++++++++++++++++++++++++++++++++++++------
>  1 file changed, 60 insertions(+), 9 deletions(-)
>
> diff --git a/rust/kernel/fs/file.rs b/rust/kernel/fs/file.rs
> index 23ee689bd240..559f0985b12c 100644
> --- a/rust/kernel/fs/file.rs
> +++ b/rust/kernel/fs/file.rs
> @@ -260,7 +260,17 @@ impl LocalFile {
>      /// [`assume_no_fdget_pos`]: LocalFile::assume_no_fdget_pos
>      #[inline]
>      pub fn fget(fd: u32) -> Result<ARef<LocalFile>, BadFdError> {
> -        // SAFETY: FFI call, there are no requirements on `fd`.
> +        let current = crate::current!();
> +
> +        // SAFETY: `current` points to the currently executing task, so it is
> +        // valid to read its `files` pointer. The pointer may be null during
> +        // task teardown.
> +        if unsafe { (*current.as_ptr()).files.is_null() } {
> +            return Err(BadFdError);
> +        }

I think we want to add `unlikely()` on them (which is being added by
https://lore.kernel.org/rust-for-linux/20260406095820.465994-2-ojeda@kernel.org/).

> +
> +        // SAFETY: There are no requirements on `fd`. We checked above that the
> +        // current task still has a file descriptor table, which `fget` accesses.
>          let ptr = ptr::NonNull::new(unsafe { bindings::fget(fd) }).ok_or(BadFdError)?;
>  
>          // SAFETY: `bindings::fget` created a refcount, and we pass ownership of it to the `ARef`.
> @@ -403,7 +413,18 @@ impl FileDescriptorReservation {
>      /// Creates a new file descriptor reservation.
>      #[inline]
>      pub fn get_unused_fd_flags(flags: u32) -> Result<Self> {
> -        // SAFETY: FFI call, there are no safety requirements on `flags`.
> +        let current = crate::current!();
> +
> +        // SAFETY: `current` points to the currently executing task, so it is
> +        // valid to read its `files` pointer. The pointer may be null during
> +        // task teardown.
> +        if unsafe { (*current.as_ptr()).files.is_null() } {
> +            return Err(EMFILE);
> +        }
> +
> +        // SAFETY: There are no safety requirements on `flags`. We checked above
> +        // that the current task still has a file descriptor table, which
> +        // `get_unused_fd_flags` accesses.
>          let fd: i32 = unsafe { bindings::get_unused_fd_flags(flags) };
>          to_result(fd)?;
>  
> @@ -421,13 +442,30 @@ pub fn reserved_fd(&self) -> u32 {
>  
>      /// Commits the reservation.
>      ///
> -    /// The previously reserved file descriptor is bound to `file`. This method consumes the
> -    /// [`FileDescriptorReservation`], so it will not be usable after this call.
> +    /// The previously reserved file descriptor is bound to `file`. If the current task no longer
> +    /// has a file descriptor table, the reservation is abandoned instead. This method consumes the
> +    /// [`FileDescriptorReservation`] in either case.
>      #[inline]
>      pub fn fd_install(self, file: ARef<File>) {
> -        // SAFETY: `self.fd` was previously returned by `get_unused_fd_flags`. We have not yet used
> -        // the fd, so it is still valid, and `current` still refers to the same task, as this type
> -        // cannot be moved across task boundaries.
> +        let current = crate::current!();
> +
> +        // SAFETY: `current` points to the currently executing task, so it is
> +        // valid to read its `files` pointer. The pointer may be null during
> +        // task teardown.
> +        if unsafe { (*current.as_ptr()).files.is_null() } {
> +            crate::pr_warn_once!(
> +                "FileDescriptorReservation::fd_install called with current->files == NULL\n"
> +            );

I wonder if we should upgrade this to `WARN_ONCE`. As code being executed when
exiting are cleanup code, for this code path to be hit, it would mean that some
code is installing FD descriptor while being dropped -- which is likely a bug.

Putting a "BTW, some Rust code is installing a FD when process is exiting" in
dmesg is not going to be useful to understand what's going on. We'd want a full
backtrace.

On the other hand, dropping a `FileDescriptorReservation` is a more realistic,
so we perhaps might even want to declare it being okay (see below).

> +
> +            // `put_unused_fd` also requires `current->files` to be valid, so do not run
> +            // the reservation's destructor after the current task has lost its fd table.
> +            core::mem::forget(self);
> +            return;
> +        }
> +
> +        // SAFETY: `self.fd` was previously returned by `get_unused_fd_flags` and has not yet been
> +        // used. This type cannot be moved across task boundaries, so `current` still refers to the
> +        // same task, and we checked above that it still has an fd table.
>          //
>          // Furthermore, the file pointer is guaranteed to own a refcount by its type invariants,
>          // and we take ownership of that refcount by not running the destructor below.
> @@ -446,9 +484,22 @@ pub fn fd_install(self, file: ARef<File>) {
>  impl Drop for FileDescriptorReservation {
>      #[inline]
>      fn drop(&mut self) {
> +        let current = crate::current!();
> +
> +        // SAFETY: `current` points to the currently executing task, so it is
> +        // valid to read its `files` pointer. The pointer may be null during
> +        // task teardown.
> +        if unsafe { (*current.as_ptr()).files.is_null() } {
> +            crate::pr_warn_once!(
> +                "FileDescriptorReservation dropped with current->files == NULL\n"
> +            );

I think we can remove this warning. Skipping put_unused_fd isn't actually
leaking anything as the files_struct is cleaned up.

Best,
Gary


> +            return;
> +        }
> +
>          // SAFETY: By the type invariants of this type, `self.fd` was previously returned by
> -        // `get_unused_fd_flags`. We have not yet used the fd, so it is still valid, and `current`
> -        // still refers to the same task, as this type cannot be moved across task boundaries.
> +        // `get_unused_fd_flags` and has not yet been used. This type cannot be moved across task
> +        // boundaries, so `current` still refers to the same task, and we checked above that it
> +        // still has an fd table.
>          unsafe { bindings::put_unused_fd(self.fd) };
>      }
>  }



  reply	other threads:[~2026-09-20 20:48 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-20 20:01 [PATCH] rust: file: handle fd table teardown in file descriptor APIs Georgios Androutsopoulos
2026-09-20 20:48 ` Gary Guo [this message]
2026-09-22 18:30   ` Georgios Androutsopoulos
2026-09-22 19:55     ` Gary Guo
2026-09-25 15:15   ` Christian Brauner

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=DLKFTVSN56P2.2NHCFW6NIUOAH@garyguo.net \
    --to=gary@garyguo.net \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=brauner@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=georgeandrout13@gmail.com \
    --cc=jack@suse.cz \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tamird@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=viro@zeniv.linux.org.uk \
    --cc=work@onurozkan.dev \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.