// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2021 Google LLC */ #define _GNU_SOURCE #include "test_fuse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *ft_src = "ft-src"; static const char *ft_dst = "ft-dst"; static void fill_buffer(uint8_t *data, size_t len, int file, int block) { int i; int seed = 7919 * file + block; for (i = 0; i < len; i++) { seed = 1103515245 * seed + 12345; data[i] = (uint8_t)(seed >> (i % 13)); } } static bool test_buffer(uint8_t *data, size_t len, int file, int block) { int i; int seed = 7919 * file + block; for (i = 0; i < len; i++) { seed = 1103515245 * seed + 12345; if (data[i] != (uint8_t)(seed >> (i % 13))) return false; } return true; } static int create_file(int dir, struct s name, int index, size_t blocks) { int result = TEST_FAILURE; int fd = -1; int i; uint8_t data[PAGE_SIZE]; TEST(fd = s_openat(dir, name, O_CREAT | O_WRONLY, 0777), fd != -1); for (i = 0; i < blocks; ++i) { fill_buffer(data, PAGE_SIZE, index, i); TESTEQUAL(write(fd, data, sizeof(data)), PAGE_SIZE); } TESTSYSCALL(close(fd)); result = TEST_SUCCESS; out: close(fd); return result; } static int bpf_clear_trace(void) { int result = TEST_FAILURE; int tp = -1; TEST(tp = s_open(s_path(tracing_folder(), s("trace")), O_WRONLY | O_TRUNC | O_CLOEXEC), tp != -1); result = TEST_SUCCESS; out: close(tp); return result; } static int bpf_test_trace_maybe(const char *substr, bool present) { int result = TEST_FAILURE; int tp = -1; char trace_buffer[4096] = {}; ssize_t bytes_read; TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")), O_RDONLY | O_CLOEXEC), tp != -1); fcntl(tp, F_SETFL, O_NONBLOCK); for (;;) { bytes_read = read(tp, trace_buffer, sizeof(trace_buffer)); if (present) TESTCOND(bytes_read > 0); else if (bytes_read <= 0) { result = TEST_SUCCESS; break; } if (test_options.verbose) ksft_print_msg("%s\n", trace_buffer); if (strstr(trace_buffer, substr)) { if (present) result = TEST_SUCCESS; break; } } out: close(tp); return result; } static int bpf_test_trace(const char *substr) { return bpf_test_trace_maybe(substr, true); } static int bpf_test_no_trace(const char *substr) { return bpf_test_trace_maybe(substr, false); } static int basic_test(const char *mount_dir) { const char *test_name = "test"; const char *test_data = "data"; int result = TEST_FAILURE; int fuse_dev = -1; char *filename = NULL; int fd = -1; int pid = -1; int status; TESTEQUAL(mount_fuse(mount_dir, -1, -1, &fuse_dev), 0); FUSE_ACTION char data[256]; filename = concat_file_name(mount_dir, test_name); TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); TESTCOND(!strcmp(data, test_data)); TESTSYSCALL(close(fd)); fd = -1; FUSE_DAEMON DECL_FUSE_IN(open); DECL_FUSE_IN(read); DECL_FUSE_IN(flush); DECL_FUSE_IN(release); TESTFUSELOOKUP(test_name, 0); TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) { .nodeid = 2, .generation = 1, .attr.ino = 100, .attr.size = 4, .attr.blksize = 512, .attr.mode = S_IFREG | 0777, })); TESTFUSEIN(FUSE_OPEN, open_in); TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { .fh = 1, .open_flags = open_in->flags, })); TESTFUSEINNULL(FUSE_CANONICAL_PATH); TESTFUSEOUTREAD("ignored", 7); TESTFUSEIN(FUSE_READ, read_in); TESTFUSEOUTREAD(test_data, strlen(test_data)); TESTFUSEIN(FUSE_FLUSH, flush_in); TESTFUSEOUTEMPTY(); TESTFUSEIN(FUSE_RELEASE, release_in); TESTFUSEOUTEMPTY(); FUSE_DONE result = TEST_SUCCESS; out: if (!pid) exit(TEST_FAILURE); close(fuse_dev); close(fd); free(filename); umount(mount_dir); return result; } static int bpf_test_real(const char *mount_dir) { const char *test_name = "real"; const char *test_data = "Weebles wobble but they don't fall down"; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; char *filename = NULL; int fd = -1; char read_buffer[256] = {}; ssize_t bytes_read; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), fd != -1); TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); TESTSYSCALL(close(fd)); fd = -1; TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); filename = concat_file_name(mount_dir, test_name); TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); bytes_read = read(fd, read_buffer, strlen(test_data)); TESTEQUAL(bytes_read, strlen(test_data)); TESTEQUAL(strcmp(test_data, read_buffer), 0); TESTEQUAL(bpf_test_trace("read"), 0); result = TEST_SUCCESS; out: close(fuse_dev); close(fd); free(filename); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } static int bpf_test_partial(const char *mount_dir) { const char *test_name = "partial"; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; char *filename = NULL; int fd = -1; int pid = -1; int status; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION uint8_t data[PAGE_SIZE]; TEST(filename = concat_file_name(mount_dir, test_name), filename); TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE); TESTEQUAL(bpf_test_trace("read"), 0); TESTCOND(test_buffer(data, PAGE_SIZE, 2, 0)); TESTCOND(!test_buffer(data, PAGE_SIZE, 1, 0)); TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE); TESTCOND(test_buffer(data, PAGE_SIZE, 1, 1)); TESTCOND(!test_buffer(data, PAGE_SIZE, 2, 1)); TESTSYSCALL(close(fd)); fd = -1; FUSE_DAEMON DECL_FUSE(open); DECL_FUSE(read); DECL_FUSE(release); uint8_t data[PAGE_SIZE]; TESTFUSEIN2(FUSE_OPEN | FUSE_POSTFILTER, open_in, open_out); TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { .fh = 1, .open_flags = open_in->flags, })); TESTFUSEIN(FUSE_READ, read_in); fill_buffer(data, PAGE_SIZE, 2, 0); TESTFUSEOUTREAD(data, PAGE_SIZE); TESTFUSEIN(FUSE_RELEASE, release_in); TESTFUSEOUTEMPTY(); FUSE_DONE result = TEST_SUCCESS; out: if (!pid) exit(TEST_FAILURE); close(fuse_dev); close(fd); free(filename); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } static int bpf_test_attrs(const char *mount_dir) { const char *test_name = "partial"; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; char *filename = NULL; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TEST(filename = concat_file_name(mount_dir, test_name), filename); TESTSYSCALL(stat(filename, &st)); TESTSYSCALL(chmod(filename, 0111)); TESTSYSCALL(stat(filename, &st)); TESTEQUAL(st.st_mode & 0777, 0111); TESTSYSCALL(chmod(filename, 0777)); TESTSYSCALL(stat(filename, &st)); TESTEQUAL(st.st_mode & 0777, 0777); TESTSYSCALL(chown(filename, 5, 6)); TESTSYSCALL(stat(filename, &st)); TESTEQUAL(st.st_uid, 5); TESTEQUAL(st.st_gid, 6); result = TEST_SUCCESS; out: close(fuse_dev); free(filename); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } static int bpf_test_readdir(const char *mount_dir) { const char *names[] = {"real", "partial", "fake", ".", ".."}; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; int pid = -1; int status; DIR *dir = NULL; struct dirent *dirent; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(create_file(src_fd, s(names[0]), 1, 2), 0); TESTEQUAL(create_file(src_fd, s(names[1]), 1, 2), 0); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION int i, j; TEST(dir = s_opendir(s(mount_dir)), dir); TESTEQUAL(bpf_test_trace("opendir"), 0); for (i = 0; i < ARRAY_SIZE(names); ++i) { TEST(dirent = readdir(dir), dirent); for (j = 0; j < ARRAY_SIZE(names); ++j) if (names[j] && strcmp(names[j], dirent->d_name) == 0) { names[j] = NULL; break; } TESTNE(j, ARRAY_SIZE(names)); } TEST(dirent = readdir(dir), dirent == NULL); TESTSYSCALL(closedir(dir)); dir = NULL; TESTEQUAL(bpf_test_trace("readdir"), 0); FUSE_DAEMON struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); struct fuse_read_out *read_out = (struct fuse_read_out *) (bytes_in + sizeof(*in_header) + sizeof(struct fuse_read_in)); struct fuse_dirent *fuse_dirent = (struct fuse_dirent *) (bytes_in + res); TESTGE(res, sizeof(*in_header) + sizeof(struct fuse_read_in)); TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER); *fuse_dirent = (struct fuse_dirent) { .ino = 100, .off = 5, .namelen = strlen("fake"), .type = DT_REG, }; strcpy((char *)(bytes_in + res + sizeof(*fuse_dirent)), "fake"); res += FUSE_DIRENT_ALIGN(sizeof(*fuse_dirent) + strlen("fake") + 1); TESTFUSEDIROUTREAD(read_out, bytes_in + sizeof(struct fuse_in_header) + sizeof(struct fuse_read_in) + sizeof(struct fuse_read_out), res - sizeof(struct fuse_in_header) - sizeof(struct fuse_read_in) - sizeof(struct fuse_read_out)); res = read(fuse_dev, bytes_in, sizeof(bytes_in)); TESTEQUAL(res, sizeof(*in_header) + sizeof(struct fuse_read_in) + sizeof(struct fuse_read_out)); TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER); TESTFUSEDIROUTREAD(read_out, bytes_in, 0); FUSE_DONE result = TEST_SUCCESS; out: closedir(dir); close(fuse_dev); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } static int bpf_test_redact_readdir(const char *mount_dir) { const char *names[] = {"f1", "f2", "f3", "f4", "f5", "f6", ".", ".."}; int num_shown = (ARRAY_SIZE(names) - 2) / 2 + 2; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; int pid = -1; int status; DIR *dir = NULL; struct dirent *dirent; int i; int count = 0; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); for (i = 0; i < ARRAY_SIZE(names) - 2; i++) TESTEQUAL(create_file(src_fd, s(names[i]), 1, 2), 0); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_readdir_redact", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION int j; TEST(dir = s_opendir(s(mount_dir)), dir); while ((dirent = readdir(dir))) { errno = 0; TESTEQUAL(errno, 0); for (j = 0; j < ARRAY_SIZE(names); ++j) if (names[j] && strcmp(names[j], dirent->d_name) == 0) { names[j] = NULL; count++; break; } TESTNE(j, ARRAY_SIZE(names)); TESTGE(num_shown, count); } TESTEQUAL(count, num_shown); TESTSYSCALL(closedir(dir)); dir = NULL; FUSE_DAEMON bool skip = true; for (int i = 0; i < ARRAY_SIZE(names) + 1; i++) { uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; uint8_t bytes_out[FUSE_MIN_READ_BUFFER]; struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); int length_out = 0; uint8_t *pos; uint8_t *dirs_in; uint8_t *dirs_out; struct fuse_read_in *fuse_read_in; struct fuse_read_out *fuse_read_out_in; struct fuse_read_out *fuse_read_out_out; struct fuse_dirent *fuse_dirent_in = NULL; struct fuse_dirent *next = NULL; bool again = false; int dir_ent_len = 0; TESTGE(res, sizeof(struct fuse_in_header) + sizeof(struct fuse_read_in) + sizeof(struct fuse_read_out)); pos = bytes_in + sizeof(struct fuse_in_header); fuse_read_in = (struct fuse_read_in *) pos; pos += sizeof(*fuse_read_in); fuse_read_out_in = (struct fuse_read_out *) pos; pos += sizeof(*fuse_read_out_in); dirs_in = pos; pos = bytes_out + sizeof(struct fuse_out_header); fuse_read_out_out = (struct fuse_read_out *) pos; pos += sizeof(*fuse_read_out_out); dirs_out = pos; if (dirs_in < bytes_in + res) { bool is_dot; fuse_dirent_in = (struct fuse_dirent *) dirs_in; is_dot = (fuse_dirent_in->namelen == 1 && !strncmp(fuse_dirent_in->name, ".", 1)) || (fuse_dirent_in->namelen == 2 && !strncmp(fuse_dirent_in->name, "..", 2)); dir_ent_len = FUSE_DIRENT_ALIGN( sizeof(*fuse_dirent_in) + fuse_dirent_in->namelen); if (dirs_in + dir_ent_len < bytes_in + res) next = (struct fuse_dirent *) (dirs_in + dir_ent_len); if (!skip || is_dot) { memcpy(dirs_out, fuse_dirent_in, sizeof(struct fuse_dirent) + fuse_dirent_in->namelen); length_out += dir_ent_len; } again = ((skip && !is_dot) && next); if (!is_dot) skip = !skip; } fuse_read_out_out->offset = next ? next->off : fuse_read_out_in->offset; fuse_read_out_out->again = again; { struct fuse_out_header *out_header = (struct fuse_out_header *)bytes_out; *out_header = (struct fuse_out_header) { .len = sizeof(*out_header) + sizeof(*fuse_read_out_out) + length_out, .unique = in_header->unique, }; TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), out_header->len); } } FUSE_DONE result = TEST_SUCCESS; out: closedir(dir); close(fuse_dev); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } /* * This test is more to show what classic fuse does with a creat in a subdir * than a test of any new functionality */ static int bpf_test_creat(const char *mount_dir) { const char *dir_name = "show"; const char *file_name = "file"; int result = TEST_FAILURE; int fuse_dev = -1; int pid = -1; int status; int fd = -1; TESTEQUAL(mount_fuse(mount_dir, -1, -1, &fuse_dev), 0); FUSE_ACTION TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_name)), s(file_name)), 0777), fd != -1); TESTSYSCALL(close(fd)); FUSE_DAEMON DECL_FUSE_IN(create); DECL_FUSE_IN(release); DECL_FUSE_IN(flush); TESTFUSELOOKUP(dir_name, 0); TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) { .nodeid = 3, .generation = 1, .attr.ino = 100, .attr.size = 4, .attr.blksize = 512, .attr.mode = S_IFDIR | 0777, })); TESTFUSELOOKUP(file_name, 0); TESTFUSEOUTERROR(-ENOENT); TESTFUSEINEXT(FUSE_CREATE, create_in, strlen(file_name) + 1); TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) { .nodeid = 2, .generation = 1, .attr.ino = 200, .attr.size = 4, .attr.blksize = 512, .attr.mode = S_IFREG, }), fuse_open_out, ((struct fuse_open_out) { .fh = 1, .open_flags = create_in->flags, })); TESTFUSEINNULL(FUSE_CANONICAL_PATH); TESTFUSEOUTREAD("ignored", 7); TESTFUSEIN(FUSE_FLUSH, flush_in); TESTFUSEOUTEMPTY(); TESTFUSEIN(FUSE_RELEASE, release_in); TESTFUSEOUTEMPTY(); FUSE_DONE result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); return result; } static int bpf_test_hidden_entries(const char *mount_dir) { static const char * const dir_names[] = { "show", "hide", }; const char *file_name = "file"; const char *data = "The quick brown fox jumps over the lazy dog\n"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; int fd = -1; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTSYSCALL(mkdirat(src_fd, dir_names[0], 0777)); TESTSYSCALL(mkdirat(src_fd, dir_names[1], 0777)); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_hidden", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_names[0])), s(file_name)), 0777), fd != -1); TESTSYSCALL(fallocate(fd, 0, 0, 4096)); TEST(write(fd, data, strlen(data)), strlen(data)); TESTSYSCALL(close(fd)); TESTEQUAL(bpf_test_trace("Create"), 0); result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_dir(const char *mount_dir) { const char *dir_name = "dir"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); TESTEQUAL(bpf_test_trace("mkdir"), 0); TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name)))); TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); TESTEQUAL(errno, ENOENT); result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_file(const char *mount_dir, bool close_first) { const char *file_name = "real"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; int fd = -1; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); TESTEQUAL(bpf_test_trace("Create"), 0); if (close_first) { TESTSYSCALL(close(fd)); fd = -1; } TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); TESTEQUAL(bpf_test_trace("unlink"), 0); TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); TESTEQUAL(errno, ENOENT); if (!close_first) { TESTSYSCALL(close(fd)); fd = -1; } result = TEST_SUCCESS; out: close(fd); close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_file_early_close(const char *mount_dir) { return bpf_test_file(mount_dir, true); } static int bpf_test_file_late_close(const char *mount_dir) { return bpf_test_file(mount_dir, false); } static int bpf_test_alter_errcode_bpf(const char *mount_dir) { const char *dir_name = "dir"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_error", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); //TESTEQUAL(bpf_test_trace("mkdir"), 0); TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); TESTEQUAL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777), -EPERM); TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name)))); TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); TESTEQUAL(errno, ENOENT); result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_alter_errcode_userspace(const char *mount_dir) { const char *dir_name = "doesnotexist"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; int pid = -1; int status; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_error", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION TESTEQUAL(s_unlink(s_path(s(mount_dir), s(dir_name))), -1); TESTEQUAL(errno, ENOMEM); FUSE_DAEMON TESTFUSELOOKUP("doesnotexist", FUSE_POSTFILTER); TESTFUSEOUTERROR(-ENOMEM); FUSE_DONE result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_mknod(const char *mount_dir) { const char *file_name = "real"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_mkfifo(s_path(s(mount_dir), s(file_name)), 0777)); TESTEQUAL(bpf_test_trace("mknod"), 0); TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); TESTEQUAL(bpf_test_trace("unlink"), 0); TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); TESTEQUAL(errno, ENOENT); result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_largedir(const char *mount_dir) { const char *show = "show"; const int files = 1000; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct map_relocation *map_relocations = NULL; size_t map_count = 0; int pid = -1; int status; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("fd_bpf.bpf", "test_daemon", &bpf_fd, &map_relocations, &map_count), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION int i; int fd; DIR *dir = NULL; struct dirent *dirent; TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(show)), 0777)); for (i = 0; i < files; ++i) { char filename[NAME_MAX]; sprintf(filename, "%d", i); TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(show)), s(filename)), 0777), fd != -1); TESTSYSCALL(close(fd)); } TEST(dir = s_opendir(s_path(s(mount_dir), s(show))), dir); for (dirent = readdir(dir); dirent; dirent = readdir(dir)) ; closedir(dir); FUSE_DAEMON int i; for (i = 0; i < files + 2; ++i) { TESTFUSELOOKUP(show, FUSE_PREFILTER); TESTFUSEOUTREAD(show, 5); } FUSE_DONE result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_link(const char *mount_dir) { const char *file_name = "real"; const char *link_name = "partial"; int result = TEST_FAILURE; int fd = -1; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); TESTEQUAL(bpf_test_trace("Create"), 0); TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); TESTSYSCALL(s_link(s_path(s(mount_dir), s(file_name)), s_path(s(mount_dir), s(link_name)))); TESTEQUAL(bpf_test_trace("link"), 0); TESTSYSCALL(s_stat(s_path(s(ft_src), s(link_name)), &st)); TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(link_name)))); TESTEQUAL(bpf_test_trace("unlink"), 0); TESTEQUAL(s_stat(s_path(s(ft_src), s(link_name)), &st), -1); TESTEQUAL(errno, ENOENT); TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); TESTEQUAL(bpf_test_trace("unlink"), 0); TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); TESTEQUAL(errno, ENOENT); result = TEST_SUCCESS; out: close(fd); close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_symlink(const char *mount_dir) { const char *test_name = "real"; const char *symlink_name = "partial"; const char *test_data = "Weebles wobble but they don't fall down"; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; int fd = -1; char read_buffer[256] = {}; ssize_t bytes_read; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), fd != -1); TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); TESTSYSCALL(close(fd)); fd = -1; TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_symlink(s_path(s(mount_dir), s(test_name)), s_path(s(mount_dir), s(symlink_name)))); TESTEQUAL(bpf_test_trace("symlink"), 0); TESTERR(fd = s_open(s_path(s(mount_dir), s(symlink_name)), O_RDONLY | O_CLOEXEC), fd != -1); bytes_read = read(fd, read_buffer, strlen(test_data)); TESTEQUAL(bpf_test_trace("readlink"), 0); TESTEQUAL(bytes_read, strlen(test_data)); TESTEQUAL(strcmp(test_data, read_buffer), 0); result = TEST_SUCCESS; out: close(fuse_dev); close(fd); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } static int bpf_test_xattr(const char *mount_dir) { static const char file_name[] = "real"; static const char xattr_name[] = "user.xattr_test_name"; static const char xattr_value[] = "this_is_a_test"; const size_t xattr_size = sizeof(xattr_value); char xattr_value_ret[256]; ssize_t xattr_size_ret; int result = TEST_FAILURE; int fd = -1; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct stat st; memset(xattr_value_ret, '\0', sizeof(xattr_value_ret)); TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); TESTEQUAL(bpf_test_trace("Create"), 0); TESTSYSCALL(close(fd)); TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); TEST(result = s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, xattr_value_ret, sizeof(xattr_value_ret), &xattr_size_ret), result == -1); TESTEQUAL(errno, ENODATA); TESTEQUAL(bpf_test_trace("getxattr"), 0); TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)), xattr_value_ret, sizeof(xattr_value_ret), &xattr_size_ret)); TESTEQUAL(bpf_test_trace("listxattr"), 0); TESTEQUAL(xattr_size_ret, 0); TESTSYSCALL(s_setxattr(s_path(s(mount_dir), s(file_name)), xattr_name, xattr_value, xattr_size, 0)); TESTEQUAL(bpf_test_trace("setxattr"), 0); TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)), xattr_value_ret, sizeof(xattr_value_ret), &xattr_size_ret)); TESTEQUAL(bpf_test_trace("listxattr"), 0); TESTEQUAL(xattr_size_ret, sizeof(xattr_name)); TESTEQUAL(strcmp(xattr_name, xattr_value_ret), 0); TESTSYSCALL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, xattr_value_ret, sizeof(xattr_value_ret), &xattr_size_ret)); TESTEQUAL(bpf_test_trace("getxattr"), 0); TESTEQUAL(xattr_size, xattr_size_ret); TESTEQUAL(strcmp(xattr_value, xattr_value_ret), 0); TESTSYSCALL(s_removexattr(s_path(s(mount_dir), s(file_name)), xattr_name)); TESTEQUAL(bpf_test_trace("removexattr"), 0); TESTEQUAL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, xattr_value_ret, sizeof(xattr_value_ret), &xattr_size_ret), -1); TESTEQUAL(errno, ENODATA); TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); TESTEQUAL(bpf_test_trace("unlink"), 0); TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); TESTEQUAL(errno, ENOENT); result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_set_backing(const char *mount_dir) { const char *backing_name = "backing"; const char *test_data = "data"; const char *test_name = "test"; int result = TEST_FAILURE; int fuse_dev = -1; int fd = -1; int pid = -1; int status; TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0); FUSE_ACTION char data[256] = {0}; TESTERR(fd = s_open(s_path(s(mount_dir), s(test_name)), O_RDONLY | O_CLOEXEC), fd != -1); TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); TESTCOND(!strcmp(data, test_data)); TESTSYSCALL(close(fd)); fd = -1; TESTSYSCALL(umount(mount_dir)); FUSE_DAEMON int bpf_fd = -1; int backing_fd = -1; TESTERR(backing_fd = s_creat(s_path(s(ft_src), s(backing_name)), 0777), backing_fd != -1); TESTEQUAL(write(backing_fd, test_data, strlen(test_data)), strlen(test_data)); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_simple", &bpf_fd, NULL, NULL), 0); TESTFUSEINIT(); TESTFUSELOOKUP(test_name, 0); TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {0}), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) { .backing_action = FUSE_ACTION_REPLACE, .backing_fd = backing_fd, .bpf_action = FUSE_ACTION_REPLACE, .bpf_fd = bpf_fd, })); read(fuse_dev, bytes_in, sizeof(bytes_in)); TESTSYSCALL(close(bpf_fd)); TESTSYSCALL(close(backing_fd)); FUSE_DONE result = TEST_SUCCESS; out: if (!pid) exit(TEST_FAILURE); close(fuse_dev); close(fd); umount(mount_dir); return result; } static int bpf_test_remove_backing(const char *mount_dir) { const char *folder1 = "folder1"; const char *folder2 = "folder2"; const char *file = "file1"; const char *contents1 = "contents1"; const char *contents2 = "contents2"; int result = TEST_FAILURE; int fuse_dev = -1; int fd = -1; int src_fd = -1; int bpf_fd = -1; int pid = -1; int status; char data[256] = {0}; /* * Create folder1/file * folder2/file * * test will install bpf into mount * bpf will postfilter root lookup to daemon * daemon will remove bpf and redirect opens on folder1 to folder2 * test will open folder1/file which will be redirected to folder2 * test will check no traces for file, and contents are folder2/file */ TESTEQUAL(bpf_clear_trace(), 0); TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(file)), 0777), fd != -1); TESTEQUAL(write(fd, contents1, strlen(contents1)), strlen(contents1)); TESTSYSCALL(close(fd)); TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777)); TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder2), s(file)), 0777), fd != -1); TESTEQUAL(write(fd, contents2, strlen(contents2)), strlen(contents2)); TESTSYSCALL(close(fd)); TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_passthrough", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse_no_init(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION TESTERR(fd = s_open(s_pathn(3, s(mount_dir), s(folder1), s(file)), O_RDONLY | O_CLOEXEC), fd != -1); TESTEQUAL(read(fd, data, sizeof(data)), strlen(contents2)); TESTCOND(!strcmp(data, contents2)); TESTEQUAL(bpf_test_no_trace("file"), 0); TESTSYSCALL(close(fd)); fd = -1; TESTSYSCALL(umount(mount_dir)); FUSE_DAEMON struct { char name[8]; struct fuse_entry_out feo; struct fuse_entry_bpf_out febo; } __attribute__((packed)) in; int backing_fd = -1; TESTFUSEINIT(); TESTFUSEIN(FUSE_LOOKUP | FUSE_POSTFILTER, &in); TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)), O_DIRECTORY | O_RDONLY | O_CLOEXEC), backing_fd != -1); TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) {0}), fuse_entry_bpf_out, ((struct fuse_entry_bpf_out) { .bpf_action = FUSE_ACTION_REMOVE, .backing_action = FUSE_ACTION_REPLACE, .backing_fd = backing_fd, })); while (read(fuse_dev, bytes_in, sizeof(bytes_in)) != -1) ; TESTSYSCALL(close(backing_fd)); FUSE_DONE result = TEST_SUCCESS; out: close(fuse_dev); close(fd); close(src_fd); close(bpf_fd); umount(mount_dir); return result; } static int bpf_test_dir_rename(const char *mount_dir) { const char *dir_name = "dir"; const char *dir_name2 = "dir2"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; struct stat st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); TESTEQUAL(bpf_test_trace("mkdir"), 0); TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); TESTSYSCALL(s_rename(s_path(s(mount_dir), s(dir_name)), s_path(s(mount_dir), s(dir_name2)))); TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); TESTEQUAL(errno, ENOENT); TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name2)), &st)); result = TEST_SUCCESS; out: close(fuse_dev); umount(mount_dir); close(bpf_fd); close(src_fd); return result; } static int bpf_test_file_rename(const char *mount_dir) { const char *dir = "dir"; const char *file1 = "file1"; const char *file2 = "file2"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; int fd = -1; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir)), 0777)); TEST(fd = s_creat(s_pathn(3, s(mount_dir), s(dir), s(file1)), 0777), fd != -1); TESTSYSCALL(s_rename(s_pathn(3, s(mount_dir), s(dir), s(file1)), s_pathn(3, s(mount_dir), s(dir), s(file2)))); result = TEST_SUCCESS; out: close(fd); umount(mount_dir); close(fuse_dev); close(bpf_fd); close(src_fd); return result; } static int mmap_test(const char *mount_dir) { const char *file = "file"; int result = TEST_FAILURE; int src_fd = -1; int fuse_dev = -1; int fd = -1; char *addr = 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); TEST(fd = s_open(s_path(s(mount_dir), s(file)), O_CREAT | O_RDWR | O_CLOEXEC, 0777), fd != -1); TESTSYSCALL(fallocate(fd, 0, 4096, SEEK_CUR)); TEST(addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), addr != (void *) -1); memset(addr, 'a', 4096); result = TEST_SUCCESS; out: munmap(addr, 4096); close(fd); umount(mount_dir); close(fuse_dev); close(src_fd); 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 inotify_test(const char *mount_dir) { int result = TEST_FAILURE; int src_fd = -1; int fuse_dev = -1; struct s dir; int inotify_fd = -1; int watch; int fd = -1; char buffer[sizeof(struct inotify_event) + NAME_MAX + 1]; 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); TEST(inotify_fd = inotify_init1(IN_CLOEXEC), inotify_fd != -1); dir = s_path(s(mount_dir), s("dir")); TESTSYSCALL(mkdir(dir.s, 0777)); TEST(watch = inotify_add_watch(inotify_fd, dir.s, IN_CREATE), watch); TEST(fd = s_creat(s_path(s(ft_src), s("dir/file")), 0777), fd != -1); // buffer will be two struct lengths, as "file" gets rounded up to the // next multiple of struct inotify_event TESTEQUAL(read(inotify_fd, &buffer, sizeof(buffer)), sizeof(struct inotify_event) * 2); result = TEST_SUCCESS; out: close(fd); s_unlink(s_path(s(ft_src), s("dir/file"))); close(inotify_fd); rmdir(dir.s); free(dir.s); umount(mount_dir); close(fuse_dev); close(src_fd); return result; } static int bpf_test_statfs(const char *mount_dir) { int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; int fd = -1; struct statfs st; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TESTSYSCALL(s_statfs(s(mount_dir), &st)); TESTEQUAL(bpf_test_trace("statfs"), 0); TESTEQUAL(st.f_type, 0x65735546); result = TEST_SUCCESS; out: close(fd); umount(mount_dir); close(fuse_dev); close(bpf_fd); close(src_fd); return result; } static int bpf_test_lseek(const char *mount_dir) { const char *file = "real"; const char *test_data = "data"; int result = TEST_FAILURE; int src_fd = -1; int bpf_fd = -1; int fuse_dev = -1; int fd = -1; TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TEST(fd = openat(src_fd, file, O_CREAT | O_RDWR | O_CLOEXEC, 0777), fd != -1); TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); TESTSYSCALL(close(fd)); fd = -1; TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_trace", &bpf_fd, NULL, NULL), 0); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); TEST(fd = s_open(s_path(s(mount_dir), s(file)), O_RDONLY | O_CLOEXEC), fd != -1); TESTEQUAL(lseek(fd, 3, SEEK_SET), 3); TESTEQUAL(bpf_test_trace("lseek"), 0); TESTEQUAL(lseek(fd, 5, SEEK_END), 9); TESTEQUAL(bpf_test_trace("lseek"), 0); TESTEQUAL(lseek(fd, 1, SEEK_CUR), 10); TESTEQUAL(bpf_test_trace("lseek"), 0); TESTEQUAL(lseek(fd, 1, SEEK_DATA), 1); TESTEQUAL(bpf_test_trace("lseek"), 0); result = TEST_SUCCESS; out: close(fd); umount(mount_dir); close(fuse_dev); close(bpf_fd); close(src_fd); 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 bpf_test_no_readdirplus_without_nodeid(const char *mount_dir) { const char *folder1 = "folder1"; const char *folder2 = "folder2"; int result = TEST_FAILURE; int fuse_dev = -1; int src_fd = -1; int content_fd = -1; int bpf_fd = -1; int pid = -1; int status; TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_readdirplus", &bpf_fd, NULL, NULL), 0); TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777)); TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0); FUSE_ACTION DIR *open_dir = NULL; struct dirent *dirent; // Folder 1: Readdir with no nodeid TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder1))), open_dir != NULL); TEST(dirent = readdir(open_dir), dirent == NULL); TESTCOND(errno == EINVAL); TESTSYSCALL(closedir(open_dir)); open_dir = NULL; // Folder 2: Readdir with a nodeid TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder2))), open_dir != NULL); TEST(dirent = readdir(open_dir), dirent == NULL); TESTCOND(errno == EINVAL); TESTSYSCALL(closedir(open_dir)); open_dir = NULL; FUSE_DAEMON size_t read_size; struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; struct fuse_attr attr = {}; int backing_fd = -1; TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); // folder 1: Set 0 as nodeid, Expect READDIR TESTFUSELOOKUP(folder1, 0); 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 = 0, .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, .bpf_action = FUSE_ACTION_REPLACE, .bpf_fd = bpf_fd, })); TESTSYSCALL(close(backing_fd)); TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0); TESTEQUAL(in_header->opcode, FUSE_READDIR); TESTFUSEOUTERROR(-EINVAL); // folder 2: Set 10 as nodeid, Expect READDIRPLUS TESTFUSELOOKUP(folder2, 0); TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)), O_DIRECTORY | O_RDONLY | O_CLOEXEC), backing_fd != -1); TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) { .nodeid = 10, .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, .bpf_action = FUSE_ACTION_REPLACE, .bpf_fd = bpf_fd, })); TESTSYSCALL(close(backing_fd)); TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0); TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); TESTFUSEOUTERROR(-EINVAL); FUSE_DONE result = TEST_SUCCESS; out: close(fuse_dev); close(content_fd); close(src_fd); close(bpf_fd); umount(mount_dir); return result; } /* * State: * Original: dst/folder1/content.txt * ^ * | * | * Backing: src/folder1/content.txt * * Step 1: open(folder1) - lookup folder1 with entry_timeout set to 0 * Step 2: open(folder1) - lookup folder1 again to trigger revalidate wich will * set backing fd * * Check 1: cat(content.txt) - check not receiving call on the fuse daemon * and content is the same */ static int bpf_test_revalidate_handle_backing_fd(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)); TESTSYSCALL(close(content_fd)); content_fd = -1; TESTEQUAL(mount_fuse_no_init(mount_dir, -1, -1, &fuse_dev), 0); FUSE_ACTION int dst_folder1_fd = -1; int dst_content_fd = -1; int dst_content_read_size = -1; char content_buffer[9] = {0}; // 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); TESTSYSCALL(close(dst_folder1_fd)); dst_folder1_fd = -1; // Step 2: Lookup folder1 again TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); TESTSYSCALL(close(dst_folder1_fd)); dst_folder1_fd = -1; // Check 1: Read content file (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 struct fuse_attr attr = {}; int backing_fd = -1; TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); // Step 1: Lookup folder1 set entry_timeout to 0 to trigger // revalidate later 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 = 0, .attr_valid = UINT64_MAX, .entry_valid_nsec = 0, .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 1: Lookup folder1 as a reaction to revalidate call // This attempts to change the backing node, which is not allowed on revalidate 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)); // Lookup folder1 as a reaction to failed revalidate 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)); FUSE_DONE result = TEST_SUCCESS; out: close(fuse_dev); close(content_fd); close(src_fd); umount(mount_dir); return result; } static int bpf_test_lookup_postfilter(const char *mount_dir) { const char *file1_name = "file1"; const char *file2_name = "file2"; const char *file3_name = "file3"; int result = TEST_FAILURE; int bpf_fd = -1; int src_fd = -1; int fuse_dev = -1; int file_fd = -1; int pid = -1; int status; TEST(file_fd = s_creat(s_path(s(ft_src), s(file1_name)), 0777), file_fd != -1); TESTSYSCALL(close(file_fd)); TEST(file_fd = s_creat(s_path(s(ft_src), s(file2_name)), 0777), file_fd != -1); TESTSYSCALL(close(file_fd)); file_fd = -1; TESTEQUAL(install_elf_bpf("test_bpf.bpf", "test_lookup_postfilter", &bpf_fd, NULL, NULL), 0); TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), src_fd != -1); TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); FUSE_ACTION int fd = -1; TESTEQUAL(s_open(s_path(s(mount_dir), s(file1_name)), O_RDONLY), -1); TESTEQUAL(errno, ENOENT); TEST(fd = s_open(s_path(s(mount_dir), s(file2_name)), O_RDONLY), fd != -1); TESTSYSCALL(close(fd)); TESTEQUAL(s_open(s_path(s(mount_dir), s(file3_name)), O_RDONLY), -1); FUSE_DAEMON struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; struct fuse_entry_out *feo; struct fuse_entry_bpf_out *febo; TESTFUSELOOKUP(file1_name, FUSE_POSTFILTER); TESTFUSEOUTERROR(-ENOENT); TESTFUSELOOKUP(file2_name, FUSE_POSTFILTER); feo = (struct fuse_entry_out *) (bytes_in + sizeof(struct fuse_in_header) + strlen(file2_name) + 1); febo = (struct fuse_entry_bpf_out *) ((char *)feo + sizeof(*feo)); TESTFUSEOUT2(fuse_entry_out, *feo, fuse_entry_bpf_out, *febo); TESTFUSELOOKUP(file3_name, FUSE_POSTFILTER); TESTEQUAL(in_header->error_in, -ENOENT); TESTFUSEOUTERROR(-ENOENT); FUSE_DONE result = TEST_SUCCESS; out: close(file_fd); close(fuse_dev); umount(mount_dir); close(src_fd); close(bpf_fd); return result; } static void parse_range(const char *ranges, bool *run_test, size_t tests) { size_t i; char *range; for (i = 0; i < tests; ++i) run_test[i] = false; range = strtok(optarg, ","); while (range) { char *dash = strchr(range, '-'); if (dash) { size_t start = 1, end = tests; char *end_ptr; if (dash > range) { start = strtol(range, &end_ptr, 10); if (*end_ptr != '-' || start <= 0 || start > tests) ksft_exit_fail_msg("Bad range\n"); } if (dash[1]) { end = strtol(dash + 1, &end_ptr, 10); if (*end_ptr || end <= start || end > tests) ksft_exit_fail_msg("Bad range\n"); } for (i = start; i <= end; ++i) run_test[i - 1] = true; } else { char *end; long value = strtol(range, &end, 10); if (*end || value <= 0 || value > tests) ksft_exit_fail_msg("Bad range\n"); run_test[value - 1] = true; } range = strtok(NULL, ","); } } static int parse_options(int argc, char *const *argv, bool *run_test, size_t tests) { signed char c; while ((c = getopt(argc, argv, "f:t:v")) != -1) switch (c) { case 'f': test_options.file = strtol(optarg, NULL, 10); break; case 't': parse_range(optarg, run_test, tests); break; case 'v': test_options.verbose = true; break; default: return -EINVAL; } return 0; } struct test_case { int (*pfunc)(const char *dir); const char *name; }; static void run_one_test(const char *mount_dir, const struct test_case *test_case) { ksft_print_msg("Running %s\n", test_case->name); if (test_case->pfunc(mount_dir) == TEST_SUCCESS) ksft_test_result_pass("%s\n", test_case->name); else ksft_test_result_fail("%s\n", test_case->name); } int main(int argc, char *argv[]) { char *mount_dir = NULL; char *src_dir = NULL; int i; int fd, count; #define MAKE_TEST(test) \ { \ test, #test \ } const struct test_case cases[] = { MAKE_TEST(basic_test), MAKE_TEST(bpf_test_real), MAKE_TEST(bpf_test_partial), MAKE_TEST(bpf_test_attrs), MAKE_TEST(bpf_test_readdir), MAKE_TEST(bpf_test_creat), MAKE_TEST(bpf_test_hidden_entries), MAKE_TEST(bpf_test_dir), MAKE_TEST(bpf_test_file_early_close), MAKE_TEST(bpf_test_file_late_close), MAKE_TEST(bpf_test_mknod), MAKE_TEST(bpf_test_largedir), MAKE_TEST(bpf_test_link), MAKE_TEST(bpf_test_symlink), MAKE_TEST(bpf_test_xattr), MAKE_TEST(bpf_test_redact_readdir), MAKE_TEST(bpf_test_set_backing), MAKE_TEST(bpf_test_remove_backing), MAKE_TEST(bpf_test_dir_rename), MAKE_TEST(bpf_test_file_rename), MAKE_TEST(bpf_test_alter_errcode_bpf), MAKE_TEST(bpf_test_alter_errcode_userspace), MAKE_TEST(mmap_test), MAKE_TEST(readdir_perms_test), MAKE_TEST(inotify_test), MAKE_TEST(bpf_test_statfs), MAKE_TEST(bpf_test_lseek), MAKE_TEST(bpf_test_readdirplus_not_overriding_backing), MAKE_TEST(bpf_test_no_readdirplus_without_nodeid), MAKE_TEST(bpf_test_revalidate_handle_backing_fd), MAKE_TEST(bpf_test_lookup_postfilter), }; #undef MAKE_TEST bool run_test[ARRAY_SIZE(cases)]; for (int i = 0; i < ARRAY_SIZE(cases); ++i) run_test[i] = true; if (parse_options(argc, argv, run_test, ARRAY_SIZE(cases))) ksft_exit_fail_msg("Bad options\n"); // Seed randomness pool for testing on QEMU // NOTE - this abuses the concept of randomness - do *not* ever do this // on a machine for production use - the device will think it has good // randomness when it does not. fd = open("/dev/urandom", O_WRONLY | O_CLOEXEC); count = 4096; for (int i = 0; i < 128; ++i) ioctl(fd, RNDADDTOENTCNT, &count); close(fd); ksft_print_header(); if (geteuid() != 0) ksft_print_msg("Not a root, might fail to mount.\n"); if (tracing_on() != TEST_SUCCESS) ksft_exit_fail_msg("Can't turn on tracing\n"); src_dir = setup_mount_dir(ft_src); mount_dir = setup_mount_dir(ft_dst); if (src_dir == NULL || mount_dir == NULL) ksft_exit_fail_msg("Can't create a mount dir\n"); ksft_set_plan(ARRAY_SIZE(run_test)); for (i = 0; i < ARRAY_SIZE(run_test); ++i) if (run_test[i]) { delete_dir_tree(mount_dir, false); delete_dir_tree(src_dir, false); run_one_test(mount_dir, &cases[i]); } else ksft_cnt.ksft_xskip++; umount2(mount_dir, MNT_FORCE); delete_dir_tree(mount_dir, true); delete_dir_tree(src_dir, true); return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail(); }