[PATCH v3 fanotify 2/2] samples/fanotify: Add a sample fanotify fiter

Song Liu
2024-11-22T23:00:31Z

This sample can run in two different mode: filter mode and block mode.
In filter mode, the filter only sends events in a subtree to user space.
To use it:

[root] insmod ./filter-mod.ko
[root] mkdir -p /tmp/a/b/c/d
[root] ./filter-user /tmp/ /tmp/a/b &
Running in filter mode
[root] touch /tmp/xx # Doesn't generate event
[root]# touch /tmp/a/xxa # Doesn't generate event
[root]# touch /tmp/a/b/xxab # Generates an event
Accessing file xxab # this is the output from filter-user
[root@]# touch /tmp/a/b/c/xxabc # Generates an event
Accessing file xxabc # this is the output from filter-user

In block mode, the filter will block accesses to file in a subtree. To
use it:

[root] insmod ./filter-mod.ko
[root] mkdir -p /tmp/a/b/c/d
[root] ./filter-user /tmp/ /tmp/a/b block &
Running in block mode
[root]# dd if=/dev/zero of=/tmp/a/b/xx # this will fail
dd: failed to open '/tmp/a/b/xx': Operation not permitted

Signed-off-by: Song Liu <song@kernel.org>
---
MAINTAINERS | 1 +
samples/Kconfig | 20 ++++-
samples/Makefile | 2 +-
samples/fanotify/.gitignore | 1 +
samples/fanotify/Makefile | 5 +-
samples/fanotify/filter-mod.c | 105 ++++++++++++++++++++++++++
samples/fanotify/filter-user.c | 131 +++++++++++++++++++++++++++++++++
samples/fanotify/filter.h | 19 +++++
8 files changed, 281 insertions(+), 3 deletions(-)
create mode 100644 samples/fanotify/filter-mod.c
create mode 100644 samples/fanotify/filter-user.c
create mode 100644 samples/fanotify/filter.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 7ad507f49324..8939a48b2d99 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8658,6 +8658,7 @@ S: Maintained
F: fs/notify/fanotify/
F: include/linux/fanotify.h
F: include/uapi/linux/fanotify.h
+F: samples/fanotify/

FARADAY FOTG210 USB2 DUAL-ROLE CONTROLLER
M: Linus Walleij <linus.walleij@linaro.org>
diff --git a/samples/Kconfig b/samples/Kconfig
index b288d9991d27..9cc0a5cdf604 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -149,15 +149,33 @@ config SAMPLE_CONNECTOR
with it.
See also Documentation/driver-api/connector.rst

+config SAMPLE_FANOTIFY
+ bool "Build fanotify monitoring sample"
+ depends on FANOTIFY && CC_CAN_LINK && HEADERS_INSTALL
+ help
+ When enabled, this builds samples for fanotify.
+ There multiple samples for fanotify. Please see the
+ following configs for more details of these
+ samples.
+
config SAMPLE_FANOTIFY_ERROR
bool "Build fanotify error monitoring sample"
- depends on FANOTIFY && CC_CAN_LINK && HEADERS_INSTALL
+ depends on SAMPLE_FANOTIFY
help
When enabled, this builds an example code that uses the
FAN_FS_ERROR fanotify mechanism to monitor filesystem
errors.
See also Documentation/admin-guide/filesystem-monitoring.rst.

+config SAMPLE_FANOTIFY_FILTER
+ tristate "Build fanotify filter sample"
+ depends on SAMPLE_FANOTIFY && m
+ help
+ When enabled, this builds kernel module that contains a
+ fanotify filter handler.
+ The filter handler filters out certain filename
+ prefixes for the fanotify user.
+
config SAMPLE_HIDRAW
bool "hidraw sample"
depends on CC_CAN_LINK && HEADERS_INSTALL
diff --git a/samples/Makefile b/samples/Makefile
index b85fa64390c5..108360972626 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -6,7 +6,7 @@ subdir-$(CONFIG_SAMPLE_ANDROID_BINDERFS) += binderfs
subdir-$(CONFIG_SAMPLE_CGROUP) += cgroup
obj-$(CONFIG_SAMPLE_CONFIGFS) += configfs/
obj-$(CONFIG_SAMPLE_CONNECTOR) += connector/
-obj-$(CONFIG_SAMPLE_FANOTIFY_ERROR) += fanotify/
+obj-$(CONFIG_SAMPLE_FANOTIFY) += fanotify/
subdir-$(CONFIG_SAMPLE_HIDRAW) += hidraw
obj-$(CONFIG_SAMPLE_HW_BREAKPOINT) += hw_breakpoint/
obj-$(CONFIG_SAMPLE_KDB) += kdb/
diff --git a/samples/fanotify/.gitignore b/samples/fanotify/.gitignore
index d74593e8b2de..df75eb5b8f95 100644
--- a/samples/fanotify/.gitignore
+++ b/samples/fanotify/.gitignore
@@ -1 +1,2 @@
fs-monitor
+filter-user
diff --git a/samples/fanotify/Makefile b/samples/fanotify/Makefile
index e20db1bdde3b..c33e9460772e 100644
--- a/samples/fanotify/Makefile
+++ b/samples/fanotify/Makefile
@@ -1,5 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
-userprogs-always-y += fs-monitor
+userprogs-always-$(CONFIG_SAMPLE_FANOTIFY_ERROR) += fs-monitor

userccflags += -I usr/include -Wall

+obj-$(CONFIG_SAMPLE_FANOTIFY_FILTER) += filter-mod.o
+
+userprogs-always-$(CONFIG_SAMPLE_FANOTIFY_FILTER) += filter-user
diff --git a/samples/fanotify/filter-mod.c b/samples/fanotify/filter-mod.c
new file mode 100644
index 000000000000..eafe55b1840a
--- /dev/null
+++ b/samples/fanotify/filter-mod.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/fsnotify.h>
+#include <linux/fanotify.h>
+#include <linux/module.h>
+#include <linux/path.h>
+#include <linux/file.h>
+#include "filter.h"
+
+struct fan_filter_sample_data {
+ struct path subtree_path;
+ enum fan_filter_sample_mode mode;
+};
+
+static int sample_filter(struct fsnotify_group *group,
+ struct fanotify_filter_hook *filter_hook,
+ struct fanotify_filter_event *filter_event)
+{
+ struct fan_filter_sample_data *data;
+ struct dentry *dentry;
+
+ dentry = fsnotify_data_dentry(filter_event->data, filter_event->data_type);
+ if (!dentry)
+ return FAN_FILTER_RET_SEND_TO_USERSPACE;
+
+ data = filter_hook->data;
+
+ if (is_subdir(dentry, data->subtree_path.dentry)) {
+ if (data->mode == FAN_FILTER_SAMPLE_MODE_BLOCK)
+ return -EPERM;
+ return FAN_FILTER_RET_SEND_TO_USERSPACE;
+ }
+ return FAN_FILTER_RET_SKIP_EVENT;
+}
+
+static int sample_filter_init(struct fsnotify_group *group,
+ struct fanotify_filter_hook *filter_hook,
+ void *argp)
+{
+ struct fan_filter_sample_args *args;
+ struct fan_filter_sample_data *data;
+ struct file *file;
+ int fd;
+
+ args = (struct fan_filter_sample_args *)argp;
+ fd = args->subtree_fd;
+
+ file = fget(fd);
+ if (!file)
+ return -EBADF;
+ data = kzalloc(sizeof(struct fan_filter_sample_data), GFP_KERNEL);
+ if (!data) {
+ fput(file);
+ return -ENOMEM;
+ }
+ path_get(&file->f_path);
+ data->subtree_path = file->f_path;
+ fput(file);
+ data->mode = args->mode;
+ filter_hook->data = data;
+ return 0;
+}
+
+static void sample_filter_free(struct fanotify_filter_hook *filter_hook)
+{
+ struct fan_filter_sample_data *data = filter_hook->data;
+
+ path_put(&data->subtree_path);
+ kfree(data);
+}
+
+static struct fanotify_filter_ops fan_filter_sample_ops = {
+ .filter = sample_filter,
+ .filter_init = sample_filter_init,
+ .filter_free = sample_filter_free,
+ .name = "monitor-subtree",
+ .owner = THIS_MODULE,
+ .flags = FAN_FILTER_F_SYS_ADMIN_ONLY,
+ .init_args_size = sizeof(struct fan_filter_sample_args),
+ .desc =
+ "mode = 1: only emit events under a subtree\n"
+ "mode = 2: block accesses under a subtree",
+ .init_args =
+ "struct fan_filter_sample_args {\n"
+ " int subtree_fd;\n"
+ " enum fan_filter_sample_mode mode;\n"
+ "};",
+};
+
+static int __init fanotify_filter_sample_init(void)
+{
+ return fanotify_filter_register(&fan_filter_sample_ops);
+}
+static void __exit fanotify_filter_sample_exit(void)
+{
+ fanotify_filter_unregister(&fan_filter_sample_ops);
+}
+
+module_init(fanotify_filter_sample_init);
+module_exit(fanotify_filter_sample_exit);
+
+MODULE_AUTHOR("Song Liu");
+MODULE_DESCRIPTION("Example fanotify filter handler");
+MODULE_LICENSE("GPL");
diff --git a/samples/fanotify/filter-user.c b/samples/fanotify/filter-user.c
new file mode 100644
index 000000000000..8cdf8ff2bd6d
--- /dev/null
+++ b/samples/fanotify/filter-user.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#define _GNU_SOURCE
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/fanotify.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include "filter.h"
+
+static int total_event_cnt;
+
+static void handle_notifications(char *buffer, int len)
+{
+ struct fanotify_event_metadata *event =
+ (struct fanotify_event_metadata *) buffer;
+ struct fanotify_event_info_header *info;
+ struct fanotify_event_info_fid *fid;
+ struct file_handle *handle;
+ char *name;
+ int off;
+
+ for (; FAN_EVENT_OK(event, len); event = FAN_EVENT_NEXT(event, len)) {
+ for (off = sizeof(*event) ; off < event->event_len;
+ off += info->len) {
+ info = (struct fanotify_event_info_header *)
+ ((char *) event + off);
+ switch (info->info_type) {
+ case FAN_EVENT_INFO_TYPE_DFID_NAME:
+ fid = (struct fanotify_event_info_fid *) info;
+ handle = (struct file_handle *)&fid->handle;
+ name = (char *)handle + sizeof(*handle) + handle->handle_bytes;
+
+ printf("Accessing file %s\n", name);
+ total_event_cnt++;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct fanotify_filter_args args = {
+ .name = "monitor-subtree",
+ };
+ struct fan_filter_sample_args init_args;
+ int fanotify_fd, subtree_fd;
+ char buffer[BUFSIZ];
+ const char *msg;
+ __u64 mask;
+ int flags;
+
+ if (argc < 3) {
+ printf("Usage:\n"
+ "\t %s <mount point> <subtree to monitor>\n",
+ argv[0]);
+ return 1;
+ }
+
+ subtree_fd = open(argv[2], O_RDONLY | O_CLOEXEC);
+
+ if (subtree_fd < 0)
+ errx(1, "open subtree_fd");
+
+ init_args.subtree_fd = subtree_fd;
+
+ if (argc == 4 && strcmp(argv[3], "block") == 0)
+ init_args.mode = FAN_FILTER_SAMPLE_MODE_BLOCK;
+ else
+ init_args.mode = FAN_FILTER_SAMPLE_MODE_FILTER;
+
+ args.init_args = (__u64)&init_args;
+ args.init_args_size = sizeof(init_args);
+
+ flags = FAN_CLASS_NOTIF | FAN_REPORT_NAME | FAN_REPORT_DIR_FID;
+ mask = FAN_OPEN | FAN_ONDIR | FAN_EVENT_ON_CHILD;
+
+ if (init_args.mode == FAN_FILTER_SAMPLE_MODE_BLOCK) {
+ flags = FAN_CLASS_CONTENT;
+ mask |= FAN_OPEN_PERM;
+ }
+
+ fanotify_fd = fanotify_init(flags, O_RDWR);
+ if (fanotify_fd < 0) {
+ close(subtree_fd);
+ errx(1, "fanotify_init");
+ }
+
+
+ if (fanotify_mark(fanotify_fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
+ mask, AT_FDCWD, argv[1])) {
+ msg = "fanotify_mark";
+ goto err_out;
+ }
+
+ if (ioctl(fanotify_fd, FAN_IOC_ADD_FILTER, &args)) {
+ msg = "ioctl";
+ goto err_out;
+ }
+
+ printf("Running in %s mode\n",
+ init_args.mode == FAN_FILTER_SAMPLE_MODE_BLOCK ? "block" : "filter");
+
+ while (total_event_cnt < 10) {
+ int n = read(fanotify_fd, buffer, BUFSIZ);
+
+ if (n < 0) {
+ msg = "read";
+ goto err_out;
+ }
+
+ handle_notifications(buffer, n);
+ }
+
+ ioctl(fanotify_fd, FAN_IOC_DEL_FILTER);
+ close(fanotify_fd);
+ close(subtree_fd);
+ return 0;
+
+err_out:
+ close(fanotify_fd);
+ close(subtree_fd);
+ errx(1, msg);
+ return 0;
+}
diff --git a/samples/fanotify/filter.h b/samples/fanotify/filter.h
new file mode 100644
index 000000000000..cdb97499019a
--- /dev/null
+++ b/samples/fanotify/filter.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#ifndef _SAMPLES_FANOTIFY_FILTER_H
+#define _SAMPLES_FANOTIFY_FILTER_H
+
+enum fan_filter_sample_mode {
+ /* Only show event in the subtree */
+ FAN_FILTER_SAMPLE_MODE_FILTER = 1,
+ /* Block access to files in the subtree */
+ FAN_FILTER_SAMPLE_MODE_BLOCK = 2,
+};
+
+struct fan_filter_sample_args {
+ int subtree_fd;
+ enum fan_filter_sample_mode mode;
+};
+
+#endif /* _SAMPLES_FANOTIFY_FILTER_H */
--
2.43.5