On Wed, Sep 30, 2026 at 04:29:33PM +0200, Miklos Szeredi wrote:
> During ->notify_failure() callback will dereference holder.  It may be
> NULL at this point, resulting in an Oops, or it may be non-NULL but be
> freed during that call, resulting in UAF.
> 
> This affects xfs at this point, but the fuse extent map patchset[1]
> also adds dax failure handling and is affected by this issue.
> 
> I think this should be fixed in drivers/dax/super.c, since this will
> be a problem in each user.

Yes, I would have thought that the notification code would grab a
refcount on the dax device so that it can't go away.  Though it does
seem a little wild that there's no means to pause fs_dax_put until
all the notifications have cleared....?

--D

> Thanks,
> Miklos
> 
> [1] Report from sashiko (select Patch 8):
> 
> https://sashiko.dev/#/patchset/20260922061019.3320196-1-mszeredi%40redhat.com
> 
> Or here it is inline:
> 
> commit 1580a2f8ad3f103a20b6280aa39a01f8b0be2d3f
> Author: Miklos Szeredi <[email protected]>
> 
> fuse: add support for opening dax device as backing
> 
> Add FUSE_BACKING_IS_DEV flag that allows opening a character device
> (dax device) as a backing, in addition to regular files. Mark the
> inode with S_DAX if FUSE_LOOKUP returns with FUSE_ATTR_DAX set.
> 
> > diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c
> > index 58dbdd17c1ef4..aa558a0c2e647 100644
> > --- a/fs/fuse/backing.c
> > +++ b/fs/fuse/backing.c
> [ ... ]
> > @@ -22,9 +23,16 @@ static void fuse_backing_free(struct fuse_backing *fb)
> >  {
> >   pr_debug("%s: fb=0x%p\n", __func__, fb);
> >
> > - if (fb->file)
> > - fput(fb->file);
> > - put_cred(fb->cred);
> > + switch (fb->type) {
> > + case FUSE_BACKING_PATH:
> > + path_put(&fb->path);
> > + put_cred(fb->cred);
> > + break;
> > +
> > + case FUSE_BACKING_DAXDEV:
> > + fs_put_dax(fb->dax_dev, fb);
> > + break;
> > + }
> >   kfree_rcu(fb, rcu);
> 
> Does this code introduce a use-after-free race with a concurrent hardware
> memory failure?
> 
> When a DAX-backed FUSE backing file is closed, fuse_backing_free() drops the
> DAX device holder by calling fs_put_dax() and frees the backing object using
> normal RCU via kfree_rcu().
> 
> Concurrently, a hardware memory failure can trigger 
> dax_holder_notify_failure(),
> which protects its read-side critical section with SRCU using dax_read_lock().
> 
> If dax_holder_notify_failure() reads dax_dev->holder_ops and checks for NULL,
> and is preempted here, fs_put_dax() can run to completion. This would set
> dax_dev->holder_ops and dax_dev->holder_data to NULL, and eventually free fb
> after a normal RCU grace period.
> 
> Because kfree_rcu() does not wait for SRCU readers, the memory could be freed
> while dax_holder_notify_failure() is still running.
> 
> [ ... ]
> > @@ -79,23 +87,88 @@ static struct fuse_backing 
> > *fuse_backing_id_remove(struct fuse_conn *fc, u64 id,
> >   return fb;
> >  }
> >
> > +static int fuse_dax_notify_failure(struct dax_device *daxdev, u64 offset, 
> > u64 len, int mf_flags)
> > +{
> > + struct fuse_backing *fb = dax_holder(daxdev);
> > +
> > + fb->dax_error = true;
> 
> Can this code cause a NULL pointer dereference or use-after-free when racing
> with fuse_backing_free()?
> 
> When dax_holder_notify_failure() resumes after the race described in
> fuse_backing_free(), it blindly calls ops->notify_failure() which is this
> function.
> 
> Because fs_put_dax() may have already cleared dax_dev->holder_data,
> dax_holder() might return NULL causing a NULL pointer dereference here.
> Depending on the timing, it could also return a stale pointer to the freed
> fb object, resulting in a use-after-free.
> 
> > +
> > + return 0;
> > +}
> 

Reply via email to