Context |
Check |
Description |
netdev/tree_selection |
success
|
Guessing tree name failed - patch did not apply, async
|
bpf/vmtest-bpf-PR |
success
|
PR summary
|
bpf/vmtest-bpf-VM_Test-1 |
success
|
Logs for ShellCheck
|
bpf/vmtest-bpf-VM_Test-2 |
success
|
Logs for Unittests
|
bpf/vmtest-bpf-VM_Test-3 |
success
|
Logs for Validate matrix.py
|
bpf/vmtest-bpf-VM_Test-4 |
success
|
Logs for aarch64-gcc / build / build for aarch64 with gcc
|
bpf/vmtest-bpf-VM_Test-0 |
success
|
Logs for Lint
|
bpf/vmtest-bpf-VM_Test-5 |
success
|
Logs for aarch64-gcc / build-release
|
bpf/vmtest-bpf-VM_Test-6 |
success
|
Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
|
bpf/vmtest-bpf-VM_Test-7 |
success
|
Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
|
bpf/vmtest-bpf-VM_Test-8 |
success
|
Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
|
bpf/vmtest-bpf-VM_Test-9 |
success
|
Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
|
bpf/vmtest-bpf-VM_Test-10 |
success
|
Logs for aarch64-gcc / veristat
|
bpf/vmtest-bpf-VM_Test-11 |
success
|
Logs for s390x-gcc / build / build for s390x with gcc
|
bpf/vmtest-bpf-VM_Test-12 |
success
|
Logs for s390x-gcc / build-release
|
bpf/vmtest-bpf-VM_Test-13 |
success
|
Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
|
bpf/vmtest-bpf-VM_Test-14 |
success
|
Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
|
bpf/vmtest-bpf-VM_Test-15 |
success
|
Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
|
bpf/vmtest-bpf-VM_Test-16 |
success
|
Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
|
bpf/vmtest-bpf-VM_Test-17 |
success
|
Logs for s390x-gcc / veristat
|
bpf/vmtest-bpf-VM_Test-18 |
success
|
Logs for set-matrix
|
bpf/vmtest-bpf-VM_Test-19 |
success
|
Logs for x86_64-gcc / build / build for x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-20 |
success
|
Logs for x86_64-gcc / build-release
|
bpf/vmtest-bpf-VM_Test-21 |
success
|
Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-22 |
success
|
Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-23 |
success
|
Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-24 |
success
|
Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-25 |
success
|
Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-26 |
success
|
Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-27 |
success
|
Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
|
bpf/vmtest-bpf-VM_Test-28 |
success
|
Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
|
bpf/vmtest-bpf-VM_Test-29 |
success
|
Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
|
bpf/vmtest-bpf-VM_Test-30 |
success
|
Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
|
bpf/vmtest-bpf-VM_Test-31 |
success
|
Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
|
bpf/vmtest-bpf-VM_Test-32 |
success
|
Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
|
bpf/vmtest-bpf-VM_Test-33 |
success
|
Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
|
bpf/vmtest-bpf-VM_Test-34 |
success
|
Logs for x86_64-llvm-17 / veristat
|
bpf/vmtest-bpf-VM_Test-35 |
success
|
Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
|
bpf/vmtest-bpf-VM_Test-36 |
success
|
Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
|
bpf/vmtest-bpf-VM_Test-37 |
success
|
Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
|
bpf/vmtest-bpf-VM_Test-38 |
success
|
Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
|
bpf/vmtest-bpf-VM_Test-39 |
success
|
Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
|
bpf/vmtest-bpf-VM_Test-40 |
success
|
Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
|
bpf/vmtest-bpf-VM_Test-41 |
success
|
Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
|
bpf/vmtest-bpf-VM_Test-42 |
success
|
Logs for x86_64-llvm-18 / veristat
|
@@ -16,6 +16,163 @@
#include <linux/splice.h>
#include <linux/uio.h>
+static void set_in_args(struct fuse_in_arg *dst, struct bpf_fuse_arg *src)
+{
+ if (src->is_buffer) {
+ struct fuse_buffer *buffer = src->buffer;
+
+ *dst = (struct fuse_in_arg) {
+ .size = buffer->size,
+ .value = buffer->data,
+ };
+ } else {
+ *dst = (struct fuse_in_arg) {
+ .size = src->size,
+ .value = src->value,
+ };
+ }
+}
+
+static void set_out_args(struct fuse_arg *dst, struct bpf_fuse_arg *src)
+{
+ if (src->is_buffer) {
+ struct fuse_buffer *buffer = src->buffer;
+
+ // Userspace out args presents as much space as needed
+ *dst = (struct fuse_arg) {
+ .size = buffer->max_size,
+ .value = buffer->data,
+ };
+ } else {
+ *dst = (struct fuse_arg) {
+ .size = src->size,
+ .value = src->value,
+ };
+ }
+}
+
+static int get_err_in(uint32_t error, struct fuse_in_arg *ext)
+{
+ struct fuse_ext_header *xh;
+ uint32_t *err_in;
+ uint32_t err_in_size = fuse_ext_size(sizeof(*err_in));
+
+ xh = extend_arg(ext, err_in_size);
+ if (!xh)
+ return -ENOMEM;
+ xh->size = err_in_size;
+ xh->type = FUSE_ERROR_IN;
+
+ err_in = (uint32_t *)&xh[1];
+ *err_in = error;
+ return 0;
+}
+
+static int get_filter_ext(struct fuse_args *args)
+{
+ struct fuse_in_arg ext = { .size = 0, .value = NULL };
+ int err = 0;
+
+ if (args->is_filter)
+ err = get_err_in(args->error_in, &ext);
+ if (!err && ext.size) {
+ WARN_ON(args->in_numargs >= ARRAY_SIZE(args->in_args));
+ args->is_ext = true;
+ args->ext_idx = args->in_numargs++;
+ args->in_args[args->ext_idx] = ext;
+ } else {
+ kfree(ext.value);
+ }
+ return err;
+}
+
+static ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *fa,
+ unsigned short in_numargs, unsigned short out_numargs,
+ struct bpf_fuse_arg *out_arg_array, bool add_out_to_in)
+{
+ int i;
+ ssize_t res;
+
+ struct fuse_args args = {
+ .nodeid = fa->info.nodeid,
+ .opcode = fa->info.opcode,
+ .error_in = fa->info.error_in,
+ .in_numargs = in_numargs,
+ .out_numargs = out_numargs,
+ .force = !!(fa->flags & FUSE_BPF_FORCE),
+ .out_argvar = !!(fa->flags & FUSE_BPF_OUT_ARGVAR),
+ .is_lookup = !!(fa->flags & FUSE_BPF_IS_LOOKUP),
+ .is_filter = true,
+ };
+
+ /* All out args must be writeable */
+ for (i = 0; i < out_numargs; ++i) {
+ struct fuse_buffer *buffer;
+
+ if (!out_arg_array[i].is_buffer)
+ continue;
+ buffer = out_arg_array[i].buffer;
+ if (!bpf_fuse_get_writeable(buffer, buffer->max_size, true))
+ return -ENOMEM;
+ }
+
+ /* Set in args */
+ for (i = 0; i < fa->in_numargs; ++i)
+ set_in_args(&args.in_args[i], &fa->in_args[i]);
+ if (add_out_to_in) {
+ for (i = 0; i < fa->out_numargs; ++i) {
+ set_in_args(&args.in_args[fa->in_numargs + i], &fa->out_args[i]);
+ }
+ }
+
+ /* Set out args */
+ for (i = 0; i < out_numargs; ++i)
+ set_out_args(&args.out_args[i], &out_arg_array[i]);
+
+ if (out_arg_array[out_numargs - 1].is_buffer) {
+ struct fuse_buffer *buff = out_arg_array[out_numargs - 1].buffer;
+
+ if (buff->flags & BPF_FUSE_VARIABLE_SIZE)
+ args.out_argvar = true;
+ }
+ if (add_out_to_in) {
+ res = get_filter_ext(&args);
+ if (res)
+ return res;
+ }
+ res = fuse_simple_request(fm, &args);
+
+ /* update used areas of buffers */
+ for (i = 0; i < out_numargs; ++i)
+ if (out_arg_array[i].is_buffer &&
+ (out_arg_array[i].buffer->flags & BPF_FUSE_VARIABLE_SIZE))
+ out_arg_array[i].buffer->size = args.out_args[i].size;
+ fa->ret = args.ret;
+
+ free_ext_value(&args);
+
+ return res;
+}
+
+static ssize_t fuse_prefilter_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *fa)
+{
+ uint32_t out_args = fa->in_numargs;
+
+ // mkdir and company are not permitted to change the name. This should be done at lookup
+ // Thus, these can't be set by the userspace prefilter
+ if (fa->in_args[fa->in_numargs - 1].is_buffer &&
+ (fa->in_args[fa->in_numargs - 1].buffer->flags & BPF_FUSE_IMMUTABLE))
+ out_args--;
+ return fuse_bpf_simple_request(fm, fa, fa->in_numargs, out_args,
+ fa->in_args, false);
+}
+
+static ssize_t fuse_postfilter_simple_request(struct fuse_mount *fm, struct bpf_fuse_args *fa)
+{
+ return fuse_bpf_simple_request(fm, fa, fa->in_numargs + fa->out_numargs, fa->out_numargs,
+ fa->out_args, true);
+}
+
static inline void bpf_fuse_set_in_immutable(struct bpf_fuse_args *fa)
{
int i;
@@ -62,9 +219,11 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa)
({ \
struct fuse_inode *fuse_inode = get_fuse_inode(inode); \
struct fuse_ops *fuse_ops = fuse_inode->bpf_ops; \
+ struct fuse_mount *fm = get_fuse_mount(inode); \
struct bpf_fuse_args fa = { 0 }; \
bool initialized = false; \
bool handled = false; \
+ bool locked; \
ssize_t res; \
int bpf_next; \
io feo = { 0 }; \
@@ -90,6 +249,16 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa)
break; \
} \
\
+ if (bpf_next == BPF_FUSE_USER_PREFILTER) { \
+ locked = fuse_lock_inode(inode); \
+ res = fuse_prefilter_simple_request(fm, &fa); \
+ fuse_unlock_inode(inode, locked); \
+ if (res < 0) { \
+ error = res; \
+ break; \
+ } \
+ bpf_next = fa.ret; \
+ } \
bpf_fuse_set_in_immutable(&fa); \
\
error = initialize_out(&fa, &feo, args); \
@@ -119,6 +288,16 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa)
break; \
} \
\
+ if (!(bpf_next == BPF_FUSE_USER_POSTFILTER)) \
+ break; \
+ \
+ locked = fuse_lock_inode(inode); \
+ res = fuse_postfilter_simple_request(fm, &fa); \
+ fuse_unlock_inode(inode, locked); \
+ if (res < 0) { \
+ error = res; \
+ break; \
+ } \
} while (false); \
\
if (initialized && handled) { \
@@ -521,6 +521,8 @@ ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args)
BUG_ON(args->out_numargs == 0);
ret = args->out_args[args->out_numargs - 1].size;
}
+ if (args->is_filter && args->is_ext)
+ args->ret = req->out.h.error;
fuse_put_request(req);
return ret;
@@ -640,7 +640,7 @@ static int get_security_context(struct dentry *entry, umode_t mode,
return err;
}
-static void *extend_arg(struct fuse_in_arg *buf, u32 bytes)
+void *extend_arg(struct fuse_in_arg *buf, u32 bytes)
{
void *p;
u32 newlen = buf->size + bytes;
@@ -660,7 +660,7 @@ static void *extend_arg(struct fuse_in_arg *buf, u32 bytes)
return p + newlen - bytes;
}
-static u32 fuse_ext_size(size_t size)
+u32 fuse_ext_size(size_t size)
{
return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size);
}
@@ -720,7 +720,7 @@ static int get_create_ext(struct fuse_args *args,
return err;
}
-static void free_ext_value(struct fuse_args *args)
+void free_ext_value(struct fuse_args *args)
{
if (args->is_ext)
kfree(args->in_args[args->ext_idx].value);
@@ -324,6 +324,17 @@ struct fuse_page_desc {
unsigned int offset;
};
+/* To deal with bpf pre and post filters in userspace calls, we must support
+ * passing the inputs and outputs as inputs, and we must have enough space in
+ * outputs to handle all of the inputs. Plus one more for extensions.
+ */
+#define FUSE_EXTENDED_MAX_ARGS_IN (FUSE_MAX_ARGS_IN + FUSE_MAX_ARGS_OUT + 1)
+#if FUSE_MAX_ARGS_IN > FUSE_MAX_ARGS_OUT
+#define FUSE_EXTENDED_MAX_ARGS_OUT FUSE_MAX_ARGS_IN
+#else
+#define FUSE_EXTENDED_MAX_ARGS_OUT FUSE_MAX_ARGS_OUT
+#endif
+
struct fuse_args {
uint64_t nodeid;
uint32_t opcode;
@@ -342,10 +353,12 @@ struct fuse_args {
bool page_replace:1;
bool may_block:1;
bool is_ext:1;
+ bool is_filter:1;
bool is_lookup:1;
bool via_ioctl:1;
- struct fuse_in_arg in_args[3];
- struct fuse_arg out_args[2];
+ uint32_t ret;
+ struct fuse_in_arg in_args[FUSE_EXTENDED_MAX_ARGS_IN];
+ struct fuse_arg out_args[FUSE_EXTENDED_MAX_ARGS_OUT];
void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
};
@@ -1194,6 +1207,22 @@ void fuse_request_end(struct fuse_req *req);
void fuse_abort_conn(struct fuse_conn *fc);
void fuse_wait_aborted(struct fuse_conn *fc);
+/**
+ * Allocated/Reallocate extended header information
+ * Returns pointer to start of most recent allocation
+ */
+void *extend_arg(struct fuse_in_arg *buf, u32 bytes);
+
+/**
+ * Returns adjusted size field for extensions
+ */
+u32 fuse_ext_size(size_t size);
+
+/**
+ * Free allocated extended header information
+ */
+void free_ext_value(struct fuse_args *args);
+
/**
* Invalidate inode attributes
*/
@@ -64,6 +64,7 @@ struct bpf_fuse_args {
uint32_t in_numargs;
uint32_t out_numargs;
uint32_t flags;
+ uint32_t ret;
struct bpf_fuse_arg in_args[FUSE_MAX_ARGS_IN];
struct bpf_fuse_arg out_args[FUSE_MAX_ARGS_OUT];
};
@@ -566,6 +566,7 @@ enum fuse_ext_type {
/* Types 0..31 are reserved for fuse_secctx_header */
FUSE_MAX_NR_SECCTX = 31,
FUSE_EXT_GROUPS = 32,
+ FUSE_ERROR_IN = 33,
};
enum fuse_opcode {
This allows fuse-bpf to call out to userspace to handle pre and post filters. Any of the inputs may be changed by the prefilter, so we must handle up to 3 outputs. For the postfilter, our inputs include the output arguments, so we must handle up to 5 inputs. Additionally, we add an extension for passing the return code of the backing call to the postfilter, adding one additional possible output bringing the total to 4. As long as you don't request both pre-filter and post-filter in userspace, we will end up doing fewer round trips to userspace. Signed-off-by: Daniel Rosenberg <drosen@google.com> --- fs/fuse/backing.c | 179 ++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.c | 2 + fs/fuse/dir.c | 6 +- fs/fuse/fuse_i.h | 33 ++++++- include/linux/bpf_fuse.h | 1 + include/uapi/linux/fuse.h | 1 + 6 files changed, 217 insertions(+), 5 deletions(-)