* [PATCH RFC 1/2] fuse: add compound command to combine multiple requests
2025-12-23 12:05 [PATCH RFC 0/2] fuse: compound commands Horst Birthelmer
@ 2025-12-23 12:05 ` Horst Birthelmer
2025-12-23 12:05 ` [PATCH RFC 2/2] fuse: add an implementation of open+getattr Horst Birthelmer
2025-12-23 17:46 ` [syzbot ci] Re: fuse: compound commands syzbot ci
2 siblings, 0 replies; 4+ messages in thread
From: Horst Birthelmer @ 2025-12-23 12:05 UTC (permalink / raw)
To: Miklos Szeredi, Bernd Schubert
Cc: linux-kernel, linux-fsdevel, Horst Birthelmer
For a FUSE_COMPOUND we add a header that contains information
about how many commands there are in the compound and about the
size of the expected result. This will make the interpretation
in libfuse easier, since we can preallocate the whole result.
Then we append the requests that belong to this compound.
The API for the compound command has:
fuse_compound_alloc()
fuse_compound_add()
fuse_compound_request()
fuse_compound_free()
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
fs/fuse/Makefile | 2 +-
fs/fuse/compound.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dev.c | 25 ++++
fs/fuse/fuse_i.h | 14 ++
include/uapi/linux/fuse.h | 37 +++++
5 files changed, 445 insertions(+), 1 deletion(-)
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 22ad9538dfc4..4c09038ef995 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o
obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
fuse-y := trace.o # put trace.o first so we see ftrace errors sooner
-fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
+fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o compound.o
fuse-y += iomode.o
fuse-$(CONFIG_FUSE_DAX) += dax.o
fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c
new file mode 100644
index 000000000000..f86232d55cc7
--- /dev/null
+++ b/fs/fuse/compound.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025
+ *
+ * This file implements compound operations for FUSE, allowing multiple
+ * operations to be batched into a single request to reduce round trips
+ * between kernel and userspace.
+ */
+
+#include "fuse_i.h"
+
+/*
+ * Compound request builder and state tracker
+ *
+ * This structure manages the lifecycle of a compound FUSE request, from building
+ * the request by serializing multiple operations into a single buffer, through
+ * sending it to userspace, to parsing the compound response back into individual
+ * operation results.
+ */
+struct fuse_compound_req {
+ struct fuse_mount *fm;
+ struct fuse_compound_in compound_header;
+ struct fuse_compound_out result_header;
+
+ size_t total_size; /* Total size of serialized operations */
+ char *buffer; /* Buffer holding serialized requests */
+ size_t buffer_pos; /* Current write position in buffer */
+ size_t buffer_size; /* Total allocated buffer size */
+
+ size_t total_expected_out_size; /* Sum of expected output sizes */
+
+ /* Per-operation error codes */
+ int op_errors[FUSE_MAX_COMPOUND_OPS];
+ /* Original fuse_args for response parsing */
+ struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
+
+ bool parsed; /* Prevent double-parsing of response */
+};
+
+struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm,
+ uint32_t flags)
+{
+ struct fuse_compound_req *compound;
+
+ compound = kzalloc(sizeof(*compound), GFP_KERNEL);
+ if (!compound)
+ return ERR_PTR(-ENOMEM);
+
+ compound->fm = fm;
+ compound->compound_header.flags = flags;
+ compound->buffer_size = PAGE_SIZE;
+ compound->buffer = kvmalloc(compound->buffer_size, GFP_KERNEL);
+ if (!compound->buffer) {
+ kfree(compound);
+ return ERR_PTR(-ENOMEM);
+ }
+ return compound;
+}
+
+void fuse_compound_free(struct fuse_compound_req *compound)
+{
+ if (!compound)
+ return;
+
+ kvfree(compound->buffer);
+ kfree(compound);
+}
+
+static int fuse_compound_validate_header(struct fuse_compound_req *compound)
+{
+ struct fuse_compound_in *in_header = &compound->compound_header;
+ size_t offset = 0;
+ int i;
+
+ if (compound->buffer_pos > compound->buffer_size)
+ return -EINVAL;
+
+ if (!compound || !compound->buffer)
+ return -EINVAL;
+
+ if (compound->buffer_pos < sizeof(struct fuse_in_header))
+ return -EINVAL;
+
+ if (in_header->count == 0 || in_header->count > FUSE_MAX_COMPOUND_OPS)
+ return -EINVAL;
+
+ for (i = 0; i < in_header->count; i++) {
+ const struct fuse_in_header *op_hdr;
+
+ if (offset + sizeof(struct fuse_in_header) >
+ compound->buffer_pos) {
+ pr_info_ratelimited("FUSE: compound operation %d header extends beyond buffer (offset %zu + header size %zu > buffer pos %zu)\n",
+ i, offset,
+ sizeof(struct fuse_in_header),
+ compound->buffer_pos);
+ return -EINVAL;
+ }
+
+ op_hdr = (const struct fuse_in_header *)(compound->buffer +
+ offset);
+
+ if (op_hdr->len < sizeof(struct fuse_in_header)) {
+ pr_info_ratelimited("FUSE: compound operation %d has invalid length %u (minimum %zu bytes)\n",
+ i, op_hdr->len,
+ sizeof(struct fuse_in_header));
+ return -EINVAL;
+ }
+
+ if (offset + op_hdr->len > compound->buffer_pos) {
+ pr_info_ratelimited("FUSE: compound operation %d extends beyond buffer (offset %zu + length %u > buffer pos %zu)\n",
+ i, offset, op_hdr->len,
+ compound->buffer_pos);
+ return -EINVAL;
+ }
+
+ if (op_hdr->opcode == 0 || op_hdr->opcode == FUSE_COMPOUND) {
+ pr_info_ratelimited("FUSE: compound operation %d has invalid opcode %u (cannot be 0 or FUSE_COMPOUND)\n",
+ i, op_hdr->opcode);
+ return -EINVAL;
+ }
+
+ if (op_hdr->nodeid == 0) {
+ pr_info_ratelimited("FUSE: compound operation %d has invalid node ID 0\n",
+ i);
+ return -EINVAL;
+ }
+
+ offset += op_hdr->len;
+ }
+
+ if (offset != compound->buffer_pos) {
+ pr_info_ratelimited("FUSE: compound buffer size mismatch (calculated %zu bytes, actual %zu bytes)\n",
+ offset, compound->buffer_pos);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int fuse_compound_add(struct fuse_compound_req *compound,
+ struct fuse_args *args)
+{
+ struct fuse_in_header *hdr;
+ size_t args_size = 0;
+ size_t needed_size;
+ size_t expected_out_size = 0;
+ int i;
+
+ if (!compound ||
+ compound->compound_header.count >= FUSE_MAX_COMPOUND_OPS)
+ return -EINVAL;
+
+ if (args->in_pages)
+ return -EINVAL;
+
+ for (i = 0; i < args->in_numargs; i++)
+ args_size += args->in_args[i].size;
+
+ for (i = 0; i < args->out_numargs; i++)
+ expected_out_size += args->out_args[i].size;
+
+ needed_size = sizeof(struct fuse_in_header) + args_size;
+
+ if (compound->buffer_pos + needed_size > compound->buffer_size) {
+ size_t new_size = max(compound->buffer_size * 2,
+ compound->buffer_pos + needed_size);
+ char *new_buffer;
+
+ new_size = round_up(new_size, PAGE_SIZE);
+ new_buffer = kvrealloc(compound->buffer, new_size,
+ GFP_KERNEL);
+ if (!new_buffer)
+ return -ENOMEM;
+ compound->buffer = new_buffer;
+ compound->buffer_size = new_size;
+ }
+
+ /* Build request header */
+ hdr = (struct fuse_in_header *)(compound->buffer +
+ compound->buffer_pos);
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->len = needed_size;
+ hdr->opcode = args->opcode;
+ hdr->nodeid = args->nodeid;
+ hdr->uid = from_kuid(compound->fm->fc->user_ns, current_fsuid());
+ hdr->gid = from_kgid(compound->fm->fc->user_ns, current_fsgid());
+ hdr->pid = pid_nr_ns(task_pid(current), compound->fm->fc->pid_ns);
+ hdr->unique = fuse_get_unique(&compound->fm->fc->iq);
+ compound->buffer_pos += sizeof(*hdr);
+
+ for (i = 0; i < args->in_numargs; i++) {
+ memcpy(compound->buffer + compound->buffer_pos,
+ args->in_args[i].value, args->in_args[i].size);
+ compound->buffer_pos += args->in_args[i].size;
+ }
+
+ compound->total_expected_out_size += expected_out_size;
+
+ /* Store args for response parsing */
+ compound->op_args[compound->compound_header.count] = args;
+
+ compound->compound_header.count++;
+ compound->total_size += needed_size;
+
+ return 0;
+}
+
+static void *fuse_copy_response_data(struct fuse_args *args,
+ char *response_data)
+{
+ size_t copied = 0;
+ int arg_idx;
+
+ for (arg_idx = 0; arg_idx < args->out_numargs; arg_idx++) {
+ struct fuse_arg current_arg = args->out_args[arg_idx];
+ size_t arg_size;
+
+ /* Last argument with out_pages: copy to pages */
+ if (arg_idx == args->out_numargs - 1 && args->out_pages) {
+ /*
+ * External payload (in the last out arg)
+ * is not supported at the moment
+ */
+ return response_data;
+ }
+
+ arg_size = current_arg.size;
+
+ if (current_arg.value && arg_size > 0) {
+ memcpy(current_arg.value,
+ (char *)response_data + copied,
+ arg_size);
+ copied += arg_size;
+ }
+ }
+
+ return (char *)response_data + copied;
+}
+
+int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx)
+{
+ return compound->op_errors[op_idx];
+}
+
+static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound,
+ int op_index, void *op_out_data,
+ void *response_end)
+{
+ struct fuse_out_header *op_hdr = op_out_data;
+ struct fuse_args *args = compound->op_args[op_index];
+
+ if (op_hdr->len < sizeof(struct fuse_out_header))
+ return NULL;
+
+ /* Check if the entire operation response fits in the buffer */
+ if ((char *)op_out_data + op_hdr->len > (char *)response_end)
+ return NULL;
+
+ if (op_hdr->error != 0)
+ compound->op_errors[op_index] = op_hdr->error;
+
+ if (args && op_hdr->len > sizeof(struct fuse_out_header))
+ return fuse_copy_response_data(args, op_out_data +
+ sizeof(struct fuse_out_header));
+
+ /* No response data, just advance past the header */
+ return (char *)op_out_data + op_hdr->len;
+}
+
+static int fuse_compound_parse_resp(struct fuse_compound_req *compound,
+ uint32_t count, void *response,
+ size_t response_size)
+{
+ void *op_out_data = response;
+ void *response_end = (char *)response + response_size;
+ int i;
+
+ if (compound->parsed)
+ return 0;
+
+ if (!response || response_size < sizeof(struct fuse_out_header))
+ return -EIO;
+
+ for (i = 0; i < count && i < compound->result_header.count; i++) {
+ op_out_data = fuse_compound_parse_one_op(compound, i,
+ op_out_data,
+ response_end);
+ if (!op_out_data)
+ return -EIO;
+ }
+
+ compound->parsed = true;
+ return 0;
+}
+
+ssize_t fuse_compound_send(struct fuse_compound_req *compound)
+{
+ struct fuse_args args = {
+ .opcode = FUSE_COMPOUND,
+ .nodeid = 0,
+ .in_numargs = 2,
+ .out_numargs = 2,
+ .out_argvar = true,
+ };
+ size_t expected_response_size;
+ size_t total_buffer_size;
+ size_t actual_response_size;
+ void *resp_payload;
+ ssize_t ret;
+
+ if (!compound) {
+ pr_info_ratelimited("FUSE: compound request is NULL in %s\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (compound->compound_header.count == 0) {
+ pr_info_ratelimited("FUSE: compound request contains no operations\n");
+ return -EINVAL;
+ }
+
+ expected_response_size = compound->total_expected_out_size;
+ total_buffer_size = expected_response_size +
+ (compound->compound_header.count *
+ sizeof(struct fuse_out_header));
+
+ resp_payload = kvmalloc(total_buffer_size, GFP_KERNEL | __GFP_ZERO);
+ if (!resp_payload)
+ return -ENOMEM;
+
+ compound->compound_header.result_size = expected_response_size;
+
+ args.in_args[0].size = sizeof(compound->compound_header);
+ args.in_args[0].value = &compound->compound_header;
+ args.in_args[1].size = compound->buffer_pos;
+ args.in_args[1].value = compound->buffer;
+
+ args.out_args[0].size = sizeof(compound->result_header);
+ args.out_args[0].value = &compound->result_header;
+ args.out_args[1].size = total_buffer_size;
+ args.out_args[1].value = resp_payload;
+
+ ret = fuse_compound_validate_header(compound);
+ if (ret)
+ goto out;
+
+ ret = fuse_compound_request(compound->fm, &args);
+ if (ret)
+ goto out;
+
+ actual_response_size = args.out_args[1].size;
+
+ if (actual_response_size < sizeof(struct fuse_compound_out)) {
+ pr_info_ratelimited("FUSE: compound response too small (%zu bytes, minimum %zu bytes)\n",
+ actual_response_size,
+ sizeof(struct fuse_compound_out));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = fuse_compound_parse_resp(compound, compound->result_header.count,
+ (char *)resp_payload,
+ actual_response_size);
+out:
+ kvfree(resp_payload);
+ return ret;
+}
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 6d59cbc877c6..2d89ca69308f 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -660,6 +660,31 @@ static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args)
__set_bit(FR_ASYNC, &req->flags);
}
+ssize_t fuse_compound_request(
+ struct fuse_mount *fm, struct fuse_args *args)
+{
+ struct fuse_req *req;
+ ssize_t ret;
+
+ req = fuse_get_req(&invalid_mnt_idmap, fm, false);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ fuse_args_to_req(req, args);
+
+ if (!args->noreply)
+ __set_bit(FR_ISREPLY, &req->flags);
+
+ __fuse_request_send(req);
+ ret = req->out.h.error;
+ if (!ret && args->out_argvar) {
+ BUG_ON(args->out_numargs == 0);
+ ret = args->out_args[args->out_numargs - 1].size;
+ }
+ fuse_put_request(req);
+ return ret;
+}
+
ssize_t __fuse_simple_request(struct mnt_idmap *idmap,
struct fuse_mount *fm,
struct fuse_args *args)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 7f16049387d1..86253517f59b 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1273,6 +1273,20 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap,
int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args,
gfp_t gfp_flags);
+/**
+ * Compound request API
+ */
+struct fuse_compound_req;
+ssize_t fuse_compound_request(struct fuse_mount *fm, struct fuse_args *args);
+ssize_t fuse_compound_send(struct fuse_compound_req *compound);
+
+struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, uint32_t flags);
+int fuse_compound_add(struct fuse_compound_req *compound,
+ struct fuse_args *args);
+int fuse_compound_get_error(struct fuse_compound_req *compound,
+ int op_idx);
+void fuse_compound_free(struct fuse_compound_req *compound);
+
/**
* Assign a unique id to a fuse request
*/
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index c13e1f9a2f12..848323acecdc 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -664,6 +664,13 @@ enum fuse_opcode {
FUSE_STATX = 52,
FUSE_COPY_FILE_RANGE_64 = 53,
+ /* A compound request works like multiple simple requests.
+ * This is a special case for calls that can be combined atomic on the
+ * fuse server. If the server actually does atomically execute the command is
+ * left to the fuse server implementation.
+ */
+ FUSE_COMPOUND = 101,
+
/* CUSE specific operations */
CUSE_INIT = 4096,
@@ -1245,6 +1252,36 @@ struct fuse_supp_groups {
uint32_t groups[];
};
+#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
+
+/*
+ * Compound request header
+ *
+ * This header is followed by the fuse requests
+ */
+struct fuse_compound_in {
+ uint32_t count; /* Number of operations */
+ uint32_t flags; /* Compound flags */
+
+ /* Total size of all results.
+ * This is needed for preallocating the whole result for all
+ * commands in this compound.
+ */
+ uint32_t result_size;
+ uint64_t reserved;
+};
+
+/*
+ * Compound response header
+ *
+ * This header is followed by complete fuse responses
+ */
+struct fuse_compound_out {
+ uint32_t count; /* Number of results */
+ uint32_t flags; /* Result flags */
+ uint64_t reserved;
+};
+
/**
* Size of the ring buffer header
*/
--
2.51.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH RFC 2/2] fuse: add an implementation of open+getattr
2025-12-23 12:05 [PATCH RFC 0/2] fuse: compound commands Horst Birthelmer
2025-12-23 12:05 ` [PATCH RFC 1/2] fuse: add compound command to combine multiple requests Horst Birthelmer
@ 2025-12-23 12:05 ` Horst Birthelmer
2025-12-23 17:46 ` [syzbot ci] Re: fuse: compound commands syzbot ci
2 siblings, 0 replies; 4+ messages in thread
From: Horst Birthelmer @ 2025-12-23 12:05 UTC (permalink / raw)
To: Miklos Szeredi, Bernd Schubert
Cc: linux-kernel, linux-fsdevel, Horst Birthelmer
The discussion about compound commands in fuse was
started over an argument to add a new operation that
will open a file and return its attributes in the same operation.
Here is a demonstration of that use case with compound commands.
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
fs/fuse/file.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++--------
fs/fuse/fuse_i.h | 6 ++-
fs/fuse/inode.c | 6 +++
fs/fuse/ioctl.c | 2 +-
4 files changed, 111 insertions(+), 18 deletions(-)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 01bc894e9c2b..fd7f90f9f676 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -126,8 +126,74 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
}
}
+static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid,
+ int flags, int opcode,
+ struct fuse_file *ff,
+ struct fuse_attr_out *outattrp,
+ struct fuse_open_out *outargp)
+{
+ struct fuse_compound_req *compound;
+ struct fuse_args open_args = {}, getattr_args = {};
+ struct fuse_open_in open_in = {};
+ struct fuse_getattr_in getattr_in = {};
+ int err;
+
+ /* Build compound request with flag to execute in the given order */
+ compound = fuse_compound_alloc(fm, 0);
+ if (IS_ERR(compound))
+ return PTR_ERR(compound);
+
+ /* Add OPEN */
+ open_in.flags = flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
+ if (!fm->fc->atomic_o_trunc)
+ open_in.flags &= ~O_TRUNC;
+
+ if (fm->fc->handle_killpriv_v2 &&
+ (open_in.flags & O_TRUNC) && !capable(CAP_FSETID)) {
+ open_in.open_flags |= FUSE_OPEN_KILL_SUIDGID;
+ }
+ open_args.opcode = opcode;
+ open_args.nodeid = nodeid;
+ open_args.in_numargs = 1;
+ open_args.in_args[0].size = sizeof(open_in);
+ open_args.in_args[0].value = &open_in;
+ open_args.out_numargs = 1;
+ open_args.out_args[0].size = sizeof(struct fuse_open_out);
+ open_args.out_args[0].value = outargp;
+
+ err = fuse_compound_add(compound, &open_args);
+ if (err)
+ goto out;
+
+ /* Add GETATTR */
+ getattr_args.opcode = FUSE_GETATTR;
+ getattr_args.nodeid = nodeid;
+ getattr_args.in_numargs = 1;
+ getattr_args.in_args[0].size = sizeof(getattr_in);
+ getattr_args.in_args[0].value = &getattr_in;
+ getattr_args.out_numargs = 1;
+ getattr_args.out_args[0].size = sizeof(struct fuse_attr_out);
+ getattr_args.out_args[0].value = outattrp;
+
+ err = fuse_compound_add(compound, &getattr_args);
+ if (err)
+ goto out;
+
+ err = fuse_compound_send(compound);
+ if (err)
+ goto out;
+
+ ff->fh = outargp->fh;
+ ff->open_flags = outargp->open_flags;
+
+out:
+ fuse_compound_free(compound);
+ return err;
+}
+
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
- unsigned int open_flags, bool isdir)
+ struct inode *inode,
+ unsigned int open_flags, bool isdir)
{
struct fuse_conn *fc = fm->fc;
struct fuse_file *ff;
@@ -153,23 +219,41 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
if (open) {
/* Store outarg for fuse_finish_open() */
struct fuse_open_out *outargp = &ff->args->open_outarg;
- int err;
+ int err = -ENOSYS;
+
+ if (inode && fc->compound_open_getattr) {
+ struct fuse_attr_out attr_outarg;
+ err = fuse_compound_open_getattr(fm, nodeid, open_flags,
+ opcode, ff, &attr_outarg, outargp);
+ if (!err)
+ fuse_change_attributes(inode, &attr_outarg.attr, NULL,
+ ATTR_TIMEOUT(&attr_outarg),
+ fuse_get_attr_version(fc));
+ }
+ if (err == -ENOSYS) {
+ err = fuse_send_open(fm, nodeid, open_flags, opcode, outargp);
- err = fuse_send_open(fm, nodeid, open_flags, opcode, outargp);
- if (!err) {
- ff->fh = outargp->fh;
- ff->open_flags = outargp->open_flags;
- } else if (err != -ENOSYS) {
- fuse_file_free(ff);
- return ERR_PTR(err);
- } else {
- if (isdir) {
+ if (!err) {
+ ff->fh = outargp->fh;
+ ff->open_flags = outargp->open_flags;
+ }
+ }
+
+ if (err) {
+ if (err != -ENOSYS) {
+ /* err is not ENOSYS */
+ fuse_file_free(ff);
+ return ERR_PTR(err);
+ } else {
/* No release needed */
kfree(ff->args);
ff->args = NULL;
- fc->no_opendir = 1;
- } else {
- fc->no_open = 1;
+
+ /* we don't have open */
+ if (isdir)
+ fc->no_opendir = 1;
+ else
+ fc->no_open = 1;
}
}
}
@@ -185,11 +269,10 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
bool isdir)
{
- struct fuse_file *ff = fuse_file_open(fm, nodeid, file->f_flags, isdir);
+ struct fuse_file *ff = fuse_file_open(fm, nodeid, file_inode(file), file->f_flags, isdir);
if (!IS_ERR(ff))
file->private_data = ff;
-
return PTR_ERR_OR_ZERO(ff);
}
EXPORT_SYMBOL_GPL(fuse_do_open);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 86253517f59b..98af019037c3 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -924,6 +924,9 @@ struct fuse_conn {
/* Use io_uring for communication */
unsigned int io_uring;
+ /* Does the filesystem support compound operations? */
+ unsigned int compound_open_getattr:1;
+
/** Maximum stack depth for passthrough backing files */
int max_stack_depth;
@@ -1557,7 +1560,8 @@ void fuse_file_io_release(struct fuse_file *ff, struct inode *inode);
/* file.c */
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
- unsigned int open_flags, bool isdir);
+ struct inode *inode,
+ unsigned int open_flags, bool isdir);
void fuse_file_release(struct inode *inode, struct fuse_file *ff,
unsigned int open_flags, fl_owner_t id, bool isdir);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 819e50d66622..a5fd721be96d 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -991,6 +991,12 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
fc->blocked = 0;
fc->initialized = 0;
fc->connected = 1;
+
+ /* pretend fuse server supports compound operations
+ * until it tells us otherwise.
+ */
+ fc->compound_open_getattr = 1;
+
atomic64_set(&fc->attr_version, 1);
atomic64_set(&fc->evict_ctr, 1);
get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key));
diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c
index fdc175e93f74..07a02e47b2c3 100644
--- a/fs/fuse/ioctl.c
+++ b/fs/fuse/ioctl.c
@@ -494,7 +494,7 @@ static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode)
if (!S_ISREG(inode->i_mode) && !isdir)
return ERR_PTR(-ENOTTY);
- return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir);
+ return fuse_file_open(fm, get_node_id(inode), NULL, O_RDONLY, isdir);
}
static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff)
--
2.51.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [syzbot ci] Re: fuse: compound commands
2025-12-23 12:05 [PATCH RFC 0/2] fuse: compound commands Horst Birthelmer
2025-12-23 12:05 ` [PATCH RFC 1/2] fuse: add compound command to combine multiple requests Horst Birthelmer
2025-12-23 12:05 ` [PATCH RFC 2/2] fuse: add an implementation of open+getattr Horst Birthelmer
@ 2025-12-23 17:46 ` syzbot ci
2 siblings, 0 replies; 4+ messages in thread
From: syzbot ci @ 2025-12-23 17:46 UTC (permalink / raw)
To: bschubert, hbirthelmer, hbirthelmer, linux-fsdevel, linux-kernel,
miklos
Cc: syzbot, syzkaller-bugs
syzbot ci has tested the following series
[v1] fuse: compound commands
https://lore.kernel.org/all/20251223-fuse-compounds-upstream-v1-0-7bade663947b@ddn.com
* [PATCH RFC 1/2] fuse: add compound command to combine multiple requests
* [PATCH RFC 2/2] fuse: add an implementation of open+getattr
and found the following issue:
general protection fault in fuse_dir_open
Full report is available here:
https://ci.syzbot.org/series/11a1edb1-479b-4bb9-a173-a5aef4cc9455
***
general protection fault in fuse_dir_open
tree: torvalds
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base: 9094662f6707d1d4b53d18baba459604e8bb0783
arch: amd64
compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8
config: https://ci.syzbot.org/builds/0d046766-2385-4f04-bae0-5a8d6df01c3a/config
C repro: https://ci.syzbot.org/findings/027737d8-b067-4bd4-90f6-5b236fb8420b/c_repro
syz repro: https://ci.syzbot.org/findings/027737d8-b067-4bd4-90f6-5b236fb8420b/syz_repro
Oops: general protection fault, probably for non-canonical address 0xdffffc000000000b: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000058-0x000000000000005f]
CPU: 1 UID: 0 PID: 5985 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:fuse_dir_open+0x14e/0x220 fs/fuse/dir.c:1900
Code: eb a7 4d 8d 7e 58 4c 89 f8 48 c1 e8 03 42 80 3c 28 00 74 08 4c 89 ff e8 e0 77 f3 fe 4d 8b 3f 49 83 c7 2c 4d 89 fc 49 c1 ec 03 <43> 0f b6 04 2c 84 c0 0f 85 82 00 00 00 41 8b 2f 89 ee 83 e6 14 31
RSP: 0018:ffffc90003907860 EFLAGS: 00010203
RAX: 1ffff11022f0688b RBX: ffff8881b9320000 RCX: 0000000000000000
RDX: ffff88810afbba80 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 0000000000000000 R08: ffffffff8f822b77 R09: 1ffffffff1f0456e
R10: dffffc0000000000 R11: fffffbfff1f0456f R12: 000000000000000b
R13: dffffc0000000000 R14: ffff888117834400 R15: 000000000000005c
FS: 00007fa1fc9506c0(0000) GS:ffff8882a9e1f000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fa1fb1fdfc8 CR3: 000000016abde000 CR4: 00000000000006f0
Call Trace:
<TASK>
do_dentry_open+0x7ce/0x1420 fs/open.c:962
vfs_open+0x3b/0x340 fs/open.c:1094
do_open fs/namei.c:4628 [inline]
path_openat+0x340e/0x3dd0 fs/namei.c:4787
do_filp_open+0x1fa/0x410 fs/namei.c:4814
do_sys_openat2+0x121/0x200 fs/open.c:1430
do_sys_open fs/open.c:1436 [inline]
__do_sys_openat fs/open.c:1452 [inline]
__se_sys_openat fs/open.c:1447 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1447
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xec/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fa1fbb8e010
Code: 48 89 44 24 20 75 93 44 89 54 24 0c e8 69 95 02 00 44 8b 54 24 0c 89 da 48 89 ee 41 89 c0 bf 9c ff ff ff b8 01 01 00 00 0f 05 <48> 3d 00 f0 ff ff 77 38 44 89 c7 89 44 24 0c e8 bc 95 02 00 8b 44
RSP: 002b:00007fa1fc94fdf0 EFLAGS: 00000293 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 0000000000010000 RCX: 00007fa1fbb8e010
RDX: 0000000000010000 RSI: 00002000000000c0 RDI: 00000000ffffff9c
RBP: 00002000000000c0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000293 R12: 00002000000000c0
R13: 00007fa1fc94feb0 R14: 0000000000000000 R15: 0000200000002280
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:fuse_dir_open+0x14e/0x220 fs/fuse/dir.c:1900
Code: eb a7 4d 8d 7e 58 4c 89 f8 48 c1 e8 03 42 80 3c 28 00 74 08 4c 89 ff e8 e0 77 f3 fe 4d 8b 3f 49 83 c7 2c 4d 89 fc 49 c1 ec 03 <43> 0f b6 04 2c 84 c0 0f 85 82 00 00 00 41 8b 2f 89 ee 83 e6 14 31
RSP: 0018:ffffc90003907860 EFLAGS: 00010203
RAX: 1ffff11022f0688b RBX: ffff8881b9320000 RCX: 0000000000000000
RDX: ffff88810afbba80 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 0000000000000000 R08: ffffffff8f822b77 R09: 1ffffffff1f0456e
R10: dffffc0000000000 R11: fffffbfff1f0456f R12: 000000000000000b
R13: dffffc0000000000 R14: ffff888117834400 R15: 000000000000005c
FS: 00007fa1fc9506c0(0000) GS:ffff8882a9e1f000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fa1fb1fdfc8 CR3: 000000016abde000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
0: eb a7 jmp 0xffffffa9
2: 4d 8d 7e 58 lea 0x58(%r14),%r15
6: 4c 89 f8 mov %r15,%rax
9: 48 c1 e8 03 shr $0x3,%rax
d: 42 80 3c 28 00 cmpb $0x0,(%rax,%r13,1)
12: 74 08 je 0x1c
14: 4c 89 ff mov %r15,%rdi
17: e8 e0 77 f3 fe call 0xfef377fc
1c: 4d 8b 3f mov (%r15),%r15
1f: 49 83 c7 2c add $0x2c,%r15
23: 4d 89 fc mov %r15,%r12
26: 49 c1 ec 03 shr $0x3,%r12
* 2a: 43 0f b6 04 2c movzbl (%r12,%r13,1),%eax <-- trapping instruction
2f: 84 c0 test %al,%al
31: 0f 85 82 00 00 00 jne 0xb9
37: 41 8b 2f mov (%r15),%ebp
3a: 89 ee mov %ebp,%esi
3c: 83 e6 14 and $0x14,%esi
3f: 31 .byte 0x31
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
^ permalink raw reply [flat|nested] 4+ messages in thread