// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2021 Google LLC */ #include "test_fuse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct _test_options test_options; struct s s(const char *s1) { struct s s = {0}; if (!s1) return s; s.s = malloc(strlen(s1) + 1); if (!s.s) return s; strcpy(s.s, s1); return s; } struct s sn(const char *s1, const char *s2) { struct s s = {0}; if (!s1) return s; s.s = malloc(s2 - s1 + 1); if (!s.s) return s; strncpy(s.s, s1, s2 - s1); s.s[s2 - s1] = 0; return s; } int s_cmp(struct s s1, struct s s2) { int result = -1; if (!s1.s || !s2.s) goto out; result = strcmp(s1.s, s2.s); out: free(s1.s); free(s2.s); return result; } struct s s_cat(struct s s1, struct s s2) { struct s s = {0}; if (!s1.s || !s2.s) goto out; s.s = malloc(strlen(s1.s) + strlen(s2.s) + 1); if (!s.s) goto out; strcpy(s.s, s1.s); strcat(s.s, s2.s); out: free(s1.s); free(s2.s); return s; } struct s s_splitleft(struct s s1, char c) { struct s s = {0}; char *split; if (!s1.s) return s; split = strchr(s1.s, c); if (split) s = sn(s1.s, split); free(s1.s); return s; } struct s s_splitright(struct s s1, char c) { struct s s2 = {0}; char *split; if (!s1.s) return s2; split = strchr(s1.s, c); if (split) s2 = s(split + 1); free(s1.s); return s2; } struct s s_word(struct s s1, char c, size_t n) { while (n--) s1 = s_splitright(s1, c); return s_splitleft(s1, c); } struct s s_path(struct s s1, struct s s2) { return s_cat(s_cat(s1, s("/")), s2); } struct s s_pathn(size_t n, struct s s1, ...) { va_list argp; va_start(argp, s1); while (--n) s1 = s_path(s1, va_arg(argp, struct s)); va_end(argp); return s1; } int s_link(struct s src_pathname, struct s dst_pathname) { int res; if (src_pathname.s && dst_pathname.s) { res = link(src_pathname.s, dst_pathname.s); } else { res = -1; errno = ENOMEM; } free(src_pathname.s); free(dst_pathname.s); return res; } int s_symlink(struct s src_pathname, struct s dst_pathname) { int res; if (src_pathname.s && dst_pathname.s) { res = symlink(src_pathname.s, dst_pathname.s); } else { res = -1; errno = ENOMEM; } free(src_pathname.s); free(dst_pathname.s); return res; } int s_mkdir(struct s pathname, mode_t mode) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = mkdir(pathname.s, mode); free(pathname.s); return res; } int s_rmdir(struct s pathname) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = rmdir(pathname.s); free(pathname.s); return res; } int s_unlink(struct s pathname) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = unlink(pathname.s); free(pathname.s); return res; } int s_open(struct s pathname, int flags, ...) { va_list ap; int res; va_start(ap, flags); if (!pathname.s) { errno = ENOMEM; return -1; } if (flags & (O_CREAT | O_TMPFILE)) res = open(pathname.s, flags, va_arg(ap, mode_t)); else res = open(pathname.s, flags); free(pathname.s); va_end(ap); return res; } int s_openat(int dirfd, struct s pathname, int flags, ...) { va_list ap; int res; va_start(ap, flags); if (!pathname.s) { errno = ENOMEM; return -1; } if (flags & (O_CREAT | O_TMPFILE)) res = openat(dirfd, pathname.s, flags, va_arg(ap, mode_t)); else res = openat(dirfd, pathname.s, flags); free(pathname.s); va_end(ap); return res; } int s_creat(struct s pathname, mode_t mode) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = open(pathname.s, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); free(pathname.s); return res; } int s_mkfifo(struct s pathname, mode_t mode) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = mknod(pathname.s, S_IFIFO | mode, 0); free(pathname.s); return res; } int s_stat(struct s pathname, struct stat *st) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = stat(pathname.s, st); free(pathname.s); return res; } int s_statfs(struct s pathname, struct statfs *st) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = statfs(pathname.s, st); free(pathname.s); return res; } DIR *s_opendir(struct s pathname) { DIR *res; res = opendir(pathname.s); free(pathname.s); return res; } int s_getxattr(struct s pathname, const char name[], void *value, size_t size, ssize_t *ret_size) { if (!pathname.s) { errno = ENOMEM; return -1; } *ret_size = getxattr(pathname.s, name, value, size); free(pathname.s); return *ret_size >= 0 ? 0 : -1; } int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size) { if (!pathname.s) { errno = ENOMEM; return -1; } *ret_size = listxattr(pathname.s, list, size); free(pathname.s); return *ret_size >= 0 ? 0 : -1; } int s_setxattr(struct s pathname, const char name[], const void *value, size_t size, int flags) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = setxattr(pathname.s, name, value, size, flags); free(pathname.s); return res; } int s_removexattr(struct s pathname, const char name[]) { int res; if (!pathname.s) { errno = ENOMEM; return -1; } res = removexattr(pathname.s, name); free(pathname.s); return res; } int s_rename(struct s oldpathname, struct s newpathname) { int res; if (!oldpathname.s || !newpathname.s) { errno = ENOMEM; return -1; } res = rename(oldpathname.s, newpathname.s); free(oldpathname.s); free(newpathname.s); 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}; FILE *mounts = NULL; char *line = NULL; size_t size = 0; TEST(mounts = fopen("/proc/mounts", "re"), mounts); while (getline(&line, &size, mounts) != -1) { if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("tracefs"))) { trace = s_word(sn(line, line + size), ' ', 1); break; } if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("debugfs"))) trace = s_path(s_word(sn(line, line + size), ' ', 1), s("tracing")); } out: free(line); fclose(mounts); return trace; } int tracing_on(void) { int result = TEST_FAILURE; int tracing_on = -1; TEST(tracing_on = s_open(s_path(tracing_folder(), s("tracing_on")), O_WRONLY | O_CLOEXEC), tracing_on != -1); TESTEQUAL(write(tracing_on, "1", 1), 1); result = TEST_SUCCESS; out: close(tracing_on); return result; } char *concat_file_name(const char *dir, const char *file) { char full_name[FILENAME_MAX] = ""; if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0) return NULL; return strdup(full_name); } char *setup_mount_dir(const char *name) { struct stat st; char *current_dir = getcwd(NULL, 0); char *mount_dir = concat_file_name(current_dir, name); free(current_dir); if (stat(mount_dir, &st) == 0) { if (S_ISDIR(st.st_mode)) return mount_dir; ksft_print_msg("%s is a file, not a dir.\n", mount_dir); return NULL; } if (mkdir(mount_dir, 0777)) { ksft_print_msg("Can't create mount dir."); return NULL; } return mount_dir; } int delete_dir_tree(const char *dir_path, bool remove_root) { DIR *dir = NULL; struct dirent *dp; int result = 0; dir = opendir(dir_path); if (!dir) { result = -errno; goto out; } while ((dp = readdir(dir))) { char *full_path; if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; full_path = concat_file_name(dir_path, dp->d_name); if (dp->d_type == DT_DIR) result = delete_dir_tree(full_path, true); else result = unlink(full_path); free(full_path); if (result) goto out; } out: if (dir) closedir(dir); if (!result && remove_root) rmdir(dir_path); return result; } static int mount_fuse_maybe_init(const char *mount_dir, int bpf_fd, int dir_fd, int *fuse_dev_ptr, bool init) { int result = TEST_FAILURE; int fuse_dev = -1; char options[FILENAME_MAX]; uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; uint8_t bytes_out[FUSE_MIN_READ_BUFFER]; DECL_FUSE_IN(init); TEST(fuse_dev = open("/dev/fuse", O_RDWR | O_CLOEXEC), fuse_dev != -1); snprintf(options, FILENAME_MAX, "fd=%d,user_id=0,group_id=0,rootmode=0040000", fuse_dev); if (bpf_fd != -1) snprintf(options + strlen(options), sizeof(options) - strlen(options), ",root_bpf=%d", bpf_fd); if (dir_fd != -1) snprintf(options + strlen(options), sizeof(options) - strlen(options), ",root_dir=%d", dir_fd); TESTSYSCALL(mount("ABC", mount_dir, "fuse", 0, options)); if (init) { TESTFUSEIN(FUSE_INIT, init_in); TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { .major = FUSE_KERNEL_VERSION, .minor = FUSE_KERNEL_MINOR_VERSION, .max_readahead = 4096, .flags = 0, .max_background = 0, .congestion_threshold = 0, .max_write = 4096, .time_gran = 1000, .max_pages = 12, .map_alignment = 4096, })); } *fuse_dev_ptr = fuse_dev; fuse_dev = -1; result = TEST_SUCCESS; out: close(fuse_dev); return result; } int mount_fuse(const char *mount_dir, int bpf_fd, int dir_fd, int *fuse_dev_ptr) { return mount_fuse_maybe_init(mount_dir, bpf_fd, dir_fd, fuse_dev_ptr, true); } int mount_fuse_no_init(const char *mount_dir, int bpf_fd, int dir_fd, int *fuse_dev_ptr) { return mount_fuse_maybe_init(mount_dir, bpf_fd, dir_fd, fuse_dev_ptr, false); } struct fuse_bpf_map { unsigned int map_type; size_t key_size; size_t value_size; unsigned int max_entries; }; static int install_maps(Elf_Data *maps, int maps_index, Elf *elf, Elf_Data *symbols, int symbol_index, struct map_relocation **mr, size_t *map_count) { int result = TEST_FAILURE; int i; GElf_Sym symbol; TESTNE((void *)symbols, NULL); for (i = 0; i < symbols->d_size / sizeof(symbol); ++i) { TESTNE((void *)gelf_getsym(symbols, i, &symbol), 0); if (symbol.st_shndx == maps_index) { struct fuse_bpf_map *map; union bpf_attr attr; int map_fd; map = (struct fuse_bpf_map *) ((char *)maps->d_buf + symbol.st_value); attr = (union bpf_attr) { .map_type = map->map_type, .key_size = map->key_size, .value_size = map->value_size, .max_entries = map->max_entries, }; TEST(*mr = realloc(*mr, ++*map_count * sizeof(struct fuse_bpf_map)), *mr); TEST(map_fd = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)), map_fd != -1); (*mr)[*map_count - 1] = (struct map_relocation) { .name = strdup(elf_strptr(elf, symbol_index, symbol.st_name)), .fd = map_fd, .value = symbol.st_value, }; } } result = TEST_SUCCESS; out: return result; } static inline int relocate_maps(GElf_Shdr *rel_header, Elf_Data *rel_data, Elf_Data *prog_data, Elf_Data *symbol_data, struct map_relocation *map_relocations, size_t map_count) { int result = TEST_FAILURE; int i; struct bpf_insn *insns = (struct bpf_insn *) prog_data->d_buf; for (i = 0; i < rel_header->sh_size / rel_header->sh_entsize; ++i) { GElf_Sym sym; GElf_Rel rel; unsigned int insn_idx; int map_idx; gelf_getrel(rel_data, i, &rel); insn_idx = rel.r_offset / sizeof(struct bpf_insn); insns[insn_idx].src_reg = BPF_PSEUDO_MAP_FD; gelf_getsym(symbol_data, GELF_R_SYM(rel.r_info), &sym); for (map_idx = 0; map_idx < map_count; map_idx++) { if (map_relocations[map_idx].value == sym.st_value) { insns[insn_idx].imm = map_relocations[map_idx].fd; break; } } TESTNE(map_idx, map_count); } result = TEST_SUCCESS; out: return result; } int install_elf_bpf(const char *file, const char *section, int *fd, struct map_relocation **map_relocations, size_t *map_count) { int result = TEST_FAILURE; char path[PATH_MAX] = {}; char *last_slash; int filter_fd = -1; union bpf_attr bpf_attr; static char log[1 << 20]; Elf *elf = NULL; GElf_Ehdr ehdr; Elf_Data *data_prog = NULL, *data_maps = NULL, *data_symbols = NULL; int maps_index, symbol_index, prog_index; int i; TESTNE(readlink("/proc/self/exe", path, PATH_MAX), -1); TEST(last_slash = strrchr(path, '/'), last_slash); strcpy(last_slash + 1, file); TEST(filter_fd = open(path, O_RDONLY | O_CLOEXEC), filter_fd != -1); TESTNE(elf_version(EV_CURRENT), EV_NONE); TEST(elf = elf_begin(filter_fd, ELF_C_READ, NULL), elf); TESTEQUAL((void *) gelf_getehdr(elf, &ehdr), &ehdr); for (i = 1; i < ehdr.e_shnum; i++) { char *shname; GElf_Shdr shdr; Elf_Scn *scn; TEST(scn = elf_getscn(elf, i), scn); TESTEQUAL((void *)gelf_getshdr(scn, &shdr), &shdr); TEST(shname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name), shname); if (!strcmp(shname, "maps")) { TEST(data_maps = elf_getdata(scn, 0), data_maps); maps_index = i; } else if (shdr.sh_type == SHT_SYMTAB) { TEST(data_symbols = elf_getdata(scn, 0), data_symbols); symbol_index = shdr.sh_link; } else if (!strcmp(shname, section)) { TEST(data_prog = elf_getdata(scn, 0), data_prog); prog_index = i; } } TESTNE((void *) data_prog, NULL); if (data_maps) TESTEQUAL(install_maps(data_maps, maps_index, elf, data_symbols, symbol_index, map_relocations, map_count), 0); /* Now relocate maps */ for (i = 1; i < ehdr.e_shnum; i++) { GElf_Shdr rel_header; Elf_Scn *scn; Elf_Data *rel_data; TEST(scn = elf_getscn(elf, i), scn); TESTEQUAL((void *)gelf_getshdr(scn, &rel_header), &rel_header); if (rel_header.sh_type != SHT_REL) continue; TEST(rel_data = elf_getdata(scn, 0), rel_data); if (rel_header.sh_info != prog_index) continue; TESTEQUAL(relocate_maps(&rel_header, rel_data, data_prog, data_symbols, *map_relocations, *map_count), 0); } bpf_attr = (union bpf_attr) { .prog_type = BPF_PROG_TYPE_FUSE, .insn_cnt = data_prog->d_size / 8, .insns = ptr_to_u64(data_prog->d_buf), .license = ptr_to_u64("GPL"), .log_buf = test_options.verbose ? ptr_to_u64(log) : 0, .log_size = test_options.verbose ? sizeof(log) : 0, .log_level = test_options.verbose ? 2 : 0, }; *fd = syscall(__NR_bpf, BPF_PROG_LOAD, &bpf_attr, sizeof(bpf_attr)); if (test_options.verbose) ksft_print_msg("%s\n", log); if (*fd == -1 && errno == ENOSPC) ksft_print_msg("bpf log size too small!\n"); TESTNE(*fd, -1); result = TEST_SUCCESS; out: close(filter_fd); return result; }