From 34957d1e9236e27df4fc1e4cfbbaf271271f05ff Mon Sep 17 00:00:00 2001 From: Paul Lawrence Date: Fri, 7 Jan 2022 20:36:53 +0000 Subject: [PATCH] ANDROID: fuse-bpf: Fix perms on readdir Add checks for both fuse accesses and backing fs accesses Bug: 202785178 Test: fuse_test passes, also atest ScopedStorageDeviceTest passes Change-Id: Ida7d90e14ca36588a8cc19453e0d40b4f6f41aa9 Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 24 ++++++++++ fs/fuse/dir.c | 4 +- .../selftests/filesystems/fuse/fuse_test.c | 44 +++++++++++++++++++ .../selftests/filesystems/fuse/test_bpf.c | 10 +++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index d4610ff7a922..d201e59a7086 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -140,6 +140,8 @@ int fuse_open_backing(struct fuse_args *fa, struct fuse_mount *fm = get_fuse_mount(inode); const struct fuse_open_in *foi = fa->in_args[0].value; struct fuse_file *ff; + int retval; + int mask; struct fuse_dentry *fd = get_fuse_dentry(file->f_path.dentry); struct file *backing_file; @@ -148,9 +150,31 @@ int fuse_open_backing(struct fuse_args *fa, return -ENOMEM; file->private_data = ff; + switch (foi->flags & O_ACCMODE) { + case O_RDONLY: + mask = MAY_READ; + break; + + case O_WRONLY: + mask = MAY_WRITE; + break; + + case O_RDWR: + mask = MAY_READ | MAY_WRITE; + break; + + default: + return -EINVAL; + } + + retval = inode_permission(get_fuse_inode(inode)->backing_inode, mask); + if (retval) + return retval; + backing_file = dentry_open(&fd->backing_path, foi->flags, current_cred()); + if (IS_ERR(backing_file)) { fuse_file_free(ff); file->private_data = NULL; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 4f72a9170da0..af9e69b7a6a3 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1524,6 +1524,7 @@ static int fuse_permission(struct inode *inode, int mask) struct fuse_conn *fc = get_fuse_conn(inode); bool refreshed = false; int err = 0; + struct fuse_inode *fi = get_fuse_inode(inode); if (fuse_is_bad(inode)) return -EIO; @@ -1536,7 +1537,6 @@ static int fuse_permission(struct inode *inode, int mask) */ if (fc->default_permissions || ((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) { - struct fuse_inode *fi = get_fuse_inode(inode); u32 perm_mask = STATX_MODE | STATX_UID | STATX_GID; if (perm_mask & READ_ONCE(fi->inval_mask) || @@ -1576,6 +1576,8 @@ static int fuse_permission(struct inode *inode, int mask) if (!err && !(inode->i_mode & S_IXUGO)) return -EACCES; } + } else if (!(mask & MAY_NOT_BLOCK) && fi->backing_inode) { + err = fuse_access(inode, mask); } return err; } diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/testing/selftests/filesystems/fuse/fuse_test.c index eaca579860a2..3f1e32479152 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_test.c @@ -14,8 +14,10 @@ #include #include +#include #include +#include #include #include @@ -1324,6 +1326,47 @@ out: return result; } +static int readdir_perms_test(const char *mount_dir) +{ + int result = TEST_FAILURE; + struct __user_cap_header_struct uchs = { _LINUX_CAPABILITY_VERSION_3 }; + struct __user_cap_data_struct ucds[2]; + int src_fd = -1; + int fuse_dev = -1; + DIR *dir = NULL; + + /* Must remove capabilities for this test. */ + TESTSYSCALL(syscall(SYS_capget, &uchs, ucds)); + ucds[0].effective &= ~(1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH); + TESTSYSCALL(syscall(SYS_capset, &uchs, ucds)); + + /* This is what we are testing in fuseland. First test without fuse, */ + TESTSYSCALL(mkdir("test", 0111)); + TEST(dir = opendir("test"), dir == NULL); + closedir(dir); + dir = NULL; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(mount_fuse(mount_dir, -1, src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s("test")), 0111)); + TEST(dir = s_opendir(s_path(s(mount_dir), s("test"))), dir == NULL); + + result = TEST_SUCCESS; +out: + ucds[0].effective |= 1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH; + syscall(SYS_capset, &uchs, ucds); + + closedir(dir); + s_rmdir(s_path(s(mount_dir), s("test"))); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + rmdir("test"); + return result; +} + static int parse_options(int argc, char *const *argv) { signed char c; @@ -1424,6 +1467,7 @@ int main(int argc, char *argv[]) MAKE_TEST(bpf_test_alter_errcode_bpf), MAKE_TEST(bpf_test_alter_errcode_userspace), MAKE_TEST(mmap_test), + MAKE_TEST(readdir_perms_test), }; #undef MAKE_TEST diff --git a/tools/testing/selftests/filesystems/fuse/test_bpf.c b/tools/testing/selftests/filesystems/fuse/test_bpf.c index a5e60eec79ba..29ef4e289b60 100644 --- a/tools/testing/selftests/filesystems/fuse/test_bpf.c +++ b/tools/testing/selftests/filesystems/fuse/test_bpf.c @@ -98,6 +98,11 @@ int trace_test(struct fuse_args *fa) return 0; } + case FUSE_ACCESS | FUSE_PREFILTER: { + bpf_printk("Access: %d", fa->nodeid); + return FUSE_BPF_BACKING; + } + case FUSE_CREATE | FUSE_PREFILTER: bpf_printk("Create: %d", fa->nodeid); return FUSE_BPF_BACKING; @@ -347,6 +352,11 @@ int trace_hidden(struct fuse_args *fa) return FUSE_BPF_BACKING; } + case FUSE_ACCESS | FUSE_PREFILTER: { + bpf_printk("Access: %d", fa->nodeid); + return FUSE_BPF_BACKING; + } + case FUSE_CREATE | FUSE_PREFILTER: bpf_printk("Create: %d", fa->nodeid); return FUSE_BPF_BACKING;