Re: [PATCH] fanotify: selftests for fanotify permission events

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Jun 23, 2025 at 9:45 PM Ibrahim Jirdeh <ibrahimjirdeh@xxxxxxxx> wrote:
>
> This adds selftests which exercise generating / responding to
> permission events. They requre root privileges since

                                         ^^^^ require
> FAN_CLASS_PRE_CONTENT requires it.
>
> Signed-off-by: Ibrahim Jirdeh <ibrahimjirdeh@xxxxxxxx>
> ---
>  tools/testing/selftests/Makefile              |   1 +
>  .../selftests/filesystems/fanotify/.gitignore |   2 +
>  .../selftests/filesystems/fanotify/Makefile   |   8 +
>  .../filesystems/fanotify/fanotify_perm_test.c | 386 ++++++++++++++++++
>  4 files changed, 397 insertions(+)
>  create mode 100644 tools/testing/selftests/filesystems/fanotify/.gitignore
>  create mode 100644 tools/testing/selftests/filesystems/fanotify/Makefile
>  create mode 100644 tools/testing/selftests/filesystems/fanotify/fanotify_perm_test.c
>

Hi Ibrahim,

As a general comment, I do not mind having diverse testing
methods, but just wanted to make sure that you know that we
usually write fanotify tests to new features in LTP.

LTP vs. selftests have their pros and cons, but both bring value
and add test coverage.
selftests would not have been my first choice for this particular test,
because it is so similar to tests already existing in LTP, e.g.:
https://github.com/linux-test-project/ltp/blob/master/testcases/kernel/syscalls/fanotify/fanotify24.c

but I suppose that testing the full functionality of event listener fd handover
might be easier to implement with the selftest infrastructure.
Anyway, I will not require you to use one test suite or the other if you have
a preference.

One of the perks of using selftests is that you can post the test patch in
the same series of the feature patches, but you haven't made use of this perk,
so you may want to do that for v2.

Other than that, the test looks good, so you may add:

Reviewed-by: Amir Goldstein <amir73il@xxxxxxxxx>

Thanks,
Amir.


> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 339b31e6a6b5..9cae71edca9f 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -32,6 +32,7 @@ TARGETS += fchmodat2
>  TARGETS += filesystems
>  TARGETS += filesystems/binderfs
>  TARGETS += filesystems/epoll
> +TARGETS += filesystems/fanotify
>  TARGETS += filesystems/fat
>  TARGETS += filesystems/overlayfs
>  TARGETS += filesystems/statmount
> diff --git a/tools/testing/selftests/filesystems/fanotify/.gitignore b/tools/testing/selftests/filesystems/fanotify/.gitignore
> new file mode 100644
> index 000000000000..a9f51c9aca9f
> --- /dev/null
> +++ b/tools/testing/selftests/filesystems/fanotify/.gitignore
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +fanotify_perm_test
> diff --git a/tools/testing/selftests/filesystems/fanotify/Makefile b/tools/testing/selftests/filesystems/fanotify/Makefile
> new file mode 100644
> index 000000000000..931bedd989b9
> --- /dev/null
> +++ b/tools/testing/selftests/filesystems/fanotify/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
> +LDLIBS += -lcap
> +
> +TEST_GEN_PROGS := fanotify_perm_test
> +
> +include ../../lib.mk
> diff --git a/tools/testing/selftests/filesystems/fanotify/fanotify_perm_test.c b/tools/testing/selftests/filesystems/fanotify/fanotify_perm_test.c
> new file mode 100644
> index 000000000000..87d718323b1a
> --- /dev/null
> +++ b/tools/testing/selftests/filesystems/fanotify/fanotify_perm_test.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#define _GNU_SOURCE
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <sys/syscall.h>
> +#include <limits.h>
> +
> +#include "../../kselftest_harness.h"
> +
> +// Needed for linux/fanotify.h
> +#ifndef __kernel_fsid_t
> +typedef struct {
> +       int val[2];
> +} __kernel_fsid_t;
> +#endif
> +#include <sys/fanotify.h>
> +
> +static const char test_dir_templ[] = "/tmp/fanotify_perm_test.XXXXXX";
> +
> +FIXTURE(fanotify)
> +{
> +       char test_dir[sizeof(test_dir_templ)];
> +       char test_dir2[sizeof(test_dir_templ)];
> +       int fan_fd;
> +       int fan_fd2;
> +       char test_file_path[PATH_MAX];
> +       char test_file_path2[PATH_MAX];
> +       char test_exec_path[PATH_MAX];
> +};
> +
> +FIXTURE_SETUP(fanotify)
> +{
> +       int ret;
> +
> +       /* Setup test directories and files */
> +       strcpy(self->test_dir, test_dir_templ);
> +       ASSERT_NE(mkdtemp(self->test_dir), NULL);
> +       strcpy(self->test_dir2, test_dir_templ);
> +       ASSERT_NE(mkdtemp(self->test_dir2), NULL);
> +
> +       snprintf(self->test_file_path, PATH_MAX, "%s/test_file",
> +                self->test_dir);
> +       snprintf(self->test_file_path2, PATH_MAX, "%s/test_file2",
> +                self->test_dir2);
> +       snprintf(self->test_exec_path, PATH_MAX, "%s/test_exec",
> +                self->test_dir);
> +
> +       ret = open(self->test_file_path, O_CREAT | O_RDWR, 0644);
> +       ASSERT_GE(ret, 0);
> +       ASSERT_EQ(write(ret, "test data", 9), 9);
> +       close(ret);
> +
> +       ret = open(self->test_file_path2, O_CREAT | O_RDWR, 0644);
> +       ASSERT_GE(ret, 0);
> +       ASSERT_EQ(write(ret, "test data2", 9), 9);
> +       close(ret);
> +
> +       ret = open(self->test_exec_path, O_CREAT | O_RDWR, 0755);
> +       ASSERT_GE(ret, 0);
> +       ASSERT_EQ(write(ret, "#!/bin/bash\necho test\n", 22), 22);
> +       close(ret);
> +
> +       self->fan_fd = fanotify_init(
> +               FAN_CLASS_PRE_CONTENT | FAN_NONBLOCK | FAN_CLOEXEC, O_RDONLY);
> +       ASSERT_GE(self->fan_fd, 0);
> +
> +       self->fan_fd2 = fanotify_init(FAN_CLASS_PRE_CONTENT | FAN_NONBLOCK |
> +                                             FAN_CLOEXEC | FAN_ENABLE_EVENT_ID,
> +                                     O_RDONLY);
> +       ASSERT_GE(self->fan_fd2, 0);
> +
> +       /* Mark the directories for permission events */
> +       ret = fanotify_mark(self->fan_fd, FAN_MARK_ADD,
> +                           FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM |
> +                                   FAN_EVENT_ON_CHILD,
> +                           AT_FDCWD, self->test_dir);
> +       ASSERT_EQ(ret, 0);
> +
> +       ret = fanotify_mark(self->fan_fd2, FAN_MARK_ADD,
> +                           FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM |
> +                                   FAN_EVENT_ON_CHILD,
> +                           AT_FDCWD, self->test_dir2);
> +       ASSERT_EQ(ret, 0);
> +}
> +
> +FIXTURE_TEARDOWN(fanotify)
> +{
> +       /* Clean up test directory and files */
> +       if (self->fan_fd > 0)
> +               close(self->fan_fd);
> +       if (self->fan_fd2 > 0)
> +               close(self->fan_fd2);
> +
> +       EXPECT_EQ(unlink(self->test_file_path), 0);
> +       EXPECT_EQ(unlink(self->test_file_path2), 0);
> +       EXPECT_EQ(unlink(self->test_exec_path), 0);
> +       EXPECT_EQ(rmdir(self->test_dir), 0);
> +       EXPECT_EQ(rmdir(self->test_dir2), 0);
> +}
> +
> +static struct fanotify_event_metadata *get_event(int fd)
> +{
> +       struct fanotify_event_metadata *metadata;
> +       ssize_t len;
> +       char buf[256];
> +
> +       len = read(fd, buf, sizeof(buf));
> +       if (len <= 0)
> +               return NULL;
> +
> +       metadata = (void *)buf;
> +       if (!FAN_EVENT_OK(metadata, len))
> +               return NULL;
> +
> +       return metadata;
> +}
> +
> +static int respond_to_event(int fd, struct fanotify_event_metadata *metadata,
> +                           uint32_t response, bool useEventId)
> +{
> +       struct fanotify_response resp;
> +
> +       if (useEventId) {
> +               resp.event_id = metadata->event_id;
> +       } else {
> +               resp.fd = metadata->fd;
> +       }
> +       resp.response = response;
> +
> +       return write(fd, &resp, sizeof(resp));
> +}
> +
> +static void verify_event(struct __test_metadata *const _metadata,
> +                        struct fanotify_event_metadata *event,
> +                        uint64_t expect_mask, int expect_pid)
> +{
> +       ASSERT_NE(event, NULL);
> +       ASSERT_EQ(event->mask, expect_mask);
> +
> +       if (expect_pid > 0)
> +               ASSERT_EQ(event->pid, expect_pid);
> +}
> +
> +TEST_F(fanotify, open_perm_allow)
> +{
> +       struct fanotify_event_metadata *event;
> +       int fd, ret;
> +       pid_t child;
> +
> +       child = fork();
> +       ASSERT_GE(child, 0);
> +
> +       if (child == 0) {
> +               /* Try to open the file - this should trigger FAN_OPEN_PERM */
> +               fd = open(self->test_file_path, O_RDONLY);
> +               if (fd < 0)
> +                       exit(EXIT_FAILURE);
> +               close(fd);
> +               exit(EXIT_SUCCESS);
> +       }
> +
> +       usleep(100000);
> +       event = get_event(self->fan_fd);
> +       verify_event(_metadata, event, FAN_OPEN_PERM, child);
> +
> +       /* Allow the open operation */
> +       close(event->fd);
> +       ret = respond_to_event(self->fan_fd, event, FAN_ALLOW,
> +                              false /* useEventId */);
> +       ASSERT_EQ(ret, sizeof(struct fanotify_response));
> +
> +       int status;
> +
> +       ASSERT_EQ(waitpid(child, &status, 0), child);
> +       ASSERT_EQ(WEXITSTATUS(status), EXIT_SUCCESS);
> +}
> +
> +TEST_F(fanotify, open_perm_deny)
> +{
> +       struct fanotify_event_metadata *event;
> +       int ret;
> +       pid_t child;
> +
> +       child = fork();
> +       ASSERT_GE(child, 0);
> +
> +       if (child == 0) {
> +               /* Try to open the file - this should trigger FAN_OPEN_PERM */
> +               int fd = open(self->test_file_path, O_RDONLY);
> +
> +               /* If open succeeded, this is an error as we expect it to be denied */
> +               if (fd >= 0) {
> +                       close(fd);
> +                       exit(EXIT_FAILURE);
> +               }
> +
> +               /* Verify the expected error */
> +               if (errno == EPERM)
> +                       exit(EXIT_SUCCESS);
> +
> +               exit(EXIT_FAILURE);
> +       }
> +
> +       usleep(100000);
> +       event = get_event(self->fan_fd);
> +       verify_event(_metadata, event, FAN_OPEN_PERM, child);
> +
> +       /* Deny the open operation */
> +       close(event->fd);
> +       ret = respond_to_event(self->fan_fd, event, FAN_DENY,
> +                              false /* useEventId */);
> +       ASSERT_EQ(ret, sizeof(struct fanotify_response));
> +
> +       int status;
> +
> +       ASSERT_EQ(waitpid(child, &status, 0), child);
> +       ASSERT_EQ(WEXITSTATUS(status), EXIT_SUCCESS);
> +}
> +
> +TEST_F(fanotify, exec_perm_allow)
> +{
> +       struct fanotify_event_metadata *event;
> +       int ret;
> +       pid_t child;
> +
> +       child = fork();
> +       ASSERT_GE(child, 0);
> +
> +       if (child == 0) {
> +               /* Try to execute the file - this should trigger FAN_OPEN_EXEC_PERM */
> +               execl(self->test_exec_path, "test_exec", NULL);
> +
> +               /* If we get here, execl failed */
> +               exit(EXIT_FAILURE);
> +       }
> +
> +       usleep(100000);
> +       event = get_event(self->fan_fd);
> +       verify_event(_metadata, event, FAN_OPEN_EXEC_PERM, child);
> +
> +       /* Allow the exec operation + ignore subsequent events */
> +       ASSERT_GE(fanotify_mark(self->fan_fd,
> +                               FAN_MARK_ADD | FAN_MARK_IGNORED_MASK |
> +                                       FAN_MARK_IGNORED_SURV_MODIFY,
> +                               FAN_OPEN_PERM | FAN_OPEN_EXEC_PERM, event->fd,
> +                               NULL),
> +                 0);
> +       close(event->fd);
> +       ret = respond_to_event(self->fan_fd, event, FAN_ALLOW,
> +                              false /* useEventId */);
> +       ASSERT_EQ(ret, sizeof(struct fanotify_response));
> +
> +       int status;
> +
> +       ASSERT_EQ(waitpid(child, &status, 0), child);
> +       ASSERT_EQ(WIFEXITED(status), 1);
> +}
> +
> +TEST_F(fanotify, exec_perm_deny)
> +{
> +       struct fanotify_event_metadata *event;
> +       int ret;
> +       pid_t child;
> +
> +       child = fork();
> +       ASSERT_GE(child, 0);
> +
> +       if (child == 0) {
> +               /* Try to execute the file - this should trigger FAN_OPEN_EXEC_PERM */
> +               execl(self->test_exec_path, "test_exec", NULL);
> +
> +               /* If execl failed with EPERM, that's what we expect */
> +               if (errno == EPERM)
> +                       exit(EXIT_SUCCESS);
> +
> +               exit(EXIT_FAILURE);
> +       }
> +
> +       usleep(100000);
> +       event = get_event(self->fan_fd);
> +       verify_event(_metadata, event, FAN_OPEN_EXEC_PERM, child);
> +
> +       /* Deny the exec operation */
> +       close(event->fd);
> +       ret = respond_to_event(self->fan_fd, event, FAN_DENY,
> +                              false /* useEventId */);
> +       ASSERT_EQ(ret, sizeof(struct fanotify_response));
> +
> +       int status;
> +
> +       ASSERT_EQ(waitpid(child, &status, 0), child);
> +       ASSERT_EQ(WEXITSTATUS(status), EXIT_SUCCESS);
> +}
> +
> +TEST_F(fanotify, default_response)
> +{
> +       struct fanotify_event_metadata *event;
> +       int ret;
> +       pid_t child;
> +       struct fanotify_response resp;
> +
> +       /* Set default response to deny */
> +       resp.fd = FAN_NOFD;
> +       resp.response = FAN_DENY | FAN_DEFAULT;
> +       ret = write(self->fan_fd, &resp, sizeof(resp));
> +       ASSERT_EQ(ret, sizeof(resp));
> +
> +       child = fork();
> +       ASSERT_GE(child, 0);
> +
> +       if (child == 0) {
> +               close(self->fan_fd);
> +               /* Try to open the file - this should trigger FAN_OPEN_PERM */
> +               int fd = open(self->test_file_path, O_RDONLY);
> +
> +               /* If open succeeded, this is an error as we expect it to be denied */
> +               if (fd >= 0) {
> +                       close(fd);
> +                       exit(EXIT_FAILURE);
> +               }
> +
> +               /* Verify the expected error */
> +               if (errno == EPERM)
> +                       exit(EXIT_SUCCESS);
> +
> +               exit(EXIT_FAILURE);
> +       }
> +
> +       usleep(100000);
> +       event = get_event(self->fan_fd);
> +       verify_event(_metadata, event, FAN_OPEN_PERM, child);
> +
> +       /* Close fanotify group to return default response (DENY) */
> +       close(self->fan_fd);
> +       self->fan_fd = -1;
> +
> +       int status;
> +
> +       ASSERT_EQ(waitpid(child, &status, 0), child);
> +       ASSERT_EQ(WEXITSTATUS(status), EXIT_SUCCESS);
> +}
> +
> +TEST_F(fanotify, respond_via_event_id)
> +{
> +       struct fanotify_event_metadata *event;
> +       int fd, ret;
> +       pid_t child;
> +
> +       child = fork();
> +       ASSERT_GE(child, 0);
> +
> +       if (child == 0) {
> +               /* Try to open the file - this should trigger FAN_OPEN_PERM */
> +               fd = open(self->test_file_path2, O_RDONLY);
> +               if (fd < 0)
> +                       exit(EXIT_FAILURE);
> +               close(fd);
> +               exit(EXIT_SUCCESS);
> +       }
> +
> +       usleep(100000);
> +       event = get_event(self->fan_fd2);
> +       verify_event(_metadata, event, FAN_OPEN_PERM, child);
> +       ASSERT_EQ(event->event_id, 1);
> +
> +       /* Allow the open operation */
> +       close(event->fd);
> +       ret = respond_to_event(self->fan_fd2, event, FAN_ALLOW,
> +                              true /* useEventId */);
> +       ASSERT_EQ(ret, sizeof(struct fanotify_response));
> +
> +       int status;
> +
> +       ASSERT_EQ(waitpid(child, &status, 0), child);
> +       ASSERT_EQ(WEXITSTATUS(status), EXIT_SUCCESS);
> +}
> +
> +TEST_HARNESS_MAIN
> --
> 2.47.1
>





[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux