* [BUG] fanotify: destroy/add race leaves a mark on a detached connector
@ 2026-08-27 4:50 Daehyeon Ko
2026-08-27 12:45 ` Amir Goldstein
2026-08-27 16:30 ` Jan Kara
0 siblings, 2 replies; 7+ messages in thread
From: Daehyeon Ko @ 2026-08-27 4:50 UTC (permalink / raw)
To: Jan Kara; +Cc: Amir Goldstein, Miklos Szeredi, linux-fsdevel, linux-kernel
Hello,
I found an unprivileged race in fsnotify_destroy_marks() that can leave an
attached fanotify mark on a detached connector. On unmodified Linux v7.2 and
v6.12.105 source builds, it naturally reaches the fsnotify_conn_mask()
warning from fanotify_mark(). On unmodified builds, the only observed
system-wide failure was a v7.2 panic when panic_on_warn=1; no memory
corruption or privilege escalation was observed.
Tested and source-inspected versions:
- Linux v7.2, commit
8d3ae59288f1e7d58d76558a6ee96d533bc5019f: reproduced.
- Linux v6.12.105, commit
14c37ff05f22da2fa7076d10f6a07c7ede330c83: reproduced.
- Torvalds master at
73e3f0710014fe6d4ed98cfc02292f6121db7558: the relevant destroy
iterator and fdinfo consumer remain present. Commit e422777fdd47
changed mark-mask updates and can mask the immediate warning, but it
did not change this iterator or the detached-connector state.
The unconditional connector detachment involved in the race appears to
originate in commit 6b3f05d24d35 ("fsnotify: Detach mark from object list
when last reference is dropped"), released in v4.12-rc1. My unprivileged
runtime results are limited to the two versions listed above. The limited
unprivileged fanotify functionality used by this reproducer was introduced
by 7cea2a3c505e ("fanotify: support limited functionality for unprivileged
users"), released in v5.13-rc1; I am not asserting ordinary-user
reachability before that change.
Root cause:
fsnotify_destroy_marks() holds a reference to the current mark, drops
conn->lock, destroys that mark, and then continues the hlist walk. While the
lock is dropped, an equal-priority mark can be inserted immediately before
the current entry. The walk resumes from the current entry's next pointer,
so it never visits the new predecessor, but it still detaches the connector
from the object after the walk.
The skipped mark remains ALIVE|ATTACHED and remains on both its group list
and the connector list. At the same time, the object no longer points to the
connector, conn->obj is NULL, and conn->type is DETACHED.
Representative v6.12.105 output is:
WARNING: CPU: 1 PID: 163 at fs/notify/mark.c:128
fsnotify_conn_mask+0x113/0x150
CPU: 1 UID: 65534 PID: 163 Comm: fanotify_destro
...
do_fanotify_mark
__x64_sys_fanotify_mark
The source reproducer ran on ext4 as UID/GID 65534 with CapEff=0 and
NoNewPrivs=1. It uses ordinary FAN_REPORT_FID groups and races final unlink
against fd-based mark insertion. Both builds used a KASAN-enabled research
configuration, but neither positive was a KASAN report.
With panic_on_warn=0 and panic_on_oops=0, the unmodified v7.2 run emitted the
warning and then completed 10,000 iterations and powered off cleanly. A
separate fresh v7.2 run with panic_on_warn=1 reached the same warning and
panicked through check_panic_on_warn(). The v6.12.105 run emitted seven
warnings and reached its 10,000-iteration progress line; it later timed out
during post-loop teardown, for which I am not assigning a kernel cause.
A timing-only diagnostic kernel also demonstrated that fdinfo can combine
the old INODE type with the detached NULL object and reach
fanotify_show_fdinfo() -> igrab(NULL). I have not reproduced that Oops on an
unmodified kernel, so it is not part of the impact claim. The skipped mark's
group-list reference keeps the connector allocated; I have no evidence of a
connector UAF, arbitrary read or write, information disclosure, or LPE.
I do not have a submission-ready patch. During fix development I rejected a
survivor-preservation prototype because it introduced a mask race, a
drain/restart prototype because additions could starve cleanup, and a bounded
retry prototype because review found an unresolved lock-order/deadlock risk
when generic callers hold external locks needed by cleanup. I am reporting
the root cause rather than sending an unsafe fix.
A tested source reproducer, full serial logs, configs, and diagnostic details
are available privately to the maintainers on request. AI-assisted tooling
was used during discovery and analysis; I reviewed the source and the literal
runtime evidence above and am reporting publicly without the reproducer as
required by Documentation/process/security-bugs.rst.
Assisted-by: LLM
Thanks,
Daehyeon Ko
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] fanotify: destroy/add race leaves a mark on a detached connector
2026-08-27 4:50 [BUG] fanotify: destroy/add race leaves a mark on a detached connector Daehyeon Ko
@ 2026-08-27 12:45 ` Amir Goldstein
2026-08-27 16:30 ` Jan Kara
1 sibling, 0 replies; 7+ messages in thread
From: Amir Goldstein @ 2026-08-27 12:45 UTC (permalink / raw)
To: Daehyeon Ko; +Cc: Jan Kara, Miklos Szeredi, linux-fsdevel, linux-kernel
On Thu, Aug 27, 2026 at 6:50 AM Daehyeon Ko <4ncienth@gmail.com> wrote:
>
> Hello,
>
> I found an unprivileged race in fsnotify_destroy_marks() that can leave an
> attached fanotify mark on a detached connector. On unmodified Linux v7.2 and
> v6.12.105 source builds, it naturally reaches the fsnotify_conn_mask()
> warning from fanotify_mark(). On unmodified builds, the only observed
> system-wide failure was a v7.2 panic when panic_on_warn=1; no memory
> corruption or privilege escalation was observed.
>
> Tested and source-inspected versions:
>
> - Linux v7.2, commit
> 8d3ae59288f1e7d58d76558a6ee96d533bc5019f: reproduced.
> - Linux v6.12.105, commit
> 14c37ff05f22da2fa7076d10f6a07c7ede330c83: reproduced.
> - Torvalds master at
> 73e3f0710014fe6d4ed98cfc02292f6121db7558: the relevant destroy
> iterator and fdinfo consumer remain present. Commit e422777fdd47
> changed mark-mask updates and can mask the immediate warning, but it
> did not change this iterator or the detached-connector state.
>
> The unconditional connector detachment involved in the race appears to
> originate in commit 6b3f05d24d35 ("fsnotify: Detach mark from object list
> when last reference is dropped"), released in v4.12-rc1. My unprivileged
> runtime results are limited to the two versions listed above. The limited
> unprivileged fanotify functionality used by this reproducer was introduced
> by 7cea2a3c505e ("fanotify: support limited functionality for unprivileged
> users"), released in v5.13-rc1; I am not asserting ordinary-user
> reachability before that change.
>
> Root cause:
>
> fsnotify_destroy_marks() holds a reference to the current mark, drops
> conn->lock, destroys that mark, and then continues the hlist walk. While the
> lock is dropped, an equal-priority mark can be inserted immediately before
> the current entry. The walk resumes from the current entry's next pointer,
> so it never visits the new predecessor, but it still detaches the connector
> from the object after the walk.
>
> The skipped mark remains ALIVE|ATTACHED and remains on both its group list
> and the connector list. At the same time, the object no longer points to the
> connector, conn->obj is NULL, and conn->type is DETACHED.
>
> Representative v6.12.105 output is:
>
> WARNING: CPU: 1 PID: 163 at fs/notify/mark.c:128
> fsnotify_conn_mask+0x113/0x150
> CPU: 1 UID: 65534 PID: 163 Comm: fanotify_destro
> ...
> do_fanotify_mark
> __x64_sys_fanotify_mark
>
> The source reproducer ran on ext4 as UID/GID 65534 with CapEff=0 and
> NoNewPrivs=1. It uses ordinary FAN_REPORT_FID groups and races final unlink
> against fd-based mark insertion. Both builds used a KASAN-enabled research
> configuration, but neither positive was a KASAN report.
>
> With panic_on_warn=0 and panic_on_oops=0, the unmodified v7.2 run emitted the
> warning and then completed 10,000 iterations and powered off cleanly. A
> separate fresh v7.2 run with panic_on_warn=1 reached the same warning and
> panicked through check_panic_on_warn(). The v6.12.105 run emitted seven
> warnings and reached its 10,000-iteration progress line; it later timed out
> during post-loop teardown, for which I am not assigning a kernel cause.
>
> A timing-only diagnostic kernel also demonstrated that fdinfo can combine
> the old INODE type with the detached NULL object and reach
> fanotify_show_fdinfo() -> igrab(NULL). I have not reproduced that Oops on an
> unmodified kernel, so it is not part of the impact claim. The skipped mark's
> group-list reference keeps the connector allocated; I have no evidence of a
> connector UAF, arbitrary read or write, information disclosure, or LPE.
>
> I do not have a submission-ready patch. During fix development I rejected a
> survivor-preservation prototype because it introduced a mask race, a
> drain/restart prototype because additions could starve cleanup, and a bounded
> retry prototype because review found an unresolved lock-order/deadlock risk
> when generic callers hold external locks needed by cleanup. I am reporting
> the root cause rather than sending an unsafe fix.
>
> A tested source reproducer, full serial logs, configs, and diagnostic details
> are available privately to the maintainers on request. AI-assisted tooling
> was used during discovery and analysis; I reviewed the source and the literal
> runtime evidence above and am reporting publicly without the reproducer as
> required by Documentation/process/security-bugs.rst.
>
Hi Daehyeon,
Thanks for the detailed report.
Without getting into discussions on solutions to the race, do we even care
that a detached connector has a leftover attached mark in this tiny corner case?
I mean the assertion was added because we did not anticipate it,
but now that we understand that it can happen, the assertion could be removed
or relaxed if there are no other major consequences.
Regarding the reproduced igrab(NULL), this could be fixed by
using a helper like this in show fdino code:
static inline struct inode *fsnotify_conn_get_inode_safe(struct
fsnotify_mark_connector *conn)
{
struct inode *inode = conn->obj;
return inode ? igrab(inode) : NULL;
}
WDYT?
Thanks,
Amir.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] fanotify: destroy/add race leaves a mark on a detached connector
2026-08-27 4:50 [BUG] fanotify: destroy/add race leaves a mark on a detached connector Daehyeon Ko
2026-08-27 12:45 ` Amir Goldstein
@ 2026-08-27 16:30 ` Jan Kara
2026-08-28 4:24 ` Daehyeon Ko
1 sibling, 1 reply; 7+ messages in thread
From: Jan Kara @ 2026-08-27 16:30 UTC (permalink / raw)
To: Daehyeon Ko
Cc: Jan Kara, Amir Goldstein, Miklos Szeredi, linux-fsdevel, linux-kernel
Hello!
On Thu 27-08-26 13:50:04, Daehyeon Ko wrote:
> I found an unprivileged race in fsnotify_destroy_marks() that can leave an
> attached fanotify mark on a detached connector. On unmodified Linux v7.2 and
> v6.12.105 source builds, it naturally reaches the fsnotify_conn_mask()
> warning from fanotify_mark(). On unmodified builds, the only observed
> system-wide failure was a v7.2 panic when panic_on_warn=1; no memory
> corruption or privilege escalation was observed.
>
> Tested and source-inspected versions:
>
> - Linux v7.2, commit
> 8d3ae59288f1e7d58d76558a6ee96d533bc5019f: reproduced.
> - Linux v6.12.105, commit
> 14c37ff05f22da2fa7076d10f6a07c7ede330c83: reproduced.
> - Torvalds master at
> 73e3f0710014fe6d4ed98cfc02292f6121db7558: the relevant destroy
> iterator and fdinfo consumer remain present. Commit e422777fdd47
> changed mark-mask updates and can mask the immediate warning, but it
> did not change this iterator or the detached-connector state.
>
> The unconditional connector detachment involved in the race appears to
> originate in commit 6b3f05d24d35 ("fsnotify: Detach mark from object list
> when last reference is dropped"), released in v4.12-rc1. My unprivileged
> runtime results are limited to the two versions listed above. The limited
> unprivileged fanotify functionality used by this reproducer was introduced
> by 7cea2a3c505e ("fanotify: support limited functionality for unprivileged
> users"), released in v5.13-rc1; I am not asserting ordinary-user
> reachability before that change.
>
> Root cause:
>
> fsnotify_destroy_marks() holds a reference to the current mark, drops
> conn->lock, destroys that mark, and then continues the hlist walk. While the
> lock is dropped, an equal-priority mark can be inserted immediately before
> the current entry. The walk resumes from the current entry's next pointer,
> so it never visits the new predecessor, but it still detaches the connector
> from the object after the walk.
>
> The skipped mark remains ALIVE|ATTACHED and remains on both its group list
> and the connector list. At the same time, the object no longer points to the
> connector, conn->obj is NULL, and conn->type is DETACHED.
>
> Representative v6.12.105 output is:
>
> WARNING: CPU: 1 PID: 163 at fs/notify/mark.c:128
> fsnotify_conn_mask+0x113/0x150
> CPU: 1 UID: 65534 PID: 163 Comm: fanotify_destro
> ...
> do_fanotify_mark
> __x64_sys_fanotify_mark
>
> The source reproducer ran on ext4 as UID/GID 65534 with CapEff=0 and
> NoNewPrivs=1. It uses ordinary FAN_REPORT_FID groups and races final unlink
> against fd-based mark insertion.
Thanks for the detailed report. Before discussing possible solutions I'd
like to better understand how the race happens because so far that's
unclear to me. You mention you are racing final unlink with fd-based mark
insertion - so I presume we are speaking about inode marks here. Fair.
Now for "final unlink" to reach fsnotify_destroy_marks() it has to go
through:
d_delete() -> dentry_unlink_inode() -> fsnotify_inoderemove()
But to go through this path, d_delete() has to validate that
dentry->d_lockref.count == 1 which should not be possible if the inode is
still open (and thus the file->f_path holds the dentry reference). What am
I missing here? Which two calls are exactly racing?
FWIW my current suspicion is that calling fanotify_inode_delete() from
fsnotify_inoderemove() is actually too early and we should always leave it
to __destroy_inode() time which would also avoid any races with mark
addition. But the fact that the above race can happen at all makes me
nervous we can be missing some other cornercases...
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] fanotify: destroy/add race leaves a mark on a detached connector
2026-08-27 16:30 ` Jan Kara
@ 2026-08-28 4:24 ` Daehyeon Ko
2026-08-28 9:05 ` Jan Kara
0 siblings, 1 reply; 7+ messages in thread
From: Daehyeon Ko @ 2026-08-28 4:24 UTC (permalink / raw)
To: Jan Kara; +Cc: Amir Goldstein, Miklos Szeredi, linux-fsdevel, linux-kernel
Hi Honza,
You are right that an fd-held dentry cannot satisfy the count == 1 check. I
was imprecise in the report: the fd and the final unlink refer to two
different hard-link dentries for the same inode.
The setup is:
1. create A/x and link it as B/y;
2. open B/y, add the old-group inode mark through that fd, then unlink
B/y, leaving i_nlink == 1 while the fd retains the B/y dentry;
3. race these two calls:
unlink("A/x")
fanotify_mark(racer_group, FAN_MARK_ADD, FAN_MODIFY,
victim_fd, NULL)
where victim_fd still refers to the already unlinked B/y dentry.
So the A/x dentry checked by d_delete() is not held by the fd and can have
d_lockref.count == 1. Its unlink drops the inode's last link and reaches
dentry_unlink_inode() -> fsnotify_inoderemove(). At the same time,
fanotify_find_path() gets the same inode through the fd-held B/y dentry and
adds the racer mark.
Your suspicion matches this topology: __fsnotify_inode_delete() from
fsnotify_inoderemove() is running while the inode is still live and reachable
through the other, fd-held dentry. Deferring mark cleanup until
__destroy_inode() would avoid this particular add-versus-cleanup race. I have
not audited the broader consequences of that change yet.
I can send the exact reproducer privately if that would be useful.
Thanks,
Daehyeon
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] fanotify: destroy/add race leaves a mark on a detached connector
2026-08-28 4:24 ` Daehyeon Ko
@ 2026-08-28 9:05 ` Jan Kara
2026-08-28 10:18 ` Amir Goldstein
0 siblings, 1 reply; 7+ messages in thread
From: Jan Kara @ 2026-08-28 9:05 UTC (permalink / raw)
To: Daehyeon Ko
Cc: Jan Kara, Amir Goldstein, Miklos Szeredi, linux-fsdevel, linux-kernel
Hi!
On Fri 28-08-26 13:24:19, Daehyeon Ko wrote:
> You are right that an fd-held dentry cannot satisfy the count == 1 check. I
> was imprecise in the report: the fd and the final unlink refer to two
> different hard-link dentries for the same inode.
>
> The setup is:
>
> 1. create A/x and link it as B/y;
> 2. open B/y, add the old-group inode mark through that fd, then unlink
> B/y, leaving i_nlink == 1 while the fd retains the B/y dentry;
> 3. race these two calls:
>
> unlink("A/x")
>
> fanotify_mark(racer_group, FAN_MARK_ADD, FAN_MODIFY,
> victim_fd, NULL)
>
> where victim_fd still refers to the already unlinked B/y dentry.
>
> So the A/x dentry checked by d_delete() is not held by the fd and can have
> d_lockref.count == 1. Its unlink drops the inode's last link and reaches
> dentry_unlink_inode() -> fsnotify_inoderemove(). At the same time,
> fanotify_find_path() gets the same inode through the fd-held B/y dentry and
> adds the racer mark.
Thanks for explanation of the reproducer. Now it makes sense. The
conclusion from this is that fsnotify_inoderemove() can indeed be called
for still fully alive open inodes which should be (think of permission /
pre-content events) still generating events. That could be actually
breaking some assumptions of userspace although I don't think it's a
serious issue in practice because users of permission / pre-content watches
tend to use filesystem / mount watches.
Also I think we could generate FS_DELETE_SELF twice for the inode - once it
will we generated as a result of d_delete("A/x"), and finish_dput() ->
dentry_kill() -> dentry_unlink_inode() will generate it for the second time
on the last close of the open through "B/y". That should be mostly harmless
but still it's an oddity to keep in mind and preferably avoid if we can.
Now there's the question about the best fix for the problem. I've realized
we cannot just remove the call to fsnotify_inode_delete() from the place
where fsnotify_inoderemove() is called (and rely on the call from
__destroy_inode()) because the connector holds inode reference and so
__destroy_inode() would never get called. Hrm, this is a hairy issue and so
far I don't see a good place to reliably trigger detaching of connector
from the inode. One reliable place would be iput() but that's really ugly
and would slow down every iput() user. The cleanest solution I can
currently see is to stop holding inode reference from a connector as I've
proposed in the past. Then we can reliably perform the cleanup in
__destroy_inode(). But that will be a rather non-trivial undertaking. Amir,
do you see some other option?
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] fanotify: destroy/add race leaves a mark on a detached connector
2026-08-28 9:05 ` Jan Kara
@ 2026-08-28 10:18 ` Amir Goldstein
2026-08-28 17:36 ` Jan Kara
0 siblings, 1 reply; 7+ messages in thread
From: Amir Goldstein @ 2026-08-28 10:18 UTC (permalink / raw)
To: Jan Kara; +Cc: Daehyeon Ko, Miklos Szeredi, linux-fsdevel, linux-kernel
On Fri, Aug 28, 2026 at 11:05 AM Jan Kara <jack@suse.cz> wrote:
>
> Hi!
>
> On Fri 28-08-26 13:24:19, Daehyeon Ko wrote:
> > You are right that an fd-held dentry cannot satisfy the count == 1 check. I
> > was imprecise in the report: the fd and the final unlink refer to two
> > different hard-link dentries for the same inode.
> >
> > The setup is:
> >
> > 1. create A/x and link it as B/y;
> > 2. open B/y, add the old-group inode mark through that fd, then unlink
> > B/y, leaving i_nlink == 1 while the fd retains the B/y dentry;
> > 3. race these two calls:
> >
> > unlink("A/x")
> >
> > fanotify_mark(racer_group, FAN_MARK_ADD, FAN_MODIFY,
> > victim_fd, NULL)
> >
> > where victim_fd still refers to the already unlinked B/y dentry.
> >
> > So the A/x dentry checked by d_delete() is not held by the fd and can have
> > d_lockref.count == 1. Its unlink drops the inode's last link and reaches
> > dentry_unlink_inode() -> fsnotify_inoderemove(). At the same time,
> > fanotify_find_path() gets the same inode through the fd-held B/y dentry and
> > adds the racer mark.
>
> Thanks for explanation of the reproducer. Now it makes sense. The
> conclusion from this is that fsnotify_inoderemove() can indeed be called
> for still fully alive open inodes which should be (think of permission /
> pre-content events) still generating events. That could be actually
> breaking some assumptions of userspace although I don't think it's a
> serious issue in practice because users of permission / pre-content watches
> tend to use filesystem / mount watches.
>
> Also I think we could generate FS_DELETE_SELF twice for the inode - once it
> will we generated as a result of d_delete("A/x"), and finish_dput() ->
> dentry_kill() -> dentry_unlink_inode() will generate it for the second time
> on the last close of the open through "B/y". That should be mostly harmless
> but still it's an oddity to keep in mind and preferably avoid if we can.
>
I don't know. This sounds like a fundamental behavior change.
In current upstream FAN_DELETE_SELF is a dying breath of an
inode and no more events on this inode are expected IIUC.
Applications could be relying on this.
In fact I have an application that almost relies on this, but IIRC
it does one last open_by_handle_at() to make sure before calling
the inode dead. Other applications could be less strict.
Changing behavior so events could happen after FAN_DELETE_SELF
sounds risky.
> Now there's the question about the best fix for the problem. I've realized
> we cannot just remove the call to fsnotify_inode_delete() from the place
> where fsnotify_inoderemove() is called (and rely on the call from
> __destroy_inode()) because the connector holds inode reference and so
> __destroy_inode() would never get called. Hrm, this is a hairy issue and so
> far I don't see a good place to reliably trigger detaching of connector
> from the inode. One reliable place would be iput() but that's really ugly
> and would slow down every iput() user. The cleanest solution I can
> currently see is to stop holding inode reference from a connector as I've
> proposed in the past. Then we can reliably perform the cleanup in
> __destroy_inode(). But that will be a rather non-trivial undertaking. Amir,
> do you see some other option?
>
What about the option I proposed to live with the orphan marks
Is it so bad? I mean it's confusing and removing the assertion could
hide real bugs/races, but maybe it's worth not complicating the code
until we move forward to re-attachable marks.
One way to think about it is that if we do the re-attachable marks,
we would need to store ino/st_dev and maybe fid in the mark and
then show_fdinfo wont need to get the inode reference at all.
So in a way, allowing an orphan mark on the connector is a step in
the direction of allowing re-attachable marks on the connector.
Maybe...
Thanks,
Amir.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] fanotify: destroy/add race leaves a mark on a detached connector
2026-08-28 10:18 ` Amir Goldstein
@ 2026-08-28 17:36 ` Jan Kara
0 siblings, 0 replies; 7+ messages in thread
From: Jan Kara @ 2026-08-28 17:36 UTC (permalink / raw)
To: Amir Goldstein
Cc: Jan Kara, Daehyeon Ko, Miklos Szeredi, linux-fsdevel, linux-kernel
On Fri 28-08-26 12:18:52, Amir Goldstein wrote:
> On Fri, Aug 28, 2026 at 11:05 AM Jan Kara <jack@suse.cz> wrote:
> > On Fri 28-08-26 13:24:19, Daehyeon Ko wrote:
> > > You are right that an fd-held dentry cannot satisfy the count == 1 check. I
> > > was imprecise in the report: the fd and the final unlink refer to two
> > > different hard-link dentries for the same inode.
> > >
> > > The setup is:
> > >
> > > 1. create A/x and link it as B/y;
> > > 2. open B/y, add the old-group inode mark through that fd, then unlink
> > > B/y, leaving i_nlink == 1 while the fd retains the B/y dentry;
> > > 3. race these two calls:
> > >
> > > unlink("A/x")
> > >
> > > fanotify_mark(racer_group, FAN_MARK_ADD, FAN_MODIFY,
> > > victim_fd, NULL)
> > >
> > > where victim_fd still refers to the already unlinked B/y dentry.
> > >
> > > So the A/x dentry checked by d_delete() is not held by the fd and can have
> > > d_lockref.count == 1. Its unlink drops the inode's last link and reaches
> > > dentry_unlink_inode() -> fsnotify_inoderemove(). At the same time,
> > > fanotify_find_path() gets the same inode through the fd-held B/y dentry and
> > > adds the racer mark.
> >
> > Thanks for explanation of the reproducer. Now it makes sense. The
> > conclusion from this is that fsnotify_inoderemove() can indeed be called
> > for still fully alive open inodes which should be (think of permission /
> > pre-content events) still generating events. That could be actually
> > breaking some assumptions of userspace although I don't think it's a
> > serious issue in practice because users of permission / pre-content watches
> > tend to use filesystem / mount watches.
> >
> > Also I think we could generate FS_DELETE_SELF twice for the inode - once it
> > will we generated as a result of d_delete("A/x"), and finish_dput() ->
> > dentry_kill() -> dentry_unlink_inode() will generate it for the second time
> > on the last close of the open through "B/y". That should be mostly harmless
> > but still it's an oddity to keep in mind and preferably avoid if we can.
>
> I don't know. This sounds like a fundamental behavior change.
> In current upstream FAN_DELETE_SELF is a dying breath of an
> inode and no more events on this inode are expected IIUC.
>
> Applications could be relying on this.
> In fact I have an application that almost relies on this, but IIRC
> it does one last open_by_handle_at() to make sure before calling
> the inode dead. Other applications could be less strict.
>
> Changing behavior so events could happen after FAN_DELETE_SELF
> sounds risky.
I was probably imprecise with my wording. What I meant is that *currently*
when you have a hardlinked inode, you open and unlink through B/y and then
the unlink though A/x races with close of the B/y file, then you could get
two FAN_DELETE_SELF events if the race works out right. The only change of
behavior I was planning was avoiding this race if possible :).
> > Now there's the question about the best fix for the problem. I've realized
> > we cannot just remove the call to fsnotify_inode_delete() from the place
> > where fsnotify_inoderemove() is called (and rely on the call from
> > __destroy_inode()) because the connector holds inode reference and so
> > __destroy_inode() would never get called. Hrm, this is a hairy issue and so
> > far I don't see a good place to reliably trigger detaching of connector
> > from the inode. One reliable place would be iput() but that's really ugly
> > and would slow down every iput() user. The cleanest solution I can
> > currently see is to stop holding inode reference from a connector as I've
> > proposed in the past. Then we can reliably perform the cleanup in
> > __destroy_inode(). But that will be a rather non-trivial undertaking. Amir,
> > do you see some other option?
>
> What about the option I proposed to live with the orphan marks
> Is it so bad? I mean it's confusing and removing the assertion could
> hide real bugs/races, but maybe it's worth not complicating the code
> until we move forward to re-attachable marks.
I'm not so much worried about the case when placing of the mark races with
unlink. There I agree we can somehow paper over the problem for now. What
I'm more worried about is the case where someone places a precontent /
permission mark on the inode and a malicious user does hardlink + open +
unlink trick and after unlinking the original link to the inode, he has an
open fd to the inode stripped of any notification marks which I'd say
violates the promise of precontent / permission marks.
Honza
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-28 17:36 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-27 4:50 [BUG] fanotify: destroy/add race leaves a mark on a detached connector Daehyeon Ko
2026-08-27 12:45 ` Amir Goldstein
2026-08-27 16:30 ` Jan Kara
2026-08-28 4:24 ` Daehyeon Ko
2026-08-28 9:05 ` Jan Kara
2026-08-28 10:18 ` Amir Goldstein
2026-08-28 17:36 ` Jan Kara
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®