aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@redhat.com>2025-08-05 20:31:53 +0200
committerBernd Schubert <bernd@bsbernd.com>2025-09-13 16:16:06 +0200
commit2317d8602334093ea0c88efee2e6ac326d42adc7 (patch)
treee22290de3d73692f9bebec77282ff17a96703e9c /lib
parent3ff309ea9efa8fc95274d35a96aa5700f0a0d6c1 (diff)
downloadlibfuse-2317d8602334093ea0c88efee2e6ac326d42adc7.tar.gz
libfuse: fix COPY_FILE_RANGE interface
The FUSE protocol uses struct fuse_write_out to convey the return value of copy_file_range, which is restricted to uint32_t. But the COPY_FILE_RANGE interface supports a 64-bit size copies. Currently the number of bytes copied is silently truncated to 32-bit, which is unfortunate at best. Implement the COPY_FILE_RANGE_64 interface which is identical to the old one, except the number of bytes copied is returned in a 64-bit value. The library interface remains the same. If the kernel does not support the new interface or the server is running as a 32-bit process, limit the copy size to size to UINT_MAX - 4096. Edit by Bernd: Keep ioctl_64bit and add use new bit is_copy_file_range_64 to keep flags separated from each other - easier code readability IMO. Reported-by: Florian Weimer <fweimer@redhat.com> Closes: https://lore.kernel.org/all/lhuh5ynl8z5.fsf@oldenburg.str.redhat.com/ Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/fuse_i.h1
-rw-r--r--lib/fuse_lowlevel.c66
2 files changed, 62 insertions, 5 deletions
diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index 0d0e637..d3d86d4 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -40,6 +40,7 @@ struct fuse_req {
int interrupted;
unsigned int ioctl_64bit : 1;
unsigned int is_uring : 1;
+ unsigned int is_copy_file_range_64 : 1;
union {
struct {
uint64_t unique;
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index e4544df..1e45315 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -589,7 +589,7 @@ int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
return send_reply_ok(req, &arg, sizeof(arg));
}
-int fuse_reply_write(fuse_req_t req, size_t count)
+static int do_fuse_reply_write(fuse_req_t req, size_t count)
{
struct fuse_write_out arg;
@@ -599,6 +599,28 @@ int fuse_reply_write(fuse_req_t req, size_t count)
return send_reply_ok(req, &arg, sizeof(arg));
}
+static int do_fuse_reply_copy(fuse_req_t req, size_t count)
+{
+ struct fuse_copy_file_range_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.bytes_copied = count;
+
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_write(fuse_req_t req, size_t count)
+{
+ /*
+ * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit
+ * variant.
+ */
+ if (req->is_copy_file_range_64)
+ return do_fuse_reply_copy(req, count);
+ else
+ return do_fuse_reply_write(req, count);
+}
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
{
return send_reply_ok(req, buf, size);
@@ -2403,11 +2425,9 @@ static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
_do_fallocate(req, nodeid, inarg, NULL);
}
-static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
- const void *op_in, const void *in_payload)
+static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in,
+ const struct fuse_copy_file_range_in *arg)
{
- (void)in_payload;
- const struct fuse_copy_file_range_in *arg = op_in;
struct fuse_file_info fi_in, fi_out;
memset(&fi_in, 0, sizeof(fi_in));
@@ -2424,12 +2444,46 @@ static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
fuse_reply_err(req, ENOSYS);
}
+static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+ const void *op_in, const void *in_payload)
+{
+ const struct fuse_copy_file_range_in *arg = op_in;
+ struct fuse_copy_file_range_in arg_tmp;
+
+ (void) in_payload;
+ /* fuse_write_out can only handle 32bit copy size */
+ if (arg->len > 0xfffff000) {
+ arg_tmp = *arg;
+ arg_tmp.len = 0xfffff000;
+ arg = &arg_tmp;
+ }
+ copy_file_range_common(req, nodeid_in, arg);
+}
+
static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
const void *inarg)
{
_do_copy_file_range(req, nodeid_in, inarg, NULL);
}
+static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+ const void *op_in, const void *in_payload)
+{
+ (void) in_payload;
+ req->is_copy_file_range_64 = 1;
+ /* Limit size on 32bit userspace to avoid conversion overflow */
+ if (sizeof(size_t) == 4)
+ _do_copy_file_range(req, nodeid_in, op_in, NULL);
+ else
+ copy_file_range_common(req, nodeid_in, op_in);
+}
+
+static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+ const void *inarg)
+{
+ _do_copy_file_range_64(req, nodeid_in, inarg, NULL);
+}
+
/*
* Note that the uint64_t offset in struct fuse_lseek_in is derived from
* linux kernel loff_t and is therefore signed.
@@ -3378,6 +3432,7 @@ static struct {
[FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
[FUSE_RENAME2] = { do_rename2, "RENAME2" },
[FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+ [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" },
[FUSE_LSEEK] = { do_lseek, "LSEEK" },
[FUSE_STATX] = { do_statx, "STATX" },
[CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
@@ -3433,6 +3488,7 @@ static struct {
[FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" },
[FUSE_RENAME2] = { _do_rename2, "RENAME2" },
[FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" },
+ [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" },
[FUSE_LSEEK] = { _do_lseek, "LSEEK" },
[FUSE_STATX] = { _do_statx, "STATX" },
[CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" },