[PATCH] fanotify: report names longer than NAME_MAX

From: Matthias Goergens

Date: Fri Sep 25 2026 - 22:08:51 EST


fanotify keeps the length of a reported name in a u8, so it drops any
name longer than NAME_MAX and warns:

WARNING: fs/notify/fanotify/fanotify.h:217 at fanotify_handle_event+0x2e82/0x39f0

Such names are legitimate on some filesystems. vfat mounted with utf8
takes up to 255 UTF-16 characters, which are up to 765 bytes of UTF-8,
and FUSE accepts names up to PATH_MAX - 1. readdir() and inotify report
them in full. fanotify sends the event without the name, and for
FAN_RENAME it trips a second warning in copy_fid_info_to_user(), after
which read() fails with EFAULT and the event is lost.

When syzbot hit this, Jan suggested accommodating names up to PATH_MAX
[1], the limit readdir() already applies to a name
(verify_dirent_name()). Store the name lengths as u16, which keeps
struct fanotify_info at 8 bytes, and drop without a warning only names
of PATH_MAX bytes or more, which no path can refer to. An event with
such a name can exceed a page, so allocate name events with kvmalloc(),
as Jan also suggested. Names up to NAME_MAX are reported exactly as
before, and the record format is unchanged: an event with a long name is
just longer.

Link: https://lore.kernel.org/all/20241112115059.ecvomkalee5m4i4i@quack3/ [1]
Fixes: 2d9374f09513 ("fanotify: use macros to get the offset to fanotify_info buffer")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>
Link: https://patch.msgid.link/20260926020851.2938961-1-matthias.goergens@xxxxxxxxx
Signed-off-by: Jan Kara <jack@xxxxxxx>
---
fs/notify/fanotify/fanotify.c | 7 +++++--
fs/notify/fanotify/fanotify.h | 21 ++++++++++++---------
2 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index a208a7ec1692..71b3a2361929 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -648,6 +648,9 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *dir,
unsigned long name2_len = name2 ? name2->len : 0;
unsigned int len, size;

+ if (WARN_ON_ONCE(name_len >= PATH_MAX || name2_len >= PATH_MAX))
+ return NULL;
+
/* Reserve terminating null byte even for empty name */
size = sizeof(*fne) + name_len + name2_len + 2;
if (dir_fh_len)
@@ -656,7 +659,7 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *dir,
size += FANOTIFY_FH_HDR_LEN + dir2_fh_len;
if (child_fh_len)
size += FANOTIFY_FH_HDR_LEN + child_fh_len;
- fne = kmalloc(size, gfp);
+ fne = kvmalloc(size, gfp);
if (!fne)
return NULL;

@@ -1050,7 +1053,7 @@ static void fanotify_free_fid_event(struct fanotify_event *event)

static void fanotify_free_name_event(struct fanotify_event *event)
{
- kfree(FANOTIFY_NE(event));
+ kvfree(FANOTIFY_NE(event));
}

static void fanotify_free_error_event(struct fsnotify_group *group,
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index 3710543dbf82..87ba788574bf 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -43,9 +43,16 @@ struct fanotify_info {
u8 dir_fh_totlen;
u8 dir2_fh_totlen;
u8 file_fh_totlen;
- u8 name_len;
- u8 name2_len;
- u8 pad[3];
+ u8 pad;
+ /*
+ * Deliberately not limited to NAME_MAX: some filesystems return longer
+ * names (vfat up to 255 UTF-16 characters, as Windows allows, which is
+ * up to 765 bytes of UTF-8; FUSE up to PATH_MAX - 1), and readdir() and
+ * inotify report them in full. Only names of PATH_MAX bytes or more,
+ * which no path can refer to, are dropped.
+ */
+ u16 name_len;
+ u16 name2_len;
unsigned char buf[];
/*
* (struct fanotify_fh) dir_fh starts at buf[0]
@@ -168,7 +175,7 @@ static inline char *fanotify_info_name2(struct fanotify_info *info)
static inline void fanotify_info_init(struct fanotify_info *info)
{
BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN + MAX_HANDLE_SZ > U8_MAX);
- BUILD_BUG_ON(NAME_MAX > U8_MAX);
+ BUILD_BUG_ON(PATH_MAX > U16_MAX);

info->dir_fh_totlen = 0;
info->dir2_fh_totlen = 0;
@@ -214,8 +221,7 @@ static inline void fanotify_info_set_file_fh(struct fanotify_info *info,
static inline void fanotify_info_copy_name(struct fanotify_info *info,
const struct qstr *name)
{
- if (WARN_ON_ONCE(name->len > NAME_MAX) ||
- WARN_ON_ONCE(info->name2_len > 0))
+ if (WARN_ON_ONCE(info->name2_len > 0))
return;

info->name_len = name->len;
@@ -225,9 +231,6 @@ static inline void fanotify_info_copy_name(struct fanotify_info *info,
static inline void fanotify_info_copy_name2(struct fanotify_info *info,
const struct qstr *name)
{
- if (WARN_ON_ONCE(name->len > NAME_MAX))
- return;
-
info->name2_len = name->len;
strscpy(fanotify_info_name2(info), name->name, name->len + 1);
}
--
2.51.0


--7ki65oeszutl43qt--