Re: [PATCH v5 8/9] rust: file: add `DeferredFdCloser`

From: Christian Brauner
Date: Sun Mar 31 2024 - 06:27:29 EST


On Thu, Mar 21, 2024 at 02:28:15PM +0100, Alice Ryhl wrote:
> On Wed, Mar 20, 2024 at 3:22 PM Christian Brauner <brauner@xxxxxxxxxx> wrote:
> >
> > On Fri, Feb 09, 2024 at 11:18:21AM +0000, Alice Ryhl wrote:
> > >
> > > +/// Helper used for closing file descriptors in a way that is safe even if the file is currently
> > > +/// held using `fdget`.
> > > +///
> > > +/// Additional motivation can be found in commit 80cd795630d6 ("binder: fix use-after-free due to
> > > +/// ksys_close() during fdget()") and in the comments on `binder_do_fd_close`.
> > > +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 {}
> > > +
> > > +/// # Invariants
> > > +///
> > > +/// If the `file` pointer is non-null, then it points at a `struct file` and owns a refcount to
> > > +/// that file.
> > > +#[repr(C)]
> > > +struct DeferredFdCloserInner {
> > > + twork: mem::MaybeUninit<bindings::callback_head>,
> > > + file: *mut bindings::file,
> > > +}
> > > +
> > > +impl DeferredFdCloser {
> >
> > So the explicitly deferred close is due to how binder works so it's not
> > much of a general purpose interface as I don't recall having other
> > codepaths with similar problems. So this should live in the binder
> > specific rust code imo.
>
> Hmm. Are there really no other ioctls that call ksys_close on a
> user-provided fd?

No, I don't think there are otherwise they would have the same bug that
binder had. io_uring comes closest but they have their own task work
and deferred close implementation.

>
> As far as I can tell, this kind of deferred API is the only way for us
> to provide a fully safe Rust api for closing an fd. Directly calling
> ksys_close must unsafely assert that the fd does not have an active
> fdget call. So it makes sense to me as an API that others might want
> to use.

I'm sorry, I don't quite understand you here.

What binder is doing iirc is that it's performing an ioctl() using a fd
to /dev/binderfs/$device-node or similar. The fatal flaw is that binder
now allows that fd to be closed during that ioctl - and by accident at
that. It's effectively closing a file it's relying on to not go away.
That's the original nonsense/creativity that necessitates this whole
manual task work shenanigans binder is doing. Unless I misremember.

But that's really frowned upon generally and absolutely not encouraged
by providing a generic interface for this stuff.

Sure, we have some users in the kernel that do stuff like call
close_fd() on a file descriptor they just installed into their file
descriptor table. That's usually bad design with maybe a few valid
exceptions. One example where that's still done and should be fixed is
e.g., cachefiles in fs/cachefiles/ondemand.c. The point is that they at
least close a file descriptor that they just installed and that they
don't rely on being valid.

So really, I need more details why without this interface it would
prevent Rust from implementing safe file descriptor closing because
right now all I see is a design flaw in binder being promoted to a
generic interface. And unless there's detailed reasons for it's
existence we're not going to do it.