CVE-2026-46150
Description
In the Linux kernel, the following vulnerability has been resolved:
fanotify: fix false positive on permission events
fsnotify_get_mark_safe() may return false for a mark on an unrelated group, which results in bypassing the permission check.
Fix by skipping over detached marks that are not in the current group.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A fanotify flaw in the Linux kernel can bypass permission checks when a mark belongs to an unrelated group, enabling unauthorized access.
Vulnerability
A false positive in fsnotify_get_mark_safe() in the Linux kernel's fanotify subsystem may return false for a mark on an unrelated group, causing permission events (such as those monitoring file access) to be bypassed. The bug exists in kernel versions prior to the fix commit [1]; the specific mark was not properly verified to belong to the current notification group, allowing detached marks to incorrectly skip the permission check.
Exploitation
An attacker must be able to trigger fanotify events on a system where a mark is placed on an unrelated group. No special authentication is required beyond normal user access to set up fanotify watches. The race condition involves the mark being detached from the expected group, causing fsnotify_get_mark_safe() to return a false negative, and thus the permission event is not delivered to the correct monitoring group.
Impact
Successful exploitation results in the bypass of fanotify permission checks, potentially allowing an attacker to access files or resources that should have been denied by the fanotify monitor. This can lead to information disclosure or violation of access control policies set by administrators.
Mitigation
The fix was committed to the Linux kernel stable tree in commit f130790f1acc8399f32652846c875a251efd040f [1]. Users should update to a kernel version containing this commit. No workarounds are available; the fix ensures that marks are only processed if they belong to the current group.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
10895ebbedf883fanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 82602157bcc0f7..7da224a0ae7ce4 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -398,7 +398,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index 4be6e883d492f6..b419a5ccf192df 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -380,9 +380,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -423,15 +420,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 575415b5134972..9bc585a29b78cc 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -817,6 +817,7 @@ static inline void fsnotify_clear_sb_marks_by_group(struct fsnotify_group *group } extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
f130790f1accfanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index c3797386e08b8f..f9b58a201fa3b9 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -421,7 +421,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index 4981439e62092a..09e50fe5757c48 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -446,9 +446,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -489,15 +486,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 3ecf7768e57794..2343136c4e9d83 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -847,6 +847,7 @@ static inline void fsnotify_clear_sb_marks_by_group(struct fsnotify_group *group } extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
7746e3bd4cc1fanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 9995de1710e596..b646a861a84c64 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -388,7 +388,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index c2ed5b11b0fe63..622f05977f86ac 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -457,9 +457,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -500,15 +497,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 95985400d3d8e2..e5cde39d6e85d6 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -915,6 +915,7 @@ extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group, unsigned int obj_type); extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
7baa02b0ae9dfanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 63dd44931989d4..cae0adce6d47a2 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -444,7 +444,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index 55a03bb05aa118..cedd84afbede53 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -453,9 +453,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -496,15 +493,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 0d954ea7b17960..4affacb909ae27 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -912,6 +912,7 @@ extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group, unsigned int obj_type); extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
b7b24b28c8cdfanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 9995de1710e596..b646a861a84c64 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -388,7 +388,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index c2ed5b11b0fe63..622f05977f86ac 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -457,9 +457,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -500,15 +497,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 95985400d3d8e2..e5cde39d6e85d6 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -915,6 +915,7 @@ extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group, unsigned int obj_type); extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
7746e3bd4cc1fanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 9995de1710e596..b646a861a84c64 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -388,7 +388,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index c2ed5b11b0fe63..622f05977f86ac 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -457,9 +457,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -500,15 +497,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 95985400d3d8e2..e5cde39d6e85d6 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -915,6 +915,7 @@ extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group, unsigned int obj_type); extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
7baa02b0ae9dfanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 63dd44931989d4..cae0adce6d47a2 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -444,7 +444,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index 55a03bb05aa118..cedd84afbede53 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -453,9 +453,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -496,15 +493,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 0d954ea7b17960..4affacb909ae27 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -912,6 +912,7 @@ extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group, unsigned int obj_type); extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
895ebbedf883fanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 82602157bcc0f7..7da224a0ae7ce4 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -398,7 +398,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index 4be6e883d492f6..b419a5ccf192df 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -380,9 +380,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -423,15 +420,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 575415b5134972..9bc585a29b78cc 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -817,6 +817,7 @@ static inline void fsnotify_clear_sb_marks_by_group(struct fsnotify_group *group } extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
b7b24b28c8cdfanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 9995de1710e596..b646a861a84c64 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -388,7 +388,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index c2ed5b11b0fe63..622f05977f86ac 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -457,9 +457,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -500,15 +497,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 95985400d3d8e2..e5cde39d6e85d6 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -915,6 +915,7 @@ extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group, unsigned int obj_type); extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
f130790f1accfanotify: fix false positive on permission events
3 files changed · +13 −9
fs/notify/fsnotify.c+1 −1 modifieddiff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index c3797386e08b8f..f9b58a201fa3b9 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -421,7 +421,7 @@ static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector return hlist_entry_safe(node, struct fsnotify_mark, obj_list); } -static struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark) { struct hlist_node *node = NULL;
fs/notify/mark.c+11 −7 modifieddiff --git a/fs/notify/mark.c b/fs/notify/mark.c index 4981439e62092a..09e50fe5757c48 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -446,9 +446,6 @@ EXPORT_SYMBOL_GPL(fsnotify_put_mark); */ static bool fsnotify_get_mark_safe(struct fsnotify_mark *mark) { - if (!mark) - return true; - if (refcount_inc_not_zero(&mark->refcnt)) { spin_lock(&mark->lock); if (mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED) { @@ -489,15 +486,22 @@ bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) int type; fsnotify_foreach_iter_type(type) { + struct fsnotify_mark *mark = iter_info->marks[type]; + /* This can fail if mark is being removed */ - if (!fsnotify_get_mark_safe(iter_info->marks[type])) { - __release(&fsnotify_mark_srcu); - goto fail; + while (mark && !fsnotify_get_mark_safe(mark)) { + if (mark->group == iter_info->current_group) { + __release(&fsnotify_mark_srcu); + goto fail; + } + /* This is a mark in an unrelated group, skip */ + mark = fsnotify_next_mark(mark); + iter_info->marks[type] = mark; } } /* - * Now that both marks are pinned by refcount in the inode / vfsmount + * Now that all marks are pinned by refcount in the inode / vfsmount / etc * lists, we can drop SRCU lock, and safely resume the list iteration * once userspace returns. */
include/linux/fsnotify_backend.h+1 −1 modifieddiff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 3ecf7768e57794..2343136c4e9d83 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -847,6 +847,7 @@ static inline void fsnotify_clear_sb_marks_by_group(struct fsnotify_group *group } extern void fsnotify_get_mark(struct fsnotify_mark *mark); extern void fsnotify_put_mark(struct fsnotify_mark *mark); +struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark); extern void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info); extern bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info); -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"In `fsnotify_prepare_user_wait`, when `fsnotify_get_mark_safe()` returns false for a mark that belongs to an unrelated group, the old code immediately treated this as a failure and released the SRCU lock, causing the permission check for the current group's mark to be bypassed entirely."
Attack vector
An attacker can trigger this bug by racing mark removal on an unrelated fanotify group while a permission event is being processed. When `fsnotify_get_mark_safe()` fails on a mark from a different group (e.g. because that mark is being detached), the old code in `fsnotify_prepare_user_wait` would drop the SRCU lock and bail out, skipping the permission check for the current group's mark. This allows the attacker to bypass the fanotify permission event and perform an otherwise-denied filesystem operation. The bug is reachable from any process that can cause filesystem operations on an inode or mount that has both the victim's fanotify mark and an unrelated mark being concurrently removed [patch_id=2898265].
Affected code
The vulnerable code is in `fs/notify/mark.c` in the function `fsnotify_prepare_user_wait()`. The helper `fsnotify_get_mark_safe()` in the same file also contributed to the bug by returning `true` for a NULL mark pointer. The fix also touches `fs/notify/fsnotify.c` (making `fsnotify_next_mark` non-static) and `include/linux/fsnotify_backend.h` (declaring the new export) [patch_id=2898265].
What the fix does
The patch modifies `fsnotify_prepare_user_wait` in `fs/notify/mark.c` to loop over marks when `fsnotify_get_mark_safe()` fails. Instead of immediately failing, it checks whether the failing mark belongs to the current group; if so, it still fails (the mark is genuinely gone). If the mark belongs to an unrelated group, it skips to the next mark via `fsnotify_next_mark()` and continues. This required making `fsnotify_next_mark()` non-static (exported in `include/linux/fsnotify_backend.h`) so it can be called from `mark.c`. The early-return `if (!mark) return true` in `fsnotify_get_mark_safe()` was also removed because the caller now handles NULL marks via the `while (mark && ...)` loop condition [patch_id=2898265].
Preconditions
- inputThe attacker must be able to trigger filesystem operations on an inode or mount that has both a victim's fanotify permission mark and at least one other fanotify mark from a different group.
- configA concurrent mark removal on the unrelated group must race with the permission event processing.
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/7746e3bd4cc19b5092e00d32d676e329bfcb6900nvd
- git.kernel.org/stable/c/7baa02b0ae9d17ec5f08836d8ea88ce1927d0678nvd
- git.kernel.org/stable/c/895ebbedf88318607c24acc0f591c74b165e1d0anvd
- git.kernel.org/stable/c/b7b24b28c8cd55844cab908f4f39dded638d5538nvd
- git.kernel.org/stable/c/f130790f1acc8399f32652846c875a251efd040fnvd
News mentions
0No linked articles in our index yet.