CVE-2026-46040
Description
In the Linux kernel, the following vulnerability has been resolved:
inotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
When fsnotify_add_inode_mark_locked() fails in inotify_new_watch(), the error path calls inotify_remove_from_idr() but does not call dec_inotify_watches() to undo the preceding inc_inotify_watches(). This leaks a watch count, and repeated failures can exhaust the max_user_watches limit with -ENOSPC even when no watches are active.
Prior to commit 1cce1eea0aff ("inotify: Convert to using per-namespace limits"), the watch count was incremented after fsnotify_add_mark_locked() succeeded, so this path was not affected. The conversion moved inc_inotify_watches() before the mark insertion without adding the corresponding rollback.
Add the missing dec_inotify_watches() call in the error path.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In the Linux kernel, a missing decrement of the watch count in inotify's error path can exhaust the max_user_watches limit when fsnotify_add_inode_mark_locked() fails.
Vulnerability
In the Linux kernel's inotify subsystem, a reference count leak occurs when fsnotify_add_inode_mark_locked() fails inside inotify_new_watch(). The error path calls inotify_remove_from_idr() but does not call dec_inotify_watches() to undo the preceding inc_inotify_watches(). This flaw was introduced by commit 1cce1eea0aff ("inotify: Convert to using per-namespace limits"), which moved the increment before the mark insertion without adding the corresponding rollback. The issue affects kernel versions containing that commit up to the point where commit 6a320935fa42 (the fix) is applied. [1]
Exploitation
An attacker with the ability to repeatedly create and then cause inotify watches to fail during mark addition (for example, by triggering memory allocation failures or reaching other internal limits) can trigger the leak. Each failure increments the per-user watch counter without decrementing it, gradually consuming the max_user_watches quota. The attacker does not require special privileges beyond the ability to create inotify instances and initiate watch requests. [1]
Impact
By exhausting the max_user_watches limit, the attacker can cause subsequent inotify watch creation attempts by any user on the system to fail with -ENOSPC, effectively denying the service of inotify. This can impact monitoring tools, file managers, and other applications that rely on inotify, leading to a denial of service (availability impact). No confidentiality or integrity compromise is described. [1]
Mitigation
The fix is commit 6a320935fa42 ("inotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails") in the Linux kernel stable tree. Users should update their kernel to include this commit. If patching is not immediately possible, limiting the number of inotify watches per user via fs.inotify.max_user_watches sysctl can reduce the impact but does not prevent the leak. No workaround is available that fully addresses the bug without a kernel update. [1]
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1073ddc8518a32inotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 0794dcaf1e471e..26839972f609b1 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
fdaa42ca370dinotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index b372fb2c56bd1b..0d813c52ff9c37 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
9e48844f708einotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 5e1845f2c25dd2..2edac3b391787c 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
6a320935fa42inotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 5e1845f2c25dd2..2edac3b391787c 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
8bcc1cd237abinotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 1c4bfdab008d9b..804af9d6078b06 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -622,6 +622,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
8bcc1cd237abinotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 1c4bfdab008d9b..804af9d6078b06 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -622,6 +622,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
73ddc8518a32inotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 0794dcaf1e471e..26839972f609b1 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
9e48844f708einotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 5e1845f2c25dd2..2edac3b391787c 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
fdaa42ca370dinotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index b372fb2c56bd1b..0d813c52ff9c37 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
6a320935fa42inotify: fix watch count leak when fsnotify_add_inode_mark_locked() fails
1 file changed · +1 −1
fs/notify/inotify/inotify_user.c+1 −1 modifieddiff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 5e1845f2c25dd2..2edac3b391787c 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -621,6 +621,7 @@ static int inotify_new_watch(struct fsnotify_group *group, if (ret) { /* we failed to get on the inode, get off the idr */ inotify_remove_from_idr(group, tmp_i_mark); + dec_inotify_watches(group->inotify_data.ucounts); goto out_err; } -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing counter rollback: inc_inotify_watches() is called before fsnotify_add_inode_mark_locked(), but dec_inotify_watches() is not called in the error path when that function fails."
Attack vector
An unprivileged user can repeatedly call `inotify_add_watch()` on inodes in a way that causes `fsnotify_add_inode_mark_locked()` to fail (e.g., by racing with mark removal or hitting memory pressure). Each failure increments the per-namespace watch count via `inc_inotify_watches()` without decrementing it, leaking one watch count. By repeating this, the attacker can exhaust the `max_user_watches` limit, causing subsequent legitimate `inotify_add_watch()` calls to fail with `-ENOSPC` even though no actual watches are active [patch_id=2660206].
Affected code
The bug is in the `inotify_new_watch()` function in `fs/notify/inotify/inotify_user.c` [patch_id=2660206]. When `fsnotify_add_inode_mark_locked()` fails, the error path calls `inotify_remove_from_idr()` but omits a corresponding `dec_inotify_watches()` call to undo the earlier `inc_inotify_watches()`.
What the fix does
The patch adds a single line — `dec_inotify_watches(group->inotify_data.ucounts);` — in the error path of `inotify_new_watch()` right after `inotify_remove_from_idr()` and before `goto out_err` [patch_id=2660206]. This decrements the per-namespace watch count that was previously incremented by `inc_inotify_watches()`, restoring the counter to its correct value when the mark insertion fails. The fix ensures the watch count is always properly rolled back on failure, preventing the resource leak.
Preconditions
- authThe attacker must have the ability to call inotify_add_watch() on the system (unprivileged user with appropriate permissions on the target inode).
- inputThe attacker must be able to trigger a failure of fsnotify_add_inode_mark_locked() (e.g., via resource contention or specific race conditions).
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/6a320935fa4293e9e599ec9f85dc9eb3be7029f8nvd
- git.kernel.org/stable/c/73ddc8518a32baff6bc17afda4ee1ebae5b4ed12nvd
- git.kernel.org/stable/c/8bcc1cd237ab5ccfdd102869fa031c541943cf40nvd
- git.kernel.org/stable/c/9e48844f708eb48bae4e79cb21edc097c966306dnvd
- git.kernel.org/stable/c/fdaa42ca370d056428e5e171247c8fdce8dff36anvd
News mentions
0No linked articles in our index yet.