[PATCH] fuse: don't install DAX state when CONFIG_FS_DAX is disabled
From: Amal Rockz
Date: Tue Nov 03 2026 - 12:07:51 EST
fuse_init_inode() installs fuse_dax_aops whenever the server sets
FUSE_ATTR_DAX in a LOOKUP reply:
if ((attr->flags & FUSE_ATTR_DAX) && !fuse_inode_vdax(inode)) {
inode->i_flags |= S_DAX;
inode->i_data.a_ops = &fuse_dax_aops;
}
fuse_dax_aops only provides ->dirty_folio(). The rest of the workflow
relies on IS_DAX() to keep such inodes away from the page cache:
fuse_file_io_open() refuses to open a DAX inode unless it is opened in
passthrough mode with a DAX backing, and the truncate, fallocate and
fsync paths reject DAX inodes explicitly.
Without CONFIG_FS_DAX both S_DAX and IS_DAX() are 0, so the assignment
above still replaces the address space operations but the inode is never
marked. Every IS_DAX() check is then dead code, a DAX inode can be
opened and read through the page cache, and the first read reaches
filemap_read() -> filemap_get_pages() -> do_page_cache_ra()
-> page_cache_ra_unbounded() -> read_pages()
which calls the missing ->read_folio() and jumps to NULL:
BUG: kernel NULL pointer dereference, address: 0000000000000000
#PF: supervisor instruction fetch in kernel mode
RIP: 0010:0x0
Call Trace:
read_pages
page_cache_ra_unbounded
do_page_cache_ra
filemap_get_pages
filemap_read
vfs_read
ksys_read
FUSE_ATTR_DAX is a server-controlled protocol field that is not
validated against any capability the kernel advertised, so an
unprivileged FUSE mount can crash a kernel built without CONFIG_FS_DAX,
such as x86_64 defconfig. Reproduced as uid 1000 inside an unprivileged
user namespace.
Do not install the DAX state when DAX support is not compiled in. The
flag is then ignored and the inode keeps the regular address space
operations.
The bug was found and the fix prepared with the help of an AI coding
assistant. The reproducer was written and run against both the
unpatched and patched kernels, with CONFIG_FS_DAX=n and with
CONFIG_FS_DAX=y.
Fixes: 2fbe4586cff3 ("fuse: add support for opening dax device as backing")
Signed-off-by: Amal Rockz <amalrockz568@xxxxxxxxx>
---
fs/fuse/inode.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 1341b681d0ed..cf5fefb83e20 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -418,7 +418,13 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr,
fuse_init_common(inode);
fuse_init_file_inode(inode, attr->flags);
- if ((attr->flags & FUSE_ATTR_DAX) && !fuse_inode_vdax(inode)) {
+ /*
+ * S_DAX and IS_DAX() compile to 0 without CONFIG_FS_DAX, so
+ * installing fuse_dax_aops there would only strip the regular
+ * address space operations without marking the inode.
+ */
+ if (IS_ENABLED(CONFIG_FS_DAX) &&
+ (attr->flags & FUSE_ATTR_DAX) && !fuse_inode_vdax(inode)) {
inode->i_flags |= S_DAX;
inode->i_data.a_ops = &fuse_dax_aops;
}
--
2.56.0