From 43b8838d6af71619c071ed2ccd6518c413ae6dac Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Thu, 8 Jan 2026 15:42:07 +0800 Subject: [PATCH] Reject new watches on deleted inodes --- kernel/src/fs/notify/inotify.rs | 12 +++++-- kernel/src/fs/path/dentry.rs | 6 ++-- .../src/apps/inotify/inotify_unlink.c | 35 +++++++++++++++++++ test/initramfs/src/apps/scripts/process.sh | 1 + .../gvisor/blocklists.ext2/inotify_test | 6 ++-- .../syscall/gvisor/blocklists.ext2/pipe_test | 4 --- .../syscall/gvisor/blocklists/inotify_test | 23 ++++++++---- 7 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 test/initramfs/src/apps/inotify/inotify_unlink.c delete mode 100644 test/initramfs/src/syscall/gvisor/blocklists.ext2/pipe_test diff --git a/kernel/src/fs/notify/inotify.rs b/kernel/src/fs/notify/inotify.rs index 37567920b..548e04771 100644 --- a/kernel/src/fs/notify/inotify.rs +++ b/kernel/src/fs/notify/inotify.rs @@ -165,12 +165,20 @@ impl InotifyFile { let subscriber = inotify_subscriber.clone() as Arc; let inode = path.inode(); - if inode + if !inode .fs_event_publisher_or_init() .add_subscriber(subscriber) { - inode.fs().fs_event_subscriber_stats().add_subscriber(); + // FIXME: This can be triggered by `unlink()` race conditions or + // `inotify_add_watch("/proc/self/fd/[fd]")`, where `[fd]` is a file descriptor + // pointing to a deleted file. Although Linux allows this, we don't support it, so an + // error is returned here. + return_errno_with_message!( + Errno::ENOENT, + "adding an inotify watch to a deleted inode is not supported yet" + ); } + inode.fs().fs_event_subscriber_stats().add_subscriber(); let wd = inotify_subscriber.wd(); let entry = SubscriberEntry { diff --git a/kernel/src/fs/path/dentry.rs b/kernel/src/fs/path/dentry.rs index 277cb6916..43d1277e8 100644 --- a/kernel/src/fs/path/dentry.rs +++ b/kernel/src/fs/path/dentry.rs @@ -327,6 +327,7 @@ impl Dentry { let nlinks = child_inode.metadata().nlinks; fs::notify::on_link_count(&child_inode); if nlinks == 0 { + // FIXME: `DELETE_SELF` should be generated after closing the last FD. fs::notify::on_inode_removed(&child_inode); } fs::notify::on_delete(self.inode(), &child_inode, || name.to_string()); @@ -379,13 +380,14 @@ impl Dentry { let nlinks = child_inode.metadata().nlinks; if nlinks == 0 { + // FIXME: `DELETE_SELF` should be generated after closing the last FD. fs::notify::on_inode_removed(&child_inode); } fs::notify::on_delete(self.inode(), &child_inode, || name.to_string()); if nlinks == 0 { // Ideally, we would use `fs_event_publisher()` here to avoid creating a - // `FsEventPublisher` on a dying inode. However, it isn't possible because we need to - // disable new subscribers. + // `FsEventPublisher` instance on a dying inode. However, it isn't possible because we + // need to disable new subscribers. let publisher = child_inode.fs_event_publisher_or_init(); let removed_nr_subscribers = publisher.disable_new_and_remove_subscribers(); child_inode diff --git a/test/initramfs/src/apps/inotify/inotify_unlink.c b/test/initramfs/src/apps/inotify/inotify_unlink.c new file mode 100644 index 000000000..8806dfc47 --- /dev/null +++ b/test/initramfs/src/apps/inotify/inotify_unlink.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MPL-2.0 + +#include +#include +#include +#include "../test.h" + +#define TEST_FILE "/tmp/test1" + +FN_TEST(unlink_add) +{ + int inotify_fd, fd, wd; + + inotify_fd = TEST_SUCC(inotify_init1(O_NONBLOCK)); + + fd = TEST_RES(open(TEST_FILE, O_CREAT | O_WRONLY, 0644), _ret == 4); + TEST_SUCC(unlink(TEST_FILE)); + + // FIXME: Asterinas currently does not support adding inotify watches + // to deleted inodes. +#ifdef __asterinas__ + TEST_ERRNO(inotify_add_watch(inotify_fd, "/proc/self/fd/4", + IN_DELETE_SELF), + ENOENT); + (void)wd; +#else + wd = TEST_SUCC(inotify_add_watch(inotify_fd, "/proc/self/fd/4", + IN_DELETE_SELF)); + TEST_SUCC(inotify_rm_watch(inotify_fd, wd)); +#endif + + TEST_SUCC(close(fd)); + TEST_SUCC(close(inotify_fd)); +} +END_TEST() diff --git a/test/initramfs/src/apps/scripts/process.sh b/test/initramfs/src/apps/scripts/process.sh index bb87d1a09..d347cb079 100755 --- a/test/initramfs/src/apps/scripts/process.sh +++ b/test/initramfs/src/apps/scripts/process.sh @@ -32,6 +32,7 @@ hello_pie/hello hello_world/hello_world inotify/inotify_align inotify/inotify_poll +inotify/inotify_unlink itimer/setitimer itimer/timer_create mmap/mmap_and_fork diff --git a/test/initramfs/src/syscall/gvisor/blocklists.ext2/inotify_test b/test/initramfs/src/syscall/gvisor/blocklists.ext2/inotify_test index b453e4726..7e4dc786f 100644 --- a/test/initramfs/src/syscall/gvisor/blocklists.ext2/inotify_test +++ b/test/initramfs/src/syscall/gvisor/blocklists.ext2/inotify_test @@ -1,3 +1,5 @@ -InotifyTest.AddRemoveUnlinkDoNotDeadlock +# TODO: Due to some FS bugs, they may trigger +# "PosixError(errno=28 No space left on device)" +# because too many files are created and deleted. InotifyTest.InotifyAndTargetDestructionDoNotDeadlock -InotifyTest.NotifyNoDeadlock +InotifyTest.InotifyAndTargetDestructionDoNotDeadlock_NoRandomSave diff --git a/test/initramfs/src/syscall/gvisor/blocklists.ext2/pipe_test b/test/initramfs/src/syscall/gvisor/blocklists.ext2/pipe_test deleted file mode 100644 index 3ec200f0e..000000000 --- a/test/initramfs/src/syscall/gvisor/blocklists.ext2/pipe_test +++ /dev/null @@ -1,4 +0,0 @@ -# Nothing here. Pipe files are supported by the ext2 file system. -# -# This file is kept to ensure that the `blocklists.ext2` directory -# is not empty, as it may become useful in the future. diff --git a/test/initramfs/src/syscall/gvisor/blocklists/inotify_test b/test/initramfs/src/syscall/gvisor/blocklists/inotify_test index 3c844c15f..d73b24304 100644 --- a/test/initramfs/src/syscall/gvisor/blocklists/inotify_test +++ b/test/initramfs/src/syscall/gvisor/blocklists/inotify_test @@ -1,5 +1,13 @@ +# TODO: Generate `DELETE_SELF`/`IGNORED` after closing the last FD. Inotify.DupFD +Inotify.IncludeUnlinkedFile +Inotify.IncludeUnlinkedFile_NoRandomSave + +# Symbolic links to the busybox binary does not work: +# "gvisor_test_temp_1103_1767603858010282988: applet not found" Inotify.Exec + +# TODO: Support the `EXCL_UNLINK` flag. Inotify.ExcludeUnlink Inotify.ExcludeUnlink_NoRandomSave Inotify.ExcludeUnlinkDirectory @@ -8,16 +16,17 @@ Inotify.ExcludeUnlinkInodeEvents Inotify.ExcludeUnlinkInodeEvents_NoRandomSave Inotify.ExcludeUnlinkMultipleChildren Inotify.ExcludeUnlinkMultipleChildren_NoRandomSave -Inotify.IncludeUnlinkedFile -Inotify.IncludeUnlinkedFile_NoRandomSave + +# TODO: Report `MOVE_*` events at the `rename()` system call. Inotify.MoveGeneratesEvents Inotify.MoveWatchedTargetGeneratesEvents + +# TODO: Support the `ONESHOT` flag. Inotify.OneShot + +# TODO: Support the `splice()` system call. Inotify.SpliceOnInotifyFD Inotify.SpliceOnWatchTarget -Inotify.Utimensat -InotifyTest.AddRemoveUnlinkDoNotDeadlock -InotifyTest.InotifyAndTargetDestructionDoNotDeadlock -InotifyTest.InotifyAndTargetDestructionDoNotDeadlock_NoRandomSave -InotifyTest.NotifyNoDeadlock +# TODO: Correct event generation at the `untimensat()` system call. +Inotify.Utimensat