* [PATCH 0/3] fuse, fsnotify: handle embedded NUL names safely
@ 2026-11-15 21:09 Junyoung Jang
2026-11-15 21:09 ` [PATCH 1/3] fuse: reject embedded NULs in readdir entry names Junyoung Jang
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Junyoung Jang @ 2026-11-15 21:09 UTC (permalink / raw)
To: Miklos Szeredi, Jan Kara
Cc: Junyoung Jang, Amir Goldstein, Matt Bobrowski, Gwanhyun Lee,
fuse-devel, linux-fsdevel, linux-kernel
FUSE readdir and readdirplus parse dirent->namelen without checking for
embedded NUL bytes. Through READDIRPLUS (fuse_direntplus_link()), such a
name can instantiate or alias a dentry in dcache whose qstr length
extends past the first NUL.
When fsnotify records that dentry name, both inotify and fanotify size
the event buffer from qstr->len and copy that full length to userspace,
but populate the buffer with strscpy(). Because strscpy() stops at the
embedded NUL, the trailing heap bytes remain uninitialized.
Patch 1 rejects dirent names containing embedded NULs in FUSE.
Patch 2 zero-pads inotify event names through their recorded length.
Patch 3 zero-pads fanotify event names through their recorded lengths.
Each patch applies and builds independently on mainline commit
23c62304dde2, so the FUSE and fsnotify changes can go through their
respective trees separately.
Testing on x86_64 QEMU:
- 1/3: Crafted FUSE READDIR and READDIRPLUS entries with embedded NUL
names were accepted before the patch and rejected with -EIO
afterward.
- 2/3: With Patch 1 omitted, FUSE READDIRPLUS + inotify leaked stale
slab bytes past the embedded NUL to userspace before the patch,
and returned zeroes afterward.
- 3/3: Default FUSE mounts report a zero fsid, so fanotify_mark() with
FAN_REPORT_DFID_NAME returns -ENODEV on the FUSE setup;
regression-tested FAN_REPORT_DFID_NAME create and FAN_RENAME
events on tmpfs.
A custom LLM agent assisted with initial discovery and reproducer
setup; all root-cause analysis, patches, and runtime tests were
manually reviewed and verified by the authors.
Junyoung Jang (3):
fuse: reject embedded NULs in readdir entry names
inotify: zero-pad event names after an embedded NUL
fanotify: zero-pad copied event names after an embedded NUL
fs/fuse/readdir.c | 12 +++++++++---
fs/notify/fanotify/fanotify.h | 4 ++--
fs/notify/inotify/inotify_fsnotify.c | 2 +-
3 files changed, 12 insertions(+), 6 deletions(-)
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/3] fuse: reject embedded NULs in readdir entry names
2026-11-15 21:09 [PATCH 0/3] fuse, fsnotify: handle embedded NUL names safely Junyoung Jang
@ 2026-11-15 21:09 ` Junyoung Jang
2026-11-15 21:09 ` [PATCH 2/3] inotify: zero-pad event names after an embedded NUL Junyoung Jang
2026-11-15 21:09 ` [PATCH 3/3] fanotify: zero-pad copied " Junyoung Jang
2 siblings, 0 replies; 4+ messages in thread
From: Junyoung Jang @ 2026-11-15 21:09 UTC (permalink / raw)
To: Miklos Szeredi, Jan Kara
Cc: Junyoung Jang, Amir Goldstein, Matt Bobrowski, Gwanhyun Lee,
fuse-devel, linux-fsdevel, linux-kernel, stable
parse_dirfile() and parse_dirplusfile() reject '/' in dirent->name, but
do not check for embedded NUL bytes.
Unlike plain getdents() buffers covered by verify_dirent_name() in
fs/readdir.c, FUSE READDIRPLUS passes dirent->namelen directly to
QSTR_INIT() in fuse_direntplus_link() and instantiates or aliases live
dentries via d_splice_alias(). A dirent name containing an embedded NUL
produces a dentry whose d_name.len extends past the NUL terminator,
which allows callers such as inotify to copy uninitialized kernel slab
memory past the NUL byte to userspace.
Reject dirent names containing NUL alongside '/' in the FUSE readdir
parsers.
Fixes: 0b05b18381ee ("fuse: implement NFS-like readdirplus support")
Cc: stable@vger.kernel.org
Co-developed-by: Gwanhyun Lee <gwanhyunlee@gmail.com>
Signed-off-by: Gwanhyun Lee <gwanhyunlee@gmail.com>
Signed-off-by: Junyoung Jang <graypanda.inzag@gmail.com>
---
fs/fuse/readdir.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index 1f6cf565477f..e8b47c2a9103 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -126,6 +126,12 @@ static bool fuse_emit(struct file *file, struct dir_context *ctx,
dirent->type | FILLDIR_FLAG_NOINTR);
}
+static bool fuse_invalid_dirent_name(const struct fuse_dirent *dirent)
+{
+ return memchr(dirent->name, '/', dirent->namelen) ||
+ memchr(dirent->name, '\0', dirent->namelen);
+}
+
static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
struct dir_context *ctx)
{
@@ -136,7 +142,7 @@ static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
return -EIO;
if (reclen > nbytes)
break;
- if (memchr(dirent->name, '/', dirent->namelen) != NULL)
+ if (fuse_invalid_dirent_name(dirent))
return -EIO;
if (!fuse_emit(file, ctx, dirent))
@@ -307,7 +313,7 @@ static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
return -EIO;
if (reclen > nbytes)
break;
- if (memchr(dirent->name, '/', dirent->namelen) != NULL)
+ if (fuse_invalid_dirent_name(dirent))
return -EIO;
if (!over) {
@@ -461,7 +467,7 @@ static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff,
return FOUND_ERR;
if (WARN_ON(reclen > nbytes))
return FOUND_ERR;
- if (WARN_ON(memchr(dirent->name, '/', dirent->namelen) != NULL))
+ if (WARN_ON(fuse_invalid_dirent_name(dirent)))
return FOUND_ERR;
if (ff->readdir.pos == ctx->pos) {
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/3] inotify: zero-pad event names after an embedded NUL
2026-11-15 21:09 [PATCH 0/3] fuse, fsnotify: handle embedded NUL names safely Junyoung Jang
2026-11-15 21:09 ` [PATCH 1/3] fuse: reject embedded NULs in readdir entry names Junyoung Jang
@ 2026-11-15 21:09 ` Junyoung Jang
2026-11-15 21:09 ` [PATCH 3/3] fanotify: zero-pad copied " Junyoung Jang
2 siblings, 0 replies; 4+ messages in thread
From: Junyoung Jang @ 2026-11-15 21:09 UTC (permalink / raw)
To: Miklos Szeredi, Jan Kara
Cc: Junyoung Jang, Amir Goldstein, Matt Bobrowski, Gwanhyun Lee,
fuse-devel, linux-fsdevel, linux-kernel, stable
inotify_handle_inode_event() allocates the variable-length event with
kmalloc() without zeroing the name buffer, sets event->name_len to
name->len, and copies name->name with strscpy().
If a dentry qstr contains an embedded NUL byte (for example, via a
crafted FUSE READDIRPLUS entry aliasing an open directory), strscpy()
stops at the first NUL while event->name_len still covers the trailing
bytes. copy_event_to_user() then copies event->name_len bytes to
userspace, leaking uninitialized slab memory from the tail of the
event allocation.
This mismatch dates back to
commit ce163918cd33 ("inotify_handle_event(): don't bother with strlen()"),
which replaced strlen(file_name->name) with file_name->len, and remained
in commit b8f2688258f8 ("inotify: Use strscpy() for event->name copies").
Switch to strscpy_pad() so any trailing bytes up to event->name_len + 1
are zeroed.
Fixes: ce163918cd33 ("inotify_handle_event(): don't bother with strlen()")
Cc: stable@vger.kernel.org
Co-developed-by: Gwanhyun Lee <gwanhyunlee@gmail.com>
Signed-off-by: Gwanhyun Lee <gwanhyunlee@gmail.com>
Signed-off-by: Junyoung Jang <graypanda.inzag@gmail.com>
---
fs/notify/inotify/inotify_fsnotify.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c
index 6e3af6452dcc..78eb1e92a14c 100644
--- a/fs/notify/inotify/inotify_fsnotify.c
+++ b/fs/notify/inotify/inotify_fsnotify.c
@@ -121,7 +121,7 @@ int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask,
event->sync_cookie = cookie;
event->name_len = len;
if (len)
- strscpy(event->name, name->name, event->name_len + 1);
+ strscpy_pad(event->name, name->name, event->name_len + 1);
ret = fsnotify_add_event(group, fsn_event, inotify_merge);
if (ret) {
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 3/3] fanotify: zero-pad copied event names after an embedded NUL
2026-11-15 21:09 [PATCH 0/3] fuse, fsnotify: handle embedded NUL names safely Junyoung Jang
2026-11-15 21:09 ` [PATCH 1/3] fuse: reject embedded NULs in readdir entry names Junyoung Jang
2026-11-15 21:09 ` [PATCH 2/3] inotify: zero-pad event names after an embedded NUL Junyoung Jang
@ 2026-11-15 21:09 ` Junyoung Jang
2 siblings, 0 replies; 4+ messages in thread
From: Junyoung Jang @ 2026-11-15 21:09 UTC (permalink / raw)
To: Miklos Szeredi, Jan Kara
Cc: Junyoung Jang, Amir Goldstein, Matt Bobrowski, Gwanhyun Lee,
fuse-devel, linux-fsdevel, linux-kernel, stable
fanotify_alloc_name_event() sizes the variable-length event buffer from
the qstr lengths and allocates it via kvmalloc() without zeroing.
fanotify_info_copy_name() and fanotify_info_copy_name2() record
name->len in info->name_len and info->name2_len, but copy the strings
with strscpy().
If a qstr contains an embedded NUL byte, strscpy() stops at the first
NUL and leaves the trailing bytes of the allocated name buffer
uninitialized. copy_fid_info_to_user() then copies name_len + 1 bytes
to userspace, leaking uninitialized kernel heap memory.
This mismatch dates back to
commit 929943b38daf ("fanotify: add support for FAN_REPORT_NAME") and
remained after commit 0fdbe845534f
("fanotify: replace deprecated strcpy in fanotify_info_copy_{name,name2}").
Switch both helpers to strscpy_pad() so any trailing bytes up to the
recorded length are zeroed.
Fixes: 929943b38daf ("fanotify: add support for FAN_REPORT_NAME")
Cc: stable@vger.kernel.org
Co-developed-by: Gwanhyun Lee <gwanhyunlee@gmail.com>
Signed-off-by: Gwanhyun Lee <gwanhyunlee@gmail.com>
Signed-off-by: Junyoung Jang <graypanda.inzag@gmail.com>
---
Note: default FUSE mounts report a zero fsid (-ENODEV on fanotify_mark()
with FAN_REPORT_DFID_NAME), so this patch was runtime-tested on tmpfs
with FAN_REPORT_DFID_NAME create and FAN_RENAME events.
fs/notify/fanotify/fanotify.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index c25cf5c1fc70..4a1f14aa4974 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -225,14 +225,14 @@ static inline void fanotify_info_copy_name(struct fanotify_info *info,
return;
info->name_len = name->len;
- strscpy(fanotify_info_name(info), name->name, name->len + 1);
+ strscpy_pad(fanotify_info_name(info), name->name, name->len + 1);
}
static inline void fanotify_info_copy_name2(struct fanotify_info *info,
const struct qstr *name)
{
info->name2_len = name->len;
- strscpy(fanotify_info_name2(info), name->name, name->len + 1);
+ strscpy_pad(fanotify_info_name2(info), name->name, name->len + 1);
}
/*
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-15 21:10 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-15 21:09 [PATCH 0/3] fuse, fsnotify: handle embedded NUL names safely Junyoung Jang
2026-11-15 21:09 ` [PATCH 1/3] fuse: reject embedded NULs in readdir entry names Junyoung Jang
2026-11-15 21:09 ` [PATCH 2/3] inotify: zero-pad event names after an embedded NUL Junyoung Jang
2026-11-15 21:09 ` [PATCH 3/3] fanotify: zero-pad copied " Junyoung Jang
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®