* [LTP] [PATCH][STAGING] syscalls/fanotify26: Add test for nested permission events
@ 2026-10-27 19:37 Edvard Conrad via ltp
2026-10-27 23:20 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 2+ messages in thread
From: Edvard Conrad via ltp @ 2026-10-27 19:37 UTC (permalink / raw)
To: ltp@lists.linux.it; +Cc: Amir Goldstein, Jan Kara, Edvard Conrad
The kernel creates the file descriptor it reports with a permission
event by an open that must not generate further events. When the
reported path is on a stacked filesystem, that open opens the real file
on the backing filesystem, and since commit ebe559609d78 ("fs: get rid
of __FMODE_NONOTIFY kludge") the backing file no longer inherits
FMODE_NONOTIFY. A listener that also watches the backing filesystem is
then sent a second permission event from inside its own read(2), which
only the listener itself can answer, so a single reader never returns.
Verify that a listener returns from read(2) and answers an open
permission event for a file on an overlayfs mount. The first test case
marks the overlay only and passes on any kernel, the second one also
marks the backing filesystem, which is the shape an anti-malware
scanner uses on an overlayfs root.
The second case fails on a kernel without the pending fix "fs: let
backing files inherit FMODE_NONOTIFY from the user file", so the test
is added to runtest/staging for now.
Link: https://lore.kernel.org/all/P4wV1J9--F-9@tuta.io/
Signed-off-by: Edvard Conrad <edvard@tuta.io>
---
runtest/staging | 1 +
testcases/kernel/syscalls/fanotify/.gitignore | 1 +
.../kernel/syscalls/fanotify/fanotify26.c | 185 ++++++++++++++++++
3 files changed, 187 insertions(+)
create mode 100644 testcases/kernel/syscalls/fanotify/fanotify26.c
diff --git a/runtest/staging b/runtest/staging
index ef1cdea15..2ce76b119 100644
--- a/runtest/staging
+++ b/runtest/staging
@@ -1 +1,2 @@
# Tests for features that are not yet in the stable kernel ABI
+fanotify26 fanotify26
diff --git a/testcases/kernel/syscalls/fanotify/.gitignore b/testcases/kernel/syscalls/fanotify/.gitignore
index d6d0599f1..c13d9b33c 100644
--- a/testcases/kernel/syscalls/fanotify/.gitignore
+++ b/testcases/kernel/syscalls/fanotify/.gitignore
@@ -23,4 +23,5 @@
/fanotify23
/fanotify24
/fanotify25
+/fanotify26
/fanotify_child
diff --git a/testcases/kernel/syscalls/fanotify/fanotify26.c b/testcases/kernel/syscalls/fanotify/fanotify26.c
new file mode 100644
index 000000000..529d09e25
--- /dev/null
+++ b/testcases/kernel/syscalls/fanotify/fanotify26.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*\
+ * Check that a permission event on a stacked filesystem does not deadlock the
+ * :manpage:`fanotify(7)` listener.
+ *
+ * When the kernel reports a permission event, it creates the event file
+ * descriptor with an open that must not generate further events. If the
+ * reported path is on a stacked filesystem, that open opens the real file on
+ * the backing filesystem. Should the backing file not inherit "generate no
+ * events", and the listener watch the backing filesystem as well, the kernel
+ * queues a second permission event to the same group while the listener is
+ * still inside :manpage:`read(2)`. Only the listener can answer it, so a
+ * listener that reads with a single thread never returns, and every task that
+ * touches a watched filesystem queues up behind it.
+ *
+ * The first test case marks the overlay only and must pass on any kernel. The
+ * second one marks the backing filesystem as well, which is the shape an
+ * anti-malware scanner uses on an overlayfs root.
+ *
+ * The test needs root to create permission events and to mount overlayfs.
+ */
+
+#define _GNU_SOURCE
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "tst_test.h"
+
+#ifdef HAVE_SYS_FANOTIFY_H
+#include "fanotify.h"
+
+#define BACKING_MNT OVL_BASE_MNTPOINT
+#define FNAME "test_file"
+#define OVL_FILE OVL_MNT "/" FNAME
+#define LOWER_FILE OVL_LOWER "/" FNAME
+
+/*
+ * The listener either answers the event at once or is blocked for good, so
+ * this only has to outlast scheduling noise on a loaded machine.
+ */
+#define WAIT_MSEC 5000
+
+static int fd_notify = -1;
+static int filesystem_mark_unsupported;
+
+static struct tcase {
+ const char *tname;
+ int mark_backing;
+} tcases[] = {
+ {"Mark on the overlay", 0},
+ {"Mark on the overlay and on the backing filesystem", 1},
+};
+
+static void answer_one_event(void)
+{
+ struct fanotify_event_metadata event;
+ struct fanotify_response resp;
+
+ /* Blocks for good if the event fd generates a nested event. */
+ SAFE_READ(1, fd_notify, &event, sizeof(event));
+
+ if (event.mask != FAN_OPEN_PERM) {
+ tst_brk(TBROK, "Got unexpected event mask %llx",
+ (unsigned long long)event.mask);
+ }
+
+ resp.fd = event.fd;
+ resp.response = FAN_ALLOW;
+ SAFE_WRITE(SAFE_WRITE_ALL, fd_notify, &resp, sizeof(resp));
+
+ if (event.fd != FAN_NOFD)
+ SAFE_CLOSE(event.fd);
+}
+
+static void run(unsigned int n)
+{
+ struct tcase *tc = &tcases[n];
+ pid_t opener, reader;
+
+ tst_res(TINFO, "Test #%d: %s", n, tc->tname);
+
+ if (tc->mark_backing && filesystem_mark_unsupported) {
+ tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported");
+ return;
+ }
+
+ fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_CONTENT, O_RDONLY);
+
+ SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
+ FAN_OPEN_PERM, AT_FDCWD, OVL_MNT);
+
+ if (tc->mark_backing) {
+ SAFE_FANOTIFY_MARK(fd_notify,
+ FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
+ FAN_OPEN_PERM, AT_FDCWD, BACKING_MNT);
+ }
+
+ opener = SAFE_FORK();
+ if (!opener) {
+ int fd;
+
+ /* Must not hold the group, it has to be released on close. */
+ SAFE_CLOSE(fd_notify);
+ /*
+ * The kernel denies the event when a listener that never
+ * answered it goes away, so a failure here is the fallout of
+ * the test tearing a blocked listener down, not a result.
+ */
+ fd = open(OVL_FILE, O_RDONLY);
+ if (fd >= 0)
+ SAFE_CLOSE(fd);
+ exit(0);
+ }
+
+ /* The opener sleeps in the kernel once its event is queued. */
+ if (TST_PROCESS_STATE_WAIT(opener, 'D', WAIT_MSEC)) {
+ SAFE_CLOSE(fd_notify);
+ tst_brk(TBROK, "Open generated no permission event");
+ }
+
+ reader = SAFE_FORK();
+ if (!reader) {
+ answer_one_event();
+ exit(0);
+ }
+
+ if (TST_PROCESS_STATE_WAIT(reader, 'Z', WAIT_MSEC)) {
+ tst_res(TFAIL,
+ "Listener blocked in read() by a nested event");
+ SAFE_KILL(reader, SIGKILL);
+ } else {
+ tst_res(TPASS, "Listener answered the event");
+ }
+
+ SAFE_WAITPID(reader, NULL, 0);
+
+ /* Releases whatever is still queued, so the opener can finish. */
+ SAFE_CLOSE(fd_notify);
+ fd_notify = -1;
+
+ SAFE_WAITPID(opener, NULL, 0);
+}
+
+static void setup(void)
+{
+ /* The file has to exist in the lower layer to be opened through it. */
+ SAFE_UMOUNT(OVL_MNT);
+ SAFE_TOUCH(LOWER_FILE, 0644, NULL);
+ SAFE_MOUNT_OVERLAY();
+
+ require_fanotify_access_permissions_supported_on_fs(OVL_FILE);
+
+ filesystem_mark_unsupported =
+ fanotify_mark_supported_on_fs(FAN_MARK_FILESYSTEM, OVL_FILE) ||
+ fanotify_mark_supported_on_fs(FAN_MARK_FILESYSTEM, BACKING_MNT);
+}
+
+static void cleanup(void)
+{
+ if (fd_notify >= 0)
+ SAFE_CLOSE(fd_notify);
+}
+
+static struct tst_test test = {
+ .test = run,
+ .tcnt = ARRAY_SIZE(tcases),
+ .setup = setup,
+ .cleanup = cleanup,
+ .forks_child = 1,
+ .needs_root = 1,
+ .mount_device = 1,
+ .needs_overlay = 1,
+ .mntpoint = OVL_BASE_MNTPOINT,
+};
+
+#else
+ TST_TEST_TCONF("system doesn't have required fanotify support");
+#endif
--
2.56.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [LTP] syscalls/fanotify26: Add test for nested permission events
2026-10-27 19:37 [LTP] [PATCH][STAGING] syscalls/fanotify26: Add test for nested permission events Edvard Conrad via ltp
@ 2026-10-27 23:20 ` linuxtestproject.agent
0 siblings, 0 replies; 2+ messages in thread
From: linuxtestproject.agent @ 2026-10-27 23:20 UTC (permalink / raw)
To: Edvard Conrad; +Cc: ltp
Hi Edvard,
On Tue, Oct 27, 2026 at 08:37:01PM +0100, Edvard Conrad wrote:
> syscalls/fanotify26: Add test for nested permission events
> --- /dev/null
> +++ b/testcases/kernel/syscalls/fanotify/fanotify26.c
> @@ -0,0 +1,185 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*\
> + * Check that a permission event on a stacked filesystem does not deadlock the
A copyright header block ("/* Copyright (c) 2026 Edvard Conrad
<edvard@tuta.io> */") is missing after the SPDX license identifier.
> + if (tc->mark_backing && filesystem_mark_unsupported) {
> + tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported");
> + return;
> + }
> +
> + fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_CONTENT, O_RDONLY);
> +
> + SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
> + FAN_OPEN_PERM, AT_FDCWD, OVL_MNT);
Both test cases use FAN_MARK_FILESYSTEM on OVL_MNT, so gating
filesystem_mark_unsupported on tc->mark_backing causes the first test case to
fail with TBROK when FAN_MARK_FILESYSTEM is unsupported on overlayfs or in
the kernel. Use REQUIRE_MARK_TYPE_SUPPORTED_ON_FS(FAN_MARK_FILESYSTEM,
OVL_MNT) and REQUIRE_MARK_TYPE_SUPPORTED_ON_FS(FAN_MARK_FILESYSTEM,
BACKING_MNT) in setup() and drop filesystem_mark_unsupported.
> + fd = open(OVL_FILE, O_RDONLY);
> + if (fd >= 0)
> + SAFE_CLOSE(fd);
> + exit(0);
> + }
> +
> + /* The opener sleeps in the kernel once its event is queued. */
> + if (TST_PROCESS_STATE_WAIT(opener, 'D', WAIT_MSEC)) {
> + SAFE_CLOSE(fd_notify);
> + tst_brk(TBROK, "Open generated no permission event");
> + }
Use "if (fd != -1)" to check file descriptor validity in opener. Also,
SAFE_CLOSE(fd_notify) before tst_brk() is redundant because cleanup() already
closes fd_notify.
> + reader = SAFE_FORK();
> + if (!reader) {
> + answer_one_event();
> + exit(0);
> + }
> +
> + if (TST_PROCESS_STATE_WAIT(reader, 'Z', WAIT_MSEC)) {
> + tst_res(TFAIL,
> + "Listener blocked in read() by a nested event");
> + SAFE_KILL(reader, SIGKILL);
> + } else {
> + tst_res(TPASS, "Listener answered the event");
> + }
> +
> + SAFE_WAITPID(reader, NULL, 0);
Inferring TPASS from the child reaching 'Z' state and reaping it with
SAFE_WAITPID(reader, NULL, 0) hides abnormal child termination. Call
tst_res(TPASS, "Listener answered the event") at the end of
answer_one_event() in the child, and only call SAFE_WAITPID(reader, NULL, 0)
after SAFE_KILL(reader, SIGKILL), letting the library reap the children on
the normal path.
> + /* Releases whatever is still queued, so the opener can finish. */
> + SAFE_CLOSE(fd_notify);
> + fd_notify = -1;
Setting fd_notify = -1 is redundant because SAFE_CLOSE() already resets its
argument to -1.
> +static void cleanup(void)
> +{
> + if (fd_notify >= 0)
> + SAFE_CLOSE(fd_notify);
> +}
Use "if (fd_notify != -1)" instead of "if (fd_notify >= 0)".
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-27 23:20 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 19:37 [LTP] [PATCH][STAGING] syscalls/fanotify26: Add test for nested permission events Edvard Conrad via ltp
2026-10-27 23:20 ` [LTP] " linuxtestproject.agent
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.