2023-07-20 16:01:11

by Alice Ryhl

[permalink] [raw]
Subject: [RFC PATCH v1 5/5] rust: file: add `DeferredFdCloser`

This adds a new type called `DeferredFdCloser` that can be used to close
files by their fd in a way that is safe even if the file is currently
held using `fdget`.

This is done by grabbing an extra refcount to the file and dropping it
in a task work once we return to userspace.

See comments on `binder_do_fd_close` and commit `80cd795630d65` for
motivation.

Signed-off-by: Alice Ryhl <[email protected]>
---
This is an implementation of `binder_deferred_fd_close` in Rust.

I think the fact that binder needs to close fds in this way raises the
question of how we want the Rust APIs for closing files to look.
Apparently, fdget is not just used in easily reviewable regions, but
also around things like the ioctl syscall, meaning that all ioctls must
abide by the fdget safety requirements.

rust/bindings/bindings_helper.h | 2 +
rust/helpers.c | 7 +++
rust/kernel/file.rs | 80 ++++++++++++++++++++++++++++++++-
3 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/file.rs b/rust/kernel/file.rs
index 7281264cbaa1..9b1f4efdf7ac 100644
--- a/rust/kernel/file.rs
+++ b/rust/kernel/file.rs
@@ -11,7 +11,8 @@
error::{code::*, Error, Result},
types::{ARef, AlwaysRefCounted, Opaque},
};
-use core::{marker::PhantomData, ptr};
+use alloc::boxed::Box;
+use core::{alloc::AllocError, marker::PhantomData, mem, ptr};

mod poll_table;
pub use self::poll_table::{PollCondVar, PollTable};
@@ -241,6 +242,83 @@ fn drop(&mut self) {
}
}

+/// Helper used for closing file descriptors in a way that is safe even if the file is currently
+/// held using `fdget`.
+///
+/// See comments on `binder_do_fd_close` and commit `80cd795630d65`.
+pub struct DeferredFdCloser {
+ inner: Box<DeferredFdCloserInner>,
+}
+
+/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with
+/// moving it across threads.
+unsafe impl Send for DeferredFdCloser {}
+unsafe impl Sync for DeferredFdCloser {}
+
+#[repr(C)]
+struct DeferredFdCloserInner {
+ twork: mem::MaybeUninit<bindings::callback_head>,
+ file: *mut bindings::file,
+}
+
+impl DeferredFdCloser {
+ /// Create a new `DeferredFdCloser`.
+ pub fn new() -> Result<Self, AllocError> {
+ Ok(Self {
+ inner: Box::try_new(DeferredFdCloserInner {
+ twork: mem::MaybeUninit::uninit(),
+ file: core::ptr::null_mut(),
+ })?,
+ })
+ }
+
+ /// Schedule a task work that closes the file descriptor when this task returns to userspace.
+ pub fn close_fd(mut self, fd: u32) {
+ let file = unsafe { bindings::close_fd_get_file(fd) };
+ if !file.is_null() {
+ self.inner.file = file;
+
+ // SAFETY: Since DeferredFdCloserInner is `#[repr(C)]`, casting the pointers gives a
+ // pointer to the `twork` field.
+ let inner = Box::into_raw(self.inner) as *mut bindings::callback_head;
+
+ // SAFETY: Getting a pointer to current is always safe.
+ let current = unsafe { bindings::get_current() };
+ // SAFETY: The `file` pointer points at a valid file.
+ unsafe { bindings::get_file(file) };
+ // SAFETY: Due to the above `get_file`, even if the current task holds an `fdget` to
+ // this file right now, the refcount will not drop to zero until after it is released
+ // with `fdput`. This is because when using `fdget`, you must always use `fdput` before
+ // returning to userspace, and our task work runs after any `fdget` users have returned
+ // to user space.
+ //
+ // Note: fl_owner_t is currently a void pointer.
+ unsafe { bindings::filp_close(file, current as bindings::fl_owner_t) };
+ // SAFETY: The `inner` pointer is compatible with the `do_close_fd` method.
+ //
+ // The call to `task_work_add` can't fail, because we are scheduling the task work to
+ // the current task.
+ unsafe {
+ bindings::init_task_work(inner, Some(Self::do_close_fd));
+ bindings::task_work_add(current, inner, bindings::task_work_notify_mode_TWA_RESUME);
+ }
+ } else {
+ // Free the allocation.
+ drop(self.inner);
+ }
+ }
+
+ unsafe extern "C" fn do_close_fd(inner: *mut bindings::callback_head) {
+ // SAFETY: In `close_fd` we use this method together with a pointer that originates from a
+ // `Box<DeferredFdCloserInner>`, and we have just been given ownership of that allocation.
+ let inner = unsafe { Box::from_raw(inner as *mut DeferredFdCloserInner) };
+ // SAFETY: This drops a refcount we acquired in `close_fd`.
+ unsafe { bindings::fput(inner.file) };
+ // Free the allocation.
+ drop(inner);
+ }
+}
+
/// Represents the EBADF error code.
///
/// Used for methods that can only fail with EBADF.
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 7d83e1a7a362..6d0d044fa8cd 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -8,6 +8,7 @@

#include <linux/cred.h>
#include <linux/errname.h>
+#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/poll.h>
@@ -16,6 +17,7 @@
#include <linux/refcount.h>
#include <linux/wait.h>
#include <linux/sched.h>
+#include <linux/task_work.h>

/* `bindgen` gets confused at certain things. */
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
diff --git a/rust/helpers.c b/rust/helpers.c
index e13a7da430b1..d147ec5bc0a3 100644
--- a/rust/helpers.c
+++ b/rust/helpers.c
@@ -31,6 +31,7 @@
#include <linux/sched/signal.h>
#include <linux/security.h>
#include <linux/spinlock.h>
+#include <linux/task_work.h>
#include <linux/wait.h>

__noreturn void rust_helper_BUG(void)
@@ -166,6 +167,12 @@ void rust_helper_security_cred_getsecid(const struct cred *c, u32 *secid)
EXPORT_SYMBOL_GPL(rust_helper_security_cred_getsecid);
#endif

+void rust_helper_init_task_work(struct callback_head *twork, task_work_func_t func)
+{
+ init_task_work(twork, func);
+}
+EXPORT_SYMBOL_GPL(rust_helper_init_task_work);
+
/*
* We use `bindgen`'s `--size_t-is-usize` option to bind the C `size_t` type
* as the Rust `usize` type, so we can use it in contexts where Rust
--
2.41.0.255.g8b1d071c50-goog



2023-07-20 18:39:48

by Miguel Ojeda

[permalink] [raw]
Subject: Re: [RFC PATCH v1 5/5] rust: file: add `DeferredFdCloser`

Hi Alice,

A quick comment on referencing commits:

On Thu, Jul 20, 2023 at 5:29 PM Alice Ryhl <[email protected]> wrote:
>
> See comments on `binder_do_fd_close` and commit `80cd795630d65` for
> motivation.

The convention is to write these commit references like this:

commit 80cd795630d6 ("binder: fix use-after-free due to
ksys_close() during fdget()")

I recommend generating them with a Git pretty format -- see the config
in the bottom part of the section at
https://docs.kernel.org/process/submitting-patches.html#describe-your-changes.

Also, given it is a kernel convention, please avoid the Markdown
backticks in this case.

> +/// See comments on `binder_do_fd_close` and commit `80cd795630d65`.

Same here, i.e. in comments and documentation too (and emails too,
especially if not referenced elsewhere).

While I am at it, a few other notes below too I noticed:

> + /// Create a new `DeferredFdCloser`.

[`DeferredFdCloser`]

> + /// Schedule a task work that closes the file descriptor when this task returns to userspace.
> + pub fn close_fd(mut self, fd: u32) {
> + let file = unsafe { bindings::close_fd_get_file(fd) };
> + if !file.is_null() {

Please use the early return style here, if possible, to unindent all this.

> + // SAFETY: Since DeferredFdCloserInner is `#[repr(C)]`, casting the pointers gives a

`DeferredFdCloserInner`

> + // Note: fl_owner_t is currently a void pointer.

`fl_owner_t`

> + // SAFETY: The `inner` pointer is compatible with the `do_close_fd` method.
> + //
> + // The call to `task_work_add` can't fail, because we are scheduling the task work to
> + // the current task.
> + unsafe {
> + bindings::init_task_work(inner, Some(Self::do_close_fd));
> + bindings::task_work_add(current, inner, bindings::task_work_notify_mode_TWA_RESUME);
> + }

Should this block be split?

> /// Represents the EBADF error code.
> ///
> /// Used for methods that can only fail with EBADF.

Doclink them if possible; otherwise `EBADF`.

Cheers,
Miguel

Subject: Re: [RFC PATCH v1 5/5] rust: file: add `DeferredFdCloser`

On 7/20/23 12:28, Alice Ryhl wrote:
> This adds a new type called `DeferredFdCloser` that can be used to close
> files by their fd in a way that is safe even if the file is currently
> held using `fdget`.
>
> This is done by grabbing an extra refcount to the file and dropping it
> in a task work once we return to userspace.
>
> See comments on `binder_do_fd_close` and commit `80cd795630d65` for
> motivation.

Please provide links, at least for the doc comment.

>
> Signed-off-by: Alice Ryhl <[email protected]>
> ---
> [...]
>
> +/// Helper used for closing file descriptors in a way that is safe even if the file is currently
> +/// held using `fdget`.
> +///
> +/// See comments on `binder_do_fd_close` and commit `80cd795630d65`.

Ditto.

> +pub struct DeferredFdCloser {
> + inner: Box<DeferredFdCloserInner>,
> +}
> +
> [...]

2023-08-09 10:31:49

by Miguel Ojeda

[permalink] [raw]
Subject: Re: [RFC PATCH v1 5/5] rust: file: add `DeferredFdCloser`

On Wed, Aug 9, 2023 at 11:00 AM Miguel Ojeda
<[email protected]> wrote:
>
> If you mean for the commit, then we should follow the kernel
> convention instead. Please see my reply to Alice above.

One extra note: if it is a external repository, then yes, I would
definitely recommend adding a `Link` because readers may not be able
to easily `git show` the hash. But if it is the kernel tree, then it
is not needed.

Cheers,
Miguel

2023-08-09 12:06:42

by Miguel Ojeda

[permalink] [raw]
Subject: Re: [RFC PATCH v1 5/5] rust: file: add `DeferredFdCloser`

On Wed, Aug 9, 2023 at 6:34 AM Martin Rodriguez Reboredo
<[email protected]> wrote:
>
> Please provide links, at least for the doc comment.

If you mean for the commit, then we should follow the kernel
convention instead. Please see my reply to Alice above.

> Ditto.

For code, we are also using the same convention.

Cheers,
Miguel

Subject: Re: [RFC PATCH v1 5/5] rust: file: add `DeferredFdCloser`

On 8/9/23 06:09, Miguel Ojeda wrote:
> On Wed, Aug 9, 2023 at 11:00 AM Miguel Ojeda
> <[email protected]> wrote:
>>
>> If you mean for the commit, then we should follow the kernel
>> convention instead. Please see my reply to Alice above.
>
> One extra note: if it is a external repository, then yes, I would
> definitely recommend adding a `Link` because readers may not be able
> to easily `git show` the hash. But if it is the kernel tree, then it
> is not needed.
>
> Cheers,
> Miguel

Gotcha

-> Martin