VYPR
Unrated severityNVD Advisory· Published May 28, 2026

CVE-2026-46150

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

1

Patches

10
895ebbedf883

fanotify: fix false positive on permission events

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMiklos SzerediApr 10, 2026Fixed in 6.6.140via kernel-cna
3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
f130790f1acc

fanotify: fix false positive on permission events

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMiklos SzerediApr 10, 2026Fixed in 6.12.88via kernel-cna
3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
7746e3bd4cc1

fanotify: fix false positive on permission events

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMiklos SzerediApr 10, 2026Fixed in 7.1-rc2via kernel-cna
3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
7baa02b0ae9d

fanotify: fix false positive on permission events

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMiklos SzerediApr 10, 2026Fixed in 6.18.30via kernel-cna
3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
b7b24b28c8cd

fanotify: fix false positive on permission events

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMiklos SzerediApr 10, 2026Fixed in 7.0.7via kernel-cna
3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
7746e3bd4cc1

fanotify: fix false positive on permission events

3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
7baa02b0ae9d

fanotify: fix false positive on permission events

3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
895ebbedf883

fanotify: fix false positive on permission events

3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
b7b24b28c8cd

fanotify: fix false positive on permission events

3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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
    
    
    
f130790f1acc

fanotify: fix false positive on permission events

3 files changed · +13 9
  • fs/notify/fsnotify.c+1 1 modified
    diff --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 modified
    diff --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 modified
    diff --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

News mentions

0

No linked articles in our index yet.