diff mbox series

[RFC,v4,26/36] WIP: bpf: Add fuse_ops struct_op programs

Message ID 20240329015351.624249-27-drosen@google.com (mailing list archive)
State New
Headers show
Series Fuse-BPF and plans on merging with Fuse Passthrough | expand

Commit Message

Daniel Rosenberg March 29, 2024, 1:53 a.m. UTC
This introduces a new struct_op type: fuse_ops. This program set
provides pre and post filters to run around fuse-bpf calls that act
directly on the lower filesystem.

The inputs are either fixed structures, or struct fuse_buffer's.

These programs are not permitted to make any changes to these fuse_buffers
unless they create a dynptr wrapper using the supplied kfunc helpers.

Fuse_buffers maintain additional state information that FUSE uses to
manage memory and determine if additional set up or checks are needed.

Signed-off-by: Daniel Rosenberg <drosen@google.com>
---
 include/linux/bpf_fuse.h | 189 +++++++++++
 kernel/bpf/Makefile      |   4 +
 kernel/bpf/bpf_fuse.c    | 716 +++++++++++++++++++++++++++++++++++++++
 kernel/bpf/btf.c         |   1 +
 kernel/bpf/verifier.c    |  10 +-
 5 files changed, 919 insertions(+), 1 deletion(-)
 create mode 100644 kernel/bpf/bpf_fuse.c
diff mbox series

Patch

diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h
index ce8b1b347496..780a7889aea2 100644
--- a/include/linux/bpf_fuse.h
+++ b/include/linux/bpf_fuse.h
@@ -30,6 +30,8 @@  struct fuse_buffer {
 #define BPF_FUSE_MODIFIED	(1 << 3) // The helper function allowed writes to the buffer
 #define BPF_FUSE_ALLOCATED	(1 << 4) // The helper function allocated the buffer
 
+extern void *bpf_fuse_get_writeable(struct fuse_buffer *arg, u64 size, bool copy);
+
 /*
  * BPF Fuse Args
  *
@@ -81,4 +83,191 @@  static inline unsigned bpf_fuse_arg_size(const struct bpf_fuse_arg *arg)
 	return arg->is_buffer ? arg->buffer->size : arg->size;
 }
 
+struct fuse_ops {
+	uint32_t (*open_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_open_in *in);
+	uint32_t (*open_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_open_in *in,
+				struct fuse_open_out *out);
+
+	uint32_t (*opendir_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_open_in *in);
+	uint32_t (*opendir_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_open_in *in,
+				struct fuse_open_out *out);
+
+	uint32_t (*create_open_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_create_in *in, struct fuse_buffer *name);
+	uint32_t (*create_open_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_create_in *in, const struct fuse_buffer *name,
+				struct fuse_entry_out *entry_out, struct fuse_open_out *out);
+
+	uint32_t (*release_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_release_in *in);
+	uint32_t (*release_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_release_in *in);
+
+	uint32_t (*releasedir_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_release_in *in);
+	uint32_t (*releasedir_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_release_in *in);
+
+	uint32_t (*flush_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_flush_in *in);
+	uint32_t (*flush_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_flush_in *in);
+
+	uint32_t (*lseek_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_lseek_in *in);
+	uint32_t (*lseek_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_lseek_in *in,
+				struct fuse_lseek_out *out);
+
+	uint32_t (*copy_file_range_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_copy_file_range_in *in);
+	uint32_t (*copy_file_range_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_copy_file_range_in *in,
+				struct fuse_write_out *out);
+
+	uint32_t (*fsync_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_fsync_in *in);
+	uint32_t (*fsync_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_fsync_in *in);
+
+	uint32_t (*dir_fsync_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_fsync_in *in);
+	uint32_t (*dir_fsync_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_fsync_in *in);
+
+	uint32_t (*getxattr_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_getxattr_in *in, struct fuse_buffer *name);
+	// if in->size > 0, use value. If in->size == 0, use out.
+	uint32_t (*getxattr_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_getxattr_in *in, const struct fuse_buffer *name,
+				struct fuse_buffer *value, struct fuse_getxattr_out *out);
+
+	uint32_t (*listxattr_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_getxattr_in *in);
+	// if in->size > 0, use value. If in->size == 0, use out.
+	uint32_t (*listxattr_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_getxattr_in *in,
+				struct fuse_buffer *value, struct fuse_getxattr_out *out);
+
+	uint32_t (*setxattr_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_setxattr_in *in, struct fuse_buffer *name,
+					struct fuse_buffer *value);
+	uint32_t (*setxattr_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_setxattr_in *in, const struct fuse_buffer *name,
+					const struct fuse_buffer *value);
+
+	uint32_t (*removexattr_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name);
+	uint32_t (*removexattr_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name);
+
+	/* Read and Write iter will likely undergo some sort of change/addition to handle changing
+	 * the data buffer passed in/out. */
+	uint32_t (*read_iter_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_read_in *in);
+	uint32_t (*read_iter_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_read_in *in,
+				struct fuse_read_iter_out *out);
+
+	uint32_t (*write_iter_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_write_in *in);
+	uint32_t (*write_iter_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_write_in *in,
+				struct fuse_write_iter_out *out);
+
+	uint32_t (*file_fallocate_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_fallocate_in *in);
+	uint32_t (*file_fallocate_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_fallocate_in *in);
+
+	uint32_t (*lookup_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name);
+	uint32_t (*lookup_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name,
+				struct fuse_entry_out *out, struct fuse_buffer *entries);
+
+	uint32_t (*mknod_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_mknod_in *in, struct fuse_buffer *name);
+	uint32_t (*mknod_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_mknod_in *in, const struct fuse_buffer *name);
+
+	uint32_t (*mkdir_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_mkdir_in *in, struct fuse_buffer *name);
+	uint32_t (*mkdir_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_mkdir_in *in, const struct fuse_buffer *name);
+
+	uint32_t (*rmdir_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name);
+	uint32_t (*rmdir_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name);
+
+	uint32_t (*rename2_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_rename2_in *in, struct fuse_buffer *old_name,
+				struct fuse_buffer *new_name);
+	uint32_t (*rename2_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_rename2_in *in, const struct fuse_buffer *old_name,
+				const struct fuse_buffer *new_name);
+
+	uint32_t (*rename_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_rename_in *in, struct fuse_buffer *old_name,
+				struct fuse_buffer *new_name);
+	uint32_t (*rename_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_rename_in *in, const struct fuse_buffer *old_name,
+				const struct fuse_buffer *new_name);
+
+	uint32_t (*unlink_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name);
+	uint32_t (*unlink_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name);
+
+	uint32_t (*link_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_link_in *in, struct fuse_buffer *name);
+	uint32_t (*link_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_link_in *in, const struct fuse_buffer *name);
+
+	uint32_t (*getattr_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_getattr_in *in);
+	uint32_t (*getattr_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_getattr_in *in,
+				struct fuse_attr_out *out);
+
+	uint32_t (*setattr_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_setattr_in *in);
+	uint32_t (*setattr_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_setattr_in *in,
+				struct fuse_attr_out *out);
+
+	uint32_t (*statfs_prefilter)(const struct bpf_fuse_meta_info *meta);
+	uint32_t (*statfs_postfilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_statfs_out *out);
+
+	//TODO: This does not allow doing anything with path
+	uint32_t (*get_link_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name);
+	uint32_t (*get_link_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name);
+
+	uint32_t (*symlink_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name, struct fuse_buffer *path);
+	uint32_t (*symlink_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name, const struct fuse_buffer *path);
+
+	uint32_t (*readdir_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_read_in *in);
+	uint32_t (*readdir_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_read_in *in,
+				struct fuse_read_out *out, struct fuse_buffer *buffer);
+
+	uint32_t (*access_prefilter)(const struct bpf_fuse_meta_info *meta,
+				struct fuse_access_in *in);
+	uint32_t (*access_postfilter)(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_access_in *in);
+
+	char name[BPF_FUSE_NAME_MAX];
+};
+
 #endif /* _BPF_FUSE_H */
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 4ce95acfcaa7..1fc22dfc8bee 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -46,3 +46,7 @@  obj-$(CONFIG_BPF_PRELOAD) += preload/
 obj-$(CONFIG_BPF_SYSCALL) += relo_core.o
 $(obj)/relo_core.o: $(srctree)/tools/lib/bpf/relo_core.c FORCE
 	$(call if_changed_rule,cc_o_c)
+
+ifeq ($(CONFIG_FUSE_BPF),y)
+obj-$(CONFIG_BPF_SYSCALL) += bpf_fuse.o
+endif
diff --git a/kernel/bpf/bpf_fuse.c b/kernel/bpf/bpf_fuse.c
new file mode 100644
index 000000000000..7ae93b5230c7
--- /dev/null
+++ b/kernel/bpf/bpf_fuse.c
@@ -0,0 +1,716 @@ 
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021 Google LLC
+
+#include <linux/filter.h>
+#include <linux/bpf.h>
+#include <linux/bpf_fuse.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+
+void *bpf_fuse_get_writeable(struct fuse_buffer *arg, u64 size, bool copy)
+{
+	void *writeable_val;
+
+	if (arg->flags & BPF_FUSE_IMMUTABLE)
+		return 0;
+
+	if (size <= arg->size &&
+			(!(arg->flags & BPF_FUSE_MUST_ALLOCATE) ||
+			  (arg->flags & BPF_FUSE_ALLOCATED))) {
+		if (arg->flags & BPF_FUSE_VARIABLE_SIZE)
+			arg->size = size;
+		arg->flags |= BPF_FUSE_MODIFIED;
+		return arg->data;
+	}
+	/* Variable sized arrays must stay below max size. If the buffer must be fixed size,
+	 * don't change the allocated size. Verifier will enforce requested size for accesses
+	 */
+	if (arg->flags & BPF_FUSE_VARIABLE_SIZE) {
+		if (size > arg->max_size)
+			return 0;
+	} else {
+		if (size > arg->size)
+			return 0;
+		size = arg->size;
+	}
+
+	if (size != arg->size && size > arg->max_size)
+		return 0;
+
+	/* If our buffer is big enough, just adjust size */
+	if (size <= arg->alloc_size) {
+		if (!copy)
+			arg->size = size;
+		arg->flags |= BPF_FUSE_MODIFIED;
+		return arg->data;
+	}
+
+	writeable_val = kzalloc(size, GFP_KERNEL);
+	if (!writeable_val)
+		return 0;
+
+	arg->alloc_size = size;
+	/* If we're copying the buffer, assume the same amount is used. If that isn't the case,
+	 * caller must change size. Otherwise, assume entirety of new buffer is used.
+	 */
+	if (copy)
+		memcpy(writeable_val, arg->data, (arg->size > size) ? size : arg->size);
+	else
+		arg->size = size;
+
+	if (arg->flags & BPF_FUSE_ALLOCATED)
+		kfree(arg->data);
+	arg->data = writeable_val;
+
+	arg->flags |= BPF_FUSE_ALLOCATED | BPF_FUSE_MODIFIED;
+
+	return arg->data;
+}
+EXPORT_SYMBOL(bpf_fuse_get_writeable);
+
+__bpf_kfunc_start_defs();
+__bpf_kfunc void bpf_fuse_get_rw_dynptr(struct fuse_buffer *buffer, struct bpf_dynptr_kern *dynptr__uninit, u64 size, bool copy)
+{
+	buffer->data = bpf_fuse_get_writeable(buffer, size, copy);
+	bpf_dynptr_init(dynptr__uninit, buffer->data, BPF_DYNPTR_TYPE_LOCAL, 0, buffer->size);
+}
+
+__bpf_kfunc void bpf_fuse_get_ro_dynptr(const struct fuse_buffer *buffer, struct bpf_dynptr_kern *dynptr__uninit)
+{
+	bpf_dynptr_init(dynptr__uninit, buffer->data, BPF_DYNPTR_TYPE_LOCAL, 0, buffer->size);
+	bpf_dynptr_set_rdonly(dynptr__uninit);
+}
+
+__bpf_kfunc uint32_t bpf_fuse_return_len(struct fuse_buffer *buffer)
+{
+	return buffer->size;
+}
+__diag_pop();
+__bpf_kfunc_end_defs();
+BTF_KFUNCS_START(fuse_kfunc_set)
+BTF_ID_FLAGS(func, bpf_fuse_get_rw_dynptr)
+BTF_ID_FLAGS(func, bpf_fuse_get_ro_dynptr)
+BTF_ID_FLAGS(func, bpf_fuse_return_len)
+BTF_KFUNCS_END(fuse_kfunc_set)
+
+static const struct btf_kfunc_id_set bpf_fuse_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set = &fuse_kfunc_set,
+};
+
+static const struct bpf_func_proto *bpf_fuse_get_func_proto(enum bpf_func_id func_id,
+							      const struct bpf_prog *prog)
+{
+	switch (func_id) {
+	default:
+		return bpf_base_func_proto(func_id, prog);
+	}
+}
+
+static bool bpf_fuse_is_valid_access(int off, int size,
+				    enum bpf_access_type type,
+				    const struct bpf_prog *prog,
+				    struct bpf_insn_access_aux *info)
+{
+	return bpf_tracing_btf_ctx_access(off, size, type, prog, info);
+}
+
+const struct btf_type *fuse_buffer_struct_type;
+
+static int bpf_fuse_btf_struct_access(struct bpf_verifier_log *log,
+					const struct bpf_reg_state *reg,
+					int off, int size)
+{
+	const struct btf_type *t;
+
+	t = btf_type_by_id(reg->btf, reg->btf_id);
+	if (t == fuse_buffer_struct_type) {
+		bpf_log(log,
+			"direct access to fuse_buffer is disallowed\n");
+		return -EACCES;
+	}
+
+	return 0;
+}
+
+static const struct bpf_verifier_ops bpf_fuse_verifier_ops = {
+	.get_func_proto		= bpf_fuse_get_func_proto,
+	.is_valid_access	= bpf_fuse_is_valid_access,
+	.btf_struct_access	= bpf_fuse_btf_struct_access,
+};
+
+static int bpf_fuse_check_member(const struct btf_type *t,
+				   const struct btf_member *member,
+				   const struct bpf_prog *prog)
+{
+	//if (is_unsupported(__btf_member_bit_offset(t, member) / 8))
+	//	return -ENOTSUPP;
+	return 0;
+}
+
+static int bpf_fuse_init_member(const struct btf_type *t,
+				  const struct btf_member *member,
+				  void *kdata, const void *udata)
+{
+	const struct fuse_ops *uf_ops;
+	struct fuse_ops *f_ops;
+	u32 moff;
+
+	uf_ops = (const struct fuse_ops *)udata;
+	f_ops = (struct fuse_ops *)kdata;
+
+	moff = __btf_member_bit_offset(t, member) / 8;
+	switch (moff) {
+	case offsetof(struct fuse_ops, name):
+		if (bpf_obj_name_cpy(f_ops->name, uf_ops->name,
+				     sizeof(f_ops->name)) <= 0)
+			return -EINVAL;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int bpf_fuse_init(struct btf *btf)
+{
+	s32 type_id;
+
+	type_id = btf_find_by_name_kind(btf, "fuse_buffer", BTF_KIND_STRUCT);
+	if (type_id < 0)
+		return -EINVAL;
+	fuse_buffer_struct_type = btf_type_by_id(btf, type_id);
+
+	return 0;
+}
+
+static struct bpf_fuse_ops_attach *fuse_reg = NULL;
+
+static int bpf_fuse_reg(void *kdata)
+{
+	if (fuse_reg)
+		return fuse_reg->fuse_register_bpf(kdata);
+	pr_warn("Cannot register fuse_ops, FUSE not found");
+	return -EOPNOTSUPP;
+}
+
+static void bpf_fuse_unreg(void *kdata)
+{
+	if(fuse_reg)
+		return fuse_reg->fuse_unregister_bpf(kdata);
+}
+
+int register_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops)
+{
+	fuse_reg = reg_ops;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(register_fuse_bpf);
+
+void unregister_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops)
+{
+	if (reg_ops == fuse_reg)
+		fuse_reg = NULL;
+	else
+		pr_warn("Refusing to unregister unregistered FUSE");
+}
+EXPORT_SYMBOL_GPL(unregister_fuse_bpf);
+
+/* CFI Stubs */
+static uint32_t bpf_fuse_default_filter(const struct bpf_fuse_meta_info *meta)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_open_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_open_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_open_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_open_in *in,
+				struct fuse_open_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_opendir_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_open_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_opendir_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_open_in *in,
+				struct fuse_open_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_create_open_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_create_in *in, struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_create_open_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_create_in *in, const struct fuse_buffer *name,
+				struct fuse_entry_out *entry_out, struct fuse_open_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_release_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_release_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_release_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_release_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_releasedir_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_release_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_releasedir_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_release_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_flush_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_flush_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_flush_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_flush_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_lseek_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_lseek_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_lseek_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_lseek_in *in,
+				struct fuse_lseek_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_copy_file_range_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_copy_file_range_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_copy_file_range_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_copy_file_range_in *in,
+				struct fuse_write_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_fsync_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_fsync_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_fsync_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_fsync_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_dir_fsync_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_fsync_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_dir_fsync_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_fsync_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_getxattr_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_getxattr_in *in, struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_getxattr_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_getxattr_in *in, const struct fuse_buffer *name,
+				struct fuse_buffer *value, struct fuse_getxattr_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_listxattr_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_getxattr_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_listxattr_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_getxattr_in *in,
+				struct fuse_buffer *value, struct fuse_getxattr_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_setxattr_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_setxattr_in *in, struct fuse_buffer *name,
+					struct fuse_buffer *value)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_setxattr_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_setxattr_in *in, const struct fuse_buffer *name,
+					const struct fuse_buffer *value)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_removexattr_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_removexattr_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_read_iter_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_read_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_read_iter_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_read_in *in,
+				struct fuse_read_iter_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_write_iter_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_write_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_write_iter_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_write_in *in,
+				struct fuse_write_iter_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_file_fallocate_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_fallocate_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_file_fallocate_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_fallocate_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_lookup_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_lookup_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name,
+				struct fuse_entry_out *out, struct fuse_buffer *entries)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_mknod_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_mknod_in *in, struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_mknod_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_mknod_in *in, const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_mkdir_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_mkdir_in *in, struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_mkdir_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_mkdir_in *in, const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_rmdir_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_rmdir_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_rename2_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_rename2_in *in, struct fuse_buffer *old_name,
+				struct fuse_buffer *new_name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_rename2_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_rename2_in *in, const struct fuse_buffer *old_name,
+				const struct fuse_buffer *new_name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_rename_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_rename_in *in, struct fuse_buffer *old_name,
+				struct fuse_buffer *new_name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_rename_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_rename_in *in, const struct fuse_buffer *old_name,
+				const struct fuse_buffer *new_name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_unlink_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_unlink_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_link_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_link_in *in, struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_link_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_link_in *in, const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_getattr_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_getattr_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_getattr_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_getattr_in *in,
+				struct fuse_attr_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_setattr_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_setattr_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_setattr_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_setattr_in *in,
+				struct fuse_attr_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_statfs_prefilter(const struct bpf_fuse_meta_info *meta)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_statfs_postfilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_statfs_out *out)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_get_link_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_get_link_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_symlink_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_buffer *name, struct fuse_buffer *path)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_symlink_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_buffer *name, const struct fuse_buffer *path)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_readdir_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_read_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_readdir_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_read_in *in,
+				struct fuse_read_out *out, struct fuse_buffer *buffer)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_access_prefilter(const struct bpf_fuse_meta_info *meta,
+				struct fuse_access_in *in)
+{
+	return 0;
+}
+
+static uint32_t bpf_fuse_access_postfilter(const struct bpf_fuse_meta_info *meta,
+				const struct fuse_access_in *in)
+{
+	return 0;
+}
+
+static struct fuse_ops __bpf_fuse_ops = {
+	.default_filter = bpf_fuse_default_filter,
+	.open_prefilter = bpf_fuse_open_prefilter,
+	.open_postfilter = bpf_fuse_open_postfilter,
+	.opendir_prefilter = bpf_fuse_opendir_prefilter,
+	.opendir_postfilter = bpf_fuse_opendir_postfilter,
+	.create_open_prefilter = bpf_fuse_create_open_prefilter,
+	.create_open_postfilter = bpf_fuse_create_open_postfilter,
+	.release_prefilter = bpf_fuse_release_prefilter,
+	.release_postfilter = bpf_fuse_release_postfilter,
+	.releasedir_prefilter = bpf_fuse_releasedir_prefilter,
+	.releasedir_postfilter = bpf_fuse_releasedir_postfilter,
+	.flush_prefilter = bpf_fuse_flush_prefilter,
+	.flush_postfilter = bpf_fuse_flush_postfilter,
+	.lseek_prefilter = bpf_fuse_lseek_prefilter,
+	.lseek_postfilter = bpf_fuse_lseek_postfilter,
+	.copy_file_range_prefilter = bpf_fuse_copy_file_range_prefilter,
+	.copy_file_range_postfilter = bpf_fuse_copy_file_range_postfilter,
+	.fsync_prefilter = bpf_fuse_fsync_prefilter,
+	.fsync_postfilter = bpf_fuse_fsync_postfilter,
+	.dir_fsync_prefilter = bpf_fuse_dir_fsync_prefilter,
+	.dir_fsync_postfilter = bpf_fuse_dir_fsync_postfilter,
+	.getxattr_prefilter = bpf_fuse_getxattr_prefilter,
+	.getxattr_postfilter = bpf_fuse_getxattr_postfilter,
+	.listxattr_prefilter = bpf_fuse_listxattr_prefilter,
+	.listxattr_postfilter = bpf_fuse_listxattr_postfilter,
+	.setxattr_prefilter = bpf_fuse_setxattr_prefilter,
+	.setxattr_postfilter = bpf_fuse_setxattr_postfilter,
+	.removexattr_prefilter = bpf_fuse_removexattr_prefilter,
+	.removexattr_postfilter = bpf_fuse_removexattr_postfilter,
+	.read_iter_prefilter = bpf_fuse_read_iter_prefilter,
+	.read_iter_postfilter = bpf_fuse_read_iter_postfilter,
+	.write_iter_prefilter = bpf_fuse_write_iter_prefilter,
+	.write_iter_postfilter = bpf_fuse_write_iter_postfilter,
+	.file_fallocate_prefilter = bpf_fuse_file_fallocate_prefilter,
+	.file_fallocate_postfilter = bpf_fuse_file_fallocate_postfilter,
+	.lookup_prefilter = bpf_fuse_lookup_prefilter,
+	.lookup_postfilter = bpf_fuse_lookup_postfilter,
+	.mknod_prefilter = bpf_fuse_mknod_prefilter,
+	.mknod_postfilter = bpf_fuse_mknod_postfilter,
+	.mkdir_prefilter = bpf_fuse_mkdir_prefilter,
+	.mkdir_postfilter = bpf_fuse_mkdir_postfilter,
+	.rmdir_prefilter = bpf_fuse_rmdir_prefilter,
+	.rmdir_postfilter = bpf_fuse_rmdir_postfilter,
+	.rename2_prefilter = bpf_fuse_rename2_prefilter,
+	.rename2_postfilter = bpf_fuse_rename2_postfilter,
+	.rename_prefilter = bpf_fuse_rename_prefilter,
+	.rename_postfilter = bpf_fuse_rename_postfilter,
+	.unlink_prefilter = bpf_fuse_unlink_prefilter,
+	.unlink_postfilter = bpf_fuse_unlink_postfilter,
+	.link_prefilter = bpf_fuse_link_prefilter,
+	.link_postfilter = bpf_fuse_link_postfilter,
+	.getattr_prefilter = bpf_fuse_getattr_prefilter,
+	.getattr_postfilter = bpf_fuse_getattr_postfilter,
+	.setattr_prefilter = bpf_fuse_setattr_prefilter,
+	.setattr_postfilter = bpf_fuse_setattr_postfilter,
+	.statfs_prefilter = bpf_fuse_statfs_prefilter,
+	.statfs_postfilter = bpf_fuse_statfs_postfilter,
+	.get_link_prefilter = bpf_fuse_get_link_prefilter,
+	.get_link_postfilter = bpf_fuse_get_link_postfilter,
+	.symlink_prefilter = bpf_fuse_symlink_prefilter,
+	.symlink_postfilter = bpf_fuse_symlink_postfilter,
+	.readdir_prefilter = bpf_fuse_readdir_prefilter,
+	.readdir_postfilter = bpf_fuse_readdir_postfilter,
+	.access_prefilter = bpf_fuse_access_prefilter,
+	.access_postfilter = bpf_fuse_access_postfilter,
+};
+
+static struct bpf_struct_ops bpf_fuse_ops = {
+	.verifier_ops = &bpf_fuse_verifier_ops,
+	.reg = bpf_fuse_reg,
+	.unreg = bpf_fuse_unreg,
+	.check_member = bpf_fuse_check_member,
+	.init_member = bpf_fuse_init_member,
+	.init = bpf_fuse_init,
+	.name = "fuse_ops",
+	.cfi_stubs = &__bpf_fuse_ops,
+	.owner = THIS_MODULE,
+};
+
+static int __init bpf_fuse_ops_init(void)
+{
+	int ret;
+
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+					 &bpf_fuse_kfunc_set);
+	ret = ret ?: register_bpf_struct_ops(&bpf_fuse_ops, fuse_ops);
+
+	return ret;
+}
+late_initcall(bpf_fuse_ops_init);
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 170d017e8e4a..29f7db457127 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -26,6 +26,7 @@ 
 #include <linux/bsearch.h>
 #include <linux/kobject.h>
 #include <linux/sysfs.h>
+#include <linux/bpf_fuse.h>
 
 #include <net/netfilter/nf_bpf_link.h>
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ee86e4d7d5fc..eb0808ed3cd9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10947,6 +10947,8 @@  enum special_kfunc_type {
 	KF_bpf_percpu_obj_drop_impl,
 	KF_bpf_throw,
 	KF_bpf_iter_css_task_new,
+	KF_bpf_fuse_get_rw_dynptr,
+	KF_bpf_fuse_get_ro_dynptr,
 };
 
 BTF_SET_START(special_kfunc_set)
@@ -10973,6 +10975,8 @@  BTF_ID(func, bpf_throw)
 #ifdef CONFIG_CGROUPS
 BTF_ID(func, bpf_iter_css_task_new)
 #endif
+BTF_ID(func, bpf_fuse_get_rw_dynptr)
+BTF_ID(func, bpf_fuse_get_ro_dynptr)
 BTF_SET_END(special_kfunc_set)
 
 BTF_ID_LIST(special_kfunc_list)
@@ -11003,6 +11007,8 @@  BTF_ID(func, bpf_iter_css_task_new)
 #else
 BTF_ID_UNUSED
 #endif
+BTF_ID(func, bpf_fuse_get_rw_dynptr)
+BTF_ID(func, bpf_fuse_get_ro_dynptr)
 
 static bool is_kfunc_ret_null(struct bpf_kfunc_call_arg_meta *meta)
 {
@@ -11793,7 +11799,9 @@  static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 					verbose(env, "verifier internal error: missing ref obj id for parent of clone\n");
 					return -EFAULT;
 				}
-			}
+			} else if (meta->func_id == special_kfunc_list[KF_bpf_fuse_get_rw_dynptr] ||
+					meta->func_id == special_kfunc_list[KF_bpf_fuse_get_ro_dynptr])
+				dynptr_arg_type |= DYNPTR_TYPE_LOCAL;
 
 			ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type, clone_ref_obj_id);
 			if (ret < 0)