mirror of
https://github.com/hardkernel/linux.git
synced 2026-03-25 20:10:23 +09:00
ANDROID: fuse-bpf: Introduce readdirplus test case for
fuse bpf Readdir plus is potentially dangerous place because this leads us to allocate fuse inodes. If we have problems with inode allocation and discovery we may end up with inode conflict which may cause backing_fd losing. We currently have this problem and this test clearly reproduce it. More information about the problem: go/fuse-loosing-inode-with-backing Fixes for this problem: https://android-review.googlesource.com/c/kernel/common/+/2135866 https://android-review.googlesource.com/c/kernel/common/+/2135457 Bug: 219958836 Test: Currently it’s fairly failed, after applying patches from above it passed. Co-developed-by: Paul Lawrence <paullawrence@google.com> Change-Id: I8afb535605faffc9facf626d0c7d0f244dc8d28e Signed-off-by: Dmitrii Merkurev <dimorinny@google.com>
This commit is contained in:
@@ -394,6 +394,35 @@ int s_rename(struct s oldpathname, struct s newpathname)
|
||||
return res;
|
||||
}
|
||||
|
||||
int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out)
|
||||
{
|
||||
|
||||
struct stat st;
|
||||
int result = TEST_FAILURE;
|
||||
|
||||
TESTSYSCALL(s_stat(pathname, &st));
|
||||
|
||||
fuse_attr_out->ino = st.st_ino;
|
||||
fuse_attr_out->mode = st.st_mode;
|
||||
fuse_attr_out->nlink = st.st_nlink;
|
||||
fuse_attr_out->uid = st.st_uid;
|
||||
fuse_attr_out->gid = st.st_gid;
|
||||
fuse_attr_out->rdev = st.st_rdev;
|
||||
fuse_attr_out->size = st.st_size;
|
||||
fuse_attr_out->blksize = st.st_blksize;
|
||||
fuse_attr_out->blocks = st.st_blocks;
|
||||
fuse_attr_out->atime = st.st_atime;
|
||||
fuse_attr_out->mtime = st.st_mtime;
|
||||
fuse_attr_out->ctime = st.st_ctime;
|
||||
fuse_attr_out->atimensec = UINT32_MAX;
|
||||
fuse_attr_out->mtimensec = UINT32_MAX;
|
||||
fuse_attr_out->ctimensec = UINT32_MAX;
|
||||
|
||||
result = TEST_SUCCESS;
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
struct s tracing_folder(void)
|
||||
{
|
||||
struct s trace = {0};
|
||||
|
||||
@@ -1491,6 +1491,192 @@ out:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* State:
|
||||
* Original: dst/folder1/content.txt
|
||||
* ^
|
||||
* |
|
||||
* |
|
||||
* Backing: src/folder1/content.txt
|
||||
*
|
||||
* Step 1: open(folder1) - set backing to src/folder1
|
||||
* Check 1: cat(content.txt) - check not receiving call on the fuse daemon
|
||||
* and content is the same
|
||||
* Step 2: readdirplus(dst)
|
||||
* Check 2: cat(content.txt) - check not receiving call on the fuse daemon
|
||||
* and content is the same
|
||||
*/
|
||||
static int bpf_test_readdirplus_not_overriding_backing(const char *mount_dir)
|
||||
{
|
||||
const char *folder1 = "folder1";
|
||||
const char *content_file = "content.txt";
|
||||
const char *content = "hello world";
|
||||
|
||||
int result = TEST_FAILURE;
|
||||
int fuse_dev = -1;
|
||||
int src_fd = -1;
|
||||
int content_fd = -1;
|
||||
int pid = -1;
|
||||
int status;
|
||||
|
||||
TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777));
|
||||
TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777),
|
||||
content_fd != -1);
|
||||
TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content));
|
||||
TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0);
|
||||
|
||||
FUSE_ACTION
|
||||
DIR *open_mount_dir = NULL;
|
||||
struct dirent *mount_dirent;
|
||||
int dst_folder1_fd = -1;
|
||||
int dst_content_fd = -1;
|
||||
int dst_content_read_size = -1;
|
||||
char content_buffer[12];
|
||||
|
||||
// Step 1: Lookup folder1
|
||||
TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)),
|
||||
O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1);
|
||||
|
||||
// Check 1: Read content file (backed)
|
||||
TESTERR(dst_content_fd =
|
||||
s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)),
|
||||
O_RDONLY | O_CLOEXEC), dst_content_fd != -1);
|
||||
|
||||
TEST(dst_content_read_size =
|
||||
read(dst_content_fd, content_buffer, strlen(content)),
|
||||
dst_content_read_size == strlen(content) &&
|
||||
strcmp(content, content_buffer) == 0);
|
||||
|
||||
TESTSYSCALL(close(dst_content_fd));
|
||||
dst_content_fd = -1;
|
||||
TESTSYSCALL(close(dst_folder1_fd));
|
||||
dst_folder1_fd = -1;
|
||||
memset(content_buffer, 0, strlen(content));
|
||||
|
||||
// Step 2: readdir folder 1
|
||||
TEST(open_mount_dir = s_opendir(s(mount_dir)),
|
||||
open_mount_dir != NULL);
|
||||
TEST(mount_dirent = readdir(open_mount_dir), mount_dirent != NULL);
|
||||
TESTSYSCALL(closedir(open_mount_dir));
|
||||
open_mount_dir = NULL;
|
||||
|
||||
// Check 2: Read content file again (must be backed)
|
||||
TESTERR(dst_content_fd =
|
||||
s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)),
|
||||
O_RDONLY | O_CLOEXEC), dst_content_fd != -1);
|
||||
|
||||
TEST(dst_content_read_size =
|
||||
read(dst_content_fd, content_buffer, strlen(content)),
|
||||
dst_content_read_size == strlen(content) &&
|
||||
strcmp(content, content_buffer) == 0);
|
||||
|
||||
TESTSYSCALL(close(dst_content_fd));
|
||||
dst_content_fd = -1;
|
||||
FUSE_DAEMON
|
||||
size_t read_size = 0;
|
||||
struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in;
|
||||
struct fuse_read_out *read_out = NULL;
|
||||
struct fuse_attr attr = {};
|
||||
int backing_fd = -1;
|
||||
DECL_FUSE_IN(open);
|
||||
DECL_FUSE_IN(getattr);
|
||||
|
||||
TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO);
|
||||
|
||||
// Step 1: Lookup folder 1 with backing
|
||||
TESTFUSELOOKUP(folder1, 0);
|
||||
TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
|
||||
TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)),
|
||||
O_DIRECTORY | O_RDONLY | O_CLOEXEC),
|
||||
backing_fd != -1);
|
||||
TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {
|
||||
.nodeid = attr.ino,
|
||||
.generation = 0,
|
||||
.entry_valid = UINT64_MAX,
|
||||
.attr_valid = UINT64_MAX,
|
||||
.entry_valid_nsec = UINT32_MAX,
|
||||
.attr_valid_nsec = UINT32_MAX,
|
||||
.attr = attr,
|
||||
}), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) {
|
||||
.backing_action = FUSE_ACTION_REPLACE,
|
||||
.backing_fd = backing_fd,
|
||||
}));
|
||||
TESTSYSCALL(close(backing_fd));
|
||||
|
||||
// Step 2: Open root dir
|
||||
TESTFUSEIN(FUSE_OPENDIR, open_in);
|
||||
TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) {
|
||||
.fh = 100,
|
||||
.open_flags = open_in->flags
|
||||
}));
|
||||
|
||||
// Step 2: Handle getattr
|
||||
TESTFUSEIN(FUSE_GETATTR, getattr_in);
|
||||
TESTSYSCALL(s_fuse_attr(s(ft_src), &attr));
|
||||
TESTFUSEOUT1(fuse_attr_out, ((struct fuse_attr_out) {
|
||||
.attr_valid = UINT64_MAX,
|
||||
.attr_valid_nsec = UINT32_MAX,
|
||||
.attr = attr
|
||||
}));
|
||||
|
||||
// Step 2: Handle readdirplus
|
||||
read_size = read(fuse_dev, bytes_in, sizeof(bytes_in));
|
||||
TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS);
|
||||
|
||||
struct fuse_direntplus *dirent_plus =
|
||||
(struct fuse_direntplus *) (bytes_in + read_size);
|
||||
struct fuse_dirent dirent;
|
||||
struct fuse_entry_out entry_out;
|
||||
|
||||
read_out = (struct fuse_read_out *) (bytes_in +
|
||||
sizeof(*in_header) +
|
||||
sizeof(struct fuse_read_in));
|
||||
|
||||
TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr));
|
||||
|
||||
dirent = (struct fuse_dirent) {
|
||||
.ino = attr.ino,
|
||||
.off = 1,
|
||||
.namelen = strlen(folder1),
|
||||
.type = DT_REG
|
||||
};
|
||||
entry_out = (struct fuse_entry_out) {
|
||||
.nodeid = attr.ino,
|
||||
.generation = 0,
|
||||
.entry_valid = UINT64_MAX,
|
||||
.attr_valid = UINT64_MAX,
|
||||
.entry_valid_nsec = UINT32_MAX,
|
||||
.attr_valid_nsec = UINT32_MAX,
|
||||
.attr = attr
|
||||
};
|
||||
*dirent_plus = (struct fuse_direntplus) {
|
||||
.dirent = dirent,
|
||||
.entry_out = entry_out
|
||||
};
|
||||
|
||||
strcpy((char *)(bytes_in + read_size + sizeof(*dirent_plus)), folder1);
|
||||
read_size += FUSE_DIRENT_ALIGN(sizeof(*dirent_plus) + strlen(folder1) +
|
||||
1);
|
||||
TESTFUSEDIROUTREAD(read_out,
|
||||
bytes_in +
|
||||
sizeof(struct fuse_in_header) +
|
||||
sizeof(struct fuse_read_in) +
|
||||
sizeof(struct fuse_read_out),
|
||||
read_size - sizeof(struct fuse_in_header) -
|
||||
sizeof(struct fuse_read_in) -
|
||||
sizeof(struct fuse_read_out));
|
||||
FUSE_DONE
|
||||
|
||||
result = TEST_SUCCESS;
|
||||
|
||||
out:
|
||||
close(fuse_dev);
|
||||
close(content_fd);
|
||||
close(src_fd);
|
||||
umount(mount_dir);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static int parse_options(int argc, char *const *argv)
|
||||
{
|
||||
@@ -1596,6 +1782,7 @@ int main(int argc, char *argv[])
|
||||
MAKE_TEST(inotify_test),
|
||||
MAKE_TEST(bpf_test_statfs),
|
||||
MAKE_TEST(bpf_test_lseek),
|
||||
MAKE_TEST(bpf_test_readdirplus_not_overriding_backing)
|
||||
};
|
||||
#undef MAKE_TEST
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <include/uapi/linux/fuse.h>
|
||||
|
||||
#define PAGE_SIZE 4096
|
||||
#define FUSE_POSTFILTER 0x20000
|
||||
|
||||
@@ -52,6 +54,7 @@ int s_creat(struct s pathname, mode_t mode);
|
||||
int s_mkfifo(struct s pathname, mode_t mode);
|
||||
int s_stat(struct s pathname, struct stat *st);
|
||||
int s_statfs(struct s pathname, struct statfs *st);
|
||||
int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out);
|
||||
DIR *s_opendir(struct s pathname);
|
||||
int s_getxattr(struct s pathname, const char name[], void *value, size_t size,
|
||||
ssize_t *ret_size);
|
||||
@@ -261,7 +264,7 @@ int delete_dir_tree(const char *dir_path, bool remove_root);
|
||||
((struct fuse_out_header *)bytes_out)->len); \
|
||||
} while (false)
|
||||
|
||||
#define TESTFUSEINIT() \
|
||||
#define TESTFUSEINITFLAGS(fuse_connection_flags) \
|
||||
do { \
|
||||
DECL_FUSE_IN(init); \
|
||||
\
|
||||
@@ -272,7 +275,7 @@ int delete_dir_tree(const char *dir_path, bool remove_root);
|
||||
.major = FUSE_KERNEL_VERSION, \
|
||||
.minor = FUSE_KERNEL_MINOR_VERSION, \
|
||||
.max_readahead = 4096, \
|
||||
.flags = 0, \
|
||||
.flags = fuse_connection_flags, \
|
||||
.max_background = 0, \
|
||||
.congestion_threshold = 0, \
|
||||
.max_write = 4096, \
|
||||
@@ -282,6 +285,9 @@ int delete_dir_tree(const char *dir_path, bool remove_root);
|
||||
})); \
|
||||
} while (false)
|
||||
|
||||
#define TESTFUSEINIT() \
|
||||
TESTFUSEINITFLAGS(0)
|
||||
|
||||
#define DECL_FUSE_IN(name) \
|
||||
struct fuse_##name##_in *name##_in = \
|
||||
(struct fuse_##name##_in *) \
|
||||
|
||||
Reference in New Issue
Block a user