* [PATCH] rust: file: handle fd table teardown in file descriptor APIs
@ 2026-09-20 20:01 Georgios Androutsopoulos
2026-09-20 20:48 ` Gary Guo
0 siblings, 1 reply; 5+ messages in thread
From: Georgios Androutsopoulos @ 2026-09-20 20:01 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Miguel Ojeda
Cc: Jan Kara, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, linux-fsdevel,
rust-for-linux, linux-kernel, Georgios Androutsopoulos
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()`.
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);
+ }
+
+ // 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"
+ );
+
+ // `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"
+ );
+ 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) };
}
}
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] rust: file: handle fd table teardown in file descriptor APIs
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
2026-09-22 18:30 ` Georgios Androutsopoulos
2026-09-25 15:15 ` Christian Brauner
0 siblings, 2 replies; 5+ messages in thread
From: Gary Guo @ 2026-09-20 20:48 UTC (permalink / raw)
To: Georgios Androutsopoulos, Alexander Viro, Christian Brauner,
Miguel Ojeda
Cc: Jan Kara, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, linux-fsdevel,
rust-for-linux, linux-kernel
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) };
> }
> }
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] rust: file: handle fd table teardown in file descriptor APIs
2026-09-20 20:48 ` Gary Guo
@ 2026-09-22 18:30 ` Georgios Androutsopoulos
2026-09-22 19:55 ` Gary Guo
2026-09-25 15:15 ` Christian Brauner
1 sibling, 1 reply; 5+ messages in thread
From: Georgios Androutsopoulos @ 2026-09-22 18:30 UTC (permalink / raw)
To: Gary Guo
Cc: Alexander Viro, Christian Brauner, Miguel Ojeda, Jan Kara,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
linux-fsdevel, rust-for-linux, linux-kernel
On Sun, Sep 20, 2026 at 4:48 PM Gary Guo <gary@garyguo.net> wrote:
> 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/).
I checked current `linux-next` and `rust-next`, but this does not seem to
have landed yet. Should I leave these as regular checks for now and add a
`TODO:` to use `unlikely()` once it is available?
> 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.
`WARN_ONCE` does not seem to be available on the Rust side yet either.
`warn_on!()` is available on the current base and would give us the
backtrace. Would you prefer using that here for now?
> I think we can remove this warning. Skipping put_unused_fd isn't actually
> leaking anything as the files_struct is cleaned up.
Agreed. I'll treat dropping the reservation after fd table teardown as
normal cleanup, skip `put_unused_fd()`, and document that this can happen.
Best,
George
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] rust: file: handle fd table teardown in file descriptor APIs
2026-09-22 18:30 ` Georgios Androutsopoulos
@ 2026-09-22 19:55 ` Gary Guo
0 siblings, 0 replies; 5+ messages in thread
From: Gary Guo @ 2026-09-22 19:55 UTC (permalink / raw)
To: Georgios Androutsopoulos, Gary Guo
Cc: Alexander Viro, Christian Brauner, Miguel Ojeda, Jan Kara,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
linux-fsdevel, rust-for-linux, linux-kernel
On Tue Sep 22, 2026 at 7:30 PM BST, Georgios Androutsopoulos wrote:
> On Sun, Sep 20, 2026 at 4:48 PM Gary Guo <gary@garyguo.net> wrote:
>> 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/).
>
> I checked current `linux-next` and `rust-next`, but this does not seem to
> have landed yet. Should I leave these as regular checks for now and add a
> `TODO:` to use `unlikely()` once it is available?
Regular checks are fine, no need for TODO either. Just bring this up to nudge
Miguel to pick the patch :)
>
>> 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.
>
> `WARN_ONCE` does not seem to be available on the Rust side yet either.
> `warn_on!()` is available on the current base and would give us the
> backtrace. Would you prefer using that here for now?
I would prefer `warn_on!()`.
Best,
Gary
>
>> I think we can remove this warning. Skipping put_unused_fd isn't actually
>> leaking anything as the files_struct is cleaned up.
>
> Agreed. I'll treat dropping the reservation after fd table teardown as
> normal cleanup, skip `put_unused_fd()`, and document that this can happen.
>
> Best,
> George
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] rust: file: handle fd table teardown in file descriptor APIs
2026-09-20 20:48 ` Gary Guo
2026-09-22 18:30 ` Georgios Androutsopoulos
@ 2026-09-25 15:15 ` Christian Brauner
1 sibling, 0 replies; 5+ messages in thread
From: Christian Brauner @ 2026-09-25 15:15 UTC (permalink / raw)
To: Gary Guo
Cc: Georgios Androutsopoulos, Alexander Viro, Miguel Ojeda, Jan Kara,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
linux-fsdevel, rust-for-linux, linux-kernel
On Sun, Sep 20, 2026 at 09:48:54PM +0100, Gary Guo wrote:
> 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"?
It is a bug to call fd_install() or anything like that post
exit_files(). Whatever code is doing that needs to get fixed.
Similarly it is a bug to call fd_install() on kernel thread which do not
have files at all. So whoever you're doing this for is breaching core
api assumptions big time and I would like a detailed explanation
otherwise I see no reason to merge this.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-25 15:15 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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
2026-09-22 18:30 ` Georgios Androutsopoulos
2026-09-22 19:55 ` Gary Guo
2026-09-25 15:15 ` Christian Brauner
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®