diff mbox series

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

Message ID 20241122225958.1775625-3-song@kernel.org (mailing list archive)
State New
Headers show
Series Fanotify in kernel filter | expand

Commit Message

Song Liu Nov. 22, 2024, 10:59 p.m. UTC
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

Comments

Amir Goldstein Nov. 24, 2024, 5:07 a.m. UTC | #1
On Sat, Nov 23, 2024 at 12:00 AM Song Liu <song@kernel.org> wrote:
>
> 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);
> +}
> +

Hi Song,

This example looks fine but it raises a question.
This filter will keep the mount of subtree_path busy until the group is closed
or the filter is detached.
This is probably fine for many services that keep the mount busy anyway.

But what if this wasn't the intention?
What if an Anti-malware engine that watches all mounts wanted to use that
for configuring some ignore/block subtree filters?

One way would be to use a is_subtree() variant that looks for a
subtree root inode
number and then verifies it with a subtree root fid.
A production subtree filter will need to use a variant of is_subtree()
anyway that
looks for a set of subtree root inodes, because doing a loop of is_subtree() for
multiple paths is a no go.

Don't need to change anything in the example, unless other people
think that we do need to set a better example to begin with...

Thanks,
Amir.
Song Liu Nov. 24, 2024, 6:59 p.m. UTC | #2
> On Nov 23, 2024, at 9:07 PM, Amir Goldstein <amir73il@gmail.com> wrote:
> 
> On Sat, Nov 23, 2024 at 12:00 AM Song Liu <song@kernel.org> wrote:

[...]

>> +}
>> +
>> +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);
>> +}
>> +
> 
> Hi Song,
> 
> This example looks fine but it raises a question.
> This filter will keep the mount of subtree_path busy until the group is closed
> or the filter is detached.
> This is probably fine for many services that keep the mount busy anyway.
> 
> But what if this wasn't the intention?
> What if an Anti-malware engine that watches all mounts wanted to use that
> for configuring some ignore/block subtree filters?
> 
> One way would be to use a is_subtree() variant that looks for a
> subtree root inode
> number and then verifies it with a subtree root fid.
> A production subtree filter will need to use a variant of is_subtree()
> anyway that
> looks for a set of subtree root inodes, because doing a loop of is_subtree() for
> multiple paths is a no go.

Maybe some cache mechanism will be sufficient (and maybe also the
best we can do) in this case? 

Thanks,
Song

> 
> Don't need to change anything in the example, unless other people
> think that we do need to set a better example to begin with...
diff mbox series

Patch

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 */