ANDROID: fuse-bpf: Fix recursion in fuse_copy_file_range

fuse_copy_file_range would recurse if the input file was backed, but the
output file was not

Simply return EXDEV to signal to the vfs layer to try alternate routes

Bug: 414328228
Test: fuse_test (note causes identical recursion without the kernel fix)
Change-Id: I1b62bc69f57ce00639b283e1d9269e2a9468bd14
Signed-off-by: Paul Lawrence <paullawrence@google.com>
This commit is contained in:
Paul Lawrence
2025-04-30 12:22:34 -07:00
committed by Treehugger Robot
parent 5838b5ac0a
commit 88680fe19e
3 changed files with 202 additions and 3 deletions

View File

@@ -496,9 +496,7 @@ int fuse_copy_file_range_backing(struct fuse_bpf_args *fa, struct file *file_in,
if (backing_file_out)
return vfs_copy_file_range(backing_file_in, fci->off_in, backing_file_out,
fci->off_out, fci->len, fci->flags);
else
return vfs_copy_file_range(file_in, pos_in, file_out, pos_out, len,
flags);
return -EXDEV;
}
void *fuse_copy_file_range_finalize(struct fuse_bpf_args *fa, struct file *file_in, loff_t pos_in,

View File

@@ -2212,6 +2212,180 @@ out:
return result;
}
/**
* Test that fuse passthrough correctly traverses a mount point on the lower fs
*/
static int copy_file_range_test(const char *mount_dir)
{
const char *backing = "backing";
const char *user = "user";
const char *src = "source";
const char *dst = "dest";
int fd = -1;
char buf[4096] = {};
int src_fd = -1;
int bpf_fd = -1;
int fuse_dev = -1;
int result = TEST_FAILURE;
int backing_src = -1;
int backing_dst = -1;
int user_src = -1;
int user_dst = -1;
FUSE_DECLARE_DAEMON;
TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(backing)), 0777));
TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(user)), 0777));
TEST(fd = s_creat(s_pathn(3, s(ft_src), s(backing), s(src)), 0777),
fd != -1);
TESTEQUAL(write(fd, buf, sizeof(buf)), sizeof(buf));
TESTSYSCALL(close(fd));
TEST(fd = s_creat(s_pathn(3, s(ft_src), s(backing), s(dst)), 0777),
fd != -1);
TESTEQUAL(write(fd, buf, sizeof(buf)), sizeof(buf));
TESTSYSCALL(close(fd));
fd = -1;
TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC),
src_fd != -1);
TESTEQUAL(install_elf_bpf("test_bpf.bpf", "copy_file_range_test",
&bpf_fd, NULL, NULL), 0);
TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0);
FUSE_START_DAEMON();
if (action) {
off_t off_in, off_out;
TEST(backing_src = s_open(s_pathn(3, s(mount_dir), s(backing),
s(src)),
O_RDONLY),
backing_src != -1);
TEST(backing_dst = s_open(s_pathn(3, s(mount_dir), s(backing),
s(dst)),
O_RDWR),
backing_dst != -1);
TEST(user_src = s_open(s_pathn(3, s(mount_dir), s(user),
s(src)),
O_RDONLY),
user_src != -1);
TEST(user_dst = s_open(s_pathn(3, s(mount_dir), s(user),
s(dst)),
O_RDWR),
user_dst != -1);
off_in = 0; off_out = 0;
TESTEQUAL(copy_file_range(backing_src, &off_in, backing_dst,
&off_out, sizeof(buf), 0),
sizeof(buf));
off_in = 0; off_out = 0;
TESTEQUAL(copy_file_range(user_src, &off_in,
backing_dst, &off_out, sizeof(buf), 0),
sizeof(buf));
off_in = 0; off_out = 0;
TESTEQUAL(copy_file_range(backing_src, &off_in, user_dst,
&off_out, sizeof(buf), 0),
sizeof(buf));
off_in = 0; off_out = 0;
TESTEQUAL(copy_file_range(user_src, &off_in, user_dst, &off_out,
sizeof(buf), 0),
sizeof(buf));
TESTSYSCALL(close(backing_src));
backing_src = -1;
TESTSYSCALL(close(backing_dst));
backing_dst = -1;
TESTSYSCALL(close(user_src));
user_src = -1;
TESTSYSCALL(close(user_dst));
user_dst = -1;
} else {
DECL_FUSE_IN(open);
DECL_FUSE_IN(copy_file_range);
DECL_FUSE_IN(read);
DECL_FUSE_IN(getxattr);
DECL_FUSE_IN(write);
DECL_FUSE_IN(flush);
DECL_FUSE_IN(release);
TESTFUSELOOKUP(src, 0);
TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) {
.nodeid = 2,
.generation = 1,
.attr.ino = 100,
.attr.size = 4096,
.attr.blksize = 512,
.attr.mode = S_IFREG | 0777,
}));
TESTFUSEIN(FUSE_OPEN, open_in);
TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) {
.fh = 2,
.open_flags = open_in->flags,
}));
TESTFUSEINNULL(FUSE_CANONICAL_PATH);
TESTFUSEOUTREAD("ignored", 7);
TESTFUSELOOKUP(dst, 0);
TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) {
.nodeid = 3,
.generation = 1,
.attr.ino = 100,
.attr.size = 4096,
.attr.blksize = 512,
.attr.mode = S_IFREG | 0777,
}));
TESTFUSEIN(FUSE_OPEN, open_in);
TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) {
.fh = 3,
.open_flags = open_in->flags,
}));
TESTFUSEINNULL(FUSE_CANONICAL_PATH);
TESTFUSEOUTREAD("ignored", 7);
TESTFUSEIN(FUSE_COPY_FILE_RANGE, copy_file_range_in);
TESTEQUAL(copy_file_range_in->fh_in, 2);
TESTEQUAL(copy_file_range_in->fh_out, 0);
TESTFUSEOUTERROR(-EXDEV);
TESTFUSEIN(FUSE_READ, read_in);
TESTFUSEOUTREAD(buf, sizeof(buf));
TESTFUSEINEXT(FUSE_GETXATTR, getxattr_in, 20);
TESTFUSEDIROUTREAD(&((struct fuse_getxattr_out) {.size = 0}),
"", 0);
TESTFUSEINEXT(FUSE_WRITE, write_in, sizeof(buf));
TESTFUSEOUT1(fuse_write_out, ((struct fuse_write_out) {
.size = sizeof(buf)
}));
TESTFUSEINEXT(FUSE_GETXATTR, getxattr_in, 20);
TESTFUSEDIROUTREAD(&((struct fuse_getxattr_out) {.size = 0}),
"", 0);
TESTFUSEIN(FUSE_COPY_FILE_RANGE, copy_file_range_in);
TESTEQUAL(copy_file_range_in->fh_in, 2);
TESTEQUAL(copy_file_range_in->fh_out, 3);
TESTFUSEOUT1(fuse_write_out, ((struct fuse_write_out) {
.size = sizeof(buf)
}));
TESTFUSEIN(FUSE_FLUSH, flush_in);
TESTFUSEOUTEMPTY();
TESTFUSEIN(FUSE_RELEASE, release_in);
TESTFUSEOUTEMPTY();
TESTFUSEIN(FUSE_FLUSH, flush_in);
TESTFUSEOUTEMPTY();
TESTFUSEIN(FUSE_RELEASE, release_in);
TESTFUSEOUTEMPTY();
exit(TEST_SUCCESS);
}
FUSE_END_DAEMON();
umount(mount_dir);
close(bpf_fd);
close(src_fd);
close(fuse_dev);
close(fd);
return result;
}
static void parse_range(const char *ranges, bool *run_test, size_t tests)
{
size_t i;
@@ -2344,6 +2518,7 @@ int main(int argc, char *argv[])
MAKE_TEST(bpf_test_readahead),
MAKE_TEST(splice_test),
MAKE_TEST(bpf_test_follow_mounts),
MAKE_TEST(copy_file_range_test),
};
#undef MAKE_TEST

View File

@@ -553,3 +553,29 @@ int mkdirremovebpf_test(struct fuse_bpf_args *fa)
return FUSE_BPF_BACKING;
}
}
SEC("copy_file_range_test")
int copy_file_range_test_filter(struct fuse_bpf_args *fa)
{
switch (fa->opcode) {
case FUSE_LOOKUP | FUSE_PREFILTER:
return FUSE_BPF_BACKING | FUSE_BPF_POST_FILTER;
case FUSE_LOOKUP | FUSE_POSTFILTER: {
struct fuse_entry_bpf_out *febo = fa->out_args[1].value;
const char *name = fa->in_args[0].value;
bpf_printk("lookup postfilter %s %d", name, fa->error_in);
if (strcmp(name, "user") == 0) {
bpf_printk("lookup postfilter user");
febo->bpf_action = FUSE_ACTION_REMOVE;
febo->backing_action = FUSE_ACTION_REMOVE;
}
return 0;
}
default:
return FUSE_BPF_BACKING;
}
}