* fs_put_dax() vs. dax_holder_notify_failure() race
@ 2026-09-30 14:29 Miklos Szeredi
2026-09-30 15:35 ` Darrick J. Wong
0 siblings, 1 reply; 2+ messages in thread
From: Miklos Szeredi @ 2026-09-30 14:29 UTC (permalink / raw)
To: Vishal Verma, Dave Jiang, Alison Schofield, nvdimm, linux-cxl
Cc: fuse-devel, Carlos Maiolino, linux-xfs, linux-kernel, John Groves
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.
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 <mszeredi@redhat.com>
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;
> +}
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: fs_put_dax() vs. dax_holder_notify_failure() race
2026-09-30 14:29 fs_put_dax() vs. dax_holder_notify_failure() race Miklos Szeredi
@ 2026-09-30 15:35 ` Darrick J. Wong
0 siblings, 0 replies; 2+ messages in thread
From: Darrick J. Wong @ 2026-09-30 15:35 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Vishal Verma, Dave Jiang, Alison Schofield, nvdimm, linux-cxl,
fuse-devel, Carlos Maiolino, linux-xfs, linux-kernel,
John Groves
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 <mszeredi@redhat.com>
>
> 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;
> > +}
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-30 15:35 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 14:29 fs_put_dax() vs. dax_holder_notify_failure() race Miklos Szeredi
2026-09-30 15:35 ` Darrick J. Wong
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®