/* * bpf.c BPF common code * * This program is free software; you can distribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Daniel Borkmann * Jiri Pirko * Alexei Starovoitov */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ELF #include #include #endif #include #include #include #include #include #include #include #include #include #include "utils.h" #include "json_print.h" #include "bpf_util.h" #include "bpf_elf.h" #include "bpf_scm.h" struct bpf_prog_meta { const char *type; const char *subdir; const char *section; bool may_uds_export; }; static const enum bpf_prog_type __bpf_types[] = { BPF_PROG_TYPE_SCHED_CLS, BPF_PROG_TYPE_SCHED_ACT, BPF_PROG_TYPE_XDP, BPF_PROG_TYPE_LWT_IN, BPF_PROG_TYPE_LWT_OUT, BPF_PROG_TYPE_LWT_XMIT, }; static const struct bpf_prog_meta __bpf_prog_meta[] = { [BPF_PROG_TYPE_SCHED_CLS] = { .type = "cls", .subdir = "tc", .section = ELF_SECTION_CLASSIFIER, .may_uds_export = true, }, [BPF_PROG_TYPE_SCHED_ACT] = { .type = "act", .subdir = "tc", .section = ELF_SECTION_ACTION, .may_uds_export = true, }, [BPF_PROG_TYPE_XDP] = { .type = "xdp", .subdir = "xdp", .section = ELF_SECTION_PROG, }, [BPF_PROG_TYPE_LWT_IN] = { .type = "lwt_in", .subdir = "ip", .section = ELF_SECTION_PROG, }, [BPF_PROG_TYPE_LWT_OUT] = { .type = "lwt_out", .subdir = "ip", .section = ELF_SECTION_PROG, }, [BPF_PROG_TYPE_LWT_XMIT] = { .type = "lwt_xmit", .subdir = "ip", .section = ELF_SECTION_PROG, }, [BPF_PROG_TYPE_LWT_SEG6LOCAL] = { .type = "lwt_seg6local", .subdir = "ip", .section = ELF_SECTION_PROG, }, }; static const char *bpf_prog_to_subdir(enum bpf_prog_type type) { assert(type < ARRAY_SIZE(__bpf_prog_meta) && __bpf_prog_meta[type].subdir); return __bpf_prog_meta[type].subdir; } const char *bpf_prog_to_default_section(enum bpf_prog_type type) { assert(type < ARRAY_SIZE(__bpf_prog_meta) && __bpf_prog_meta[type].section); return __bpf_prog_meta[type].section; } #ifdef HAVE_ELF static int bpf_obj_open(const char *path, enum bpf_prog_type type, const char *sec, __u32 ifindex, bool verbose); #else static int bpf_obj_open(const char *path, enum bpf_prog_type type, const char *sec, __u32 ifindex, bool verbose) { fprintf(stderr, "No ELF library support compiled in.\n"); errno = ENOSYS; return -1; } #endif static inline __u64 bpf_ptr_to_u64(const void *ptr) { return (__u64)(unsigned long)ptr; } static int bpf(int cmd, union bpf_attr *attr, unsigned int size) { #ifdef __NR_bpf return syscall(__NR_bpf, cmd, attr, size); #else fprintf(stderr, "No bpf syscall, kernel headers too old?\n"); errno = ENOSYS; return -1; #endif } static int bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) { union bpf_attr attr = {}; attr.map_fd = fd; attr.key = bpf_ptr_to_u64(key); attr.value = bpf_ptr_to_u64(value); attr.flags = flags; return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); } static int bpf_prog_fd_by_id(uint32_t id) { union bpf_attr attr = {}; attr.prog_id = id; return bpf(BPF_PROG_GET_FD_BY_ID, &attr, sizeof(attr)); } static int bpf_prog_info_by_fd(int fd, struct bpf_prog_info *info, uint32_t *info_len) { union bpf_attr attr = {}; int ret; attr.info.bpf_fd = fd; attr.info.info = bpf_ptr_to_u64(info); attr.info.info_len = *info_len; *info_len = 0; ret = bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)); if (!ret) *info_len = attr.info.info_len; return ret; } int bpf_dump_prog_info(FILE *f, uint32_t id) { struct bpf_prog_info info = {}; uint32_t len = sizeof(info); int fd, ret, dump_ok = 0; SPRINT_BUF(tmp); open_json_object("prog"); print_uint(PRINT_ANY, "id", "id %u ", id); fd = bpf_prog_fd_by_id(id); if (fd < 0) goto out; ret = bpf_prog_info_by_fd(fd, &info, &len); if (!ret && len) { int jited = !!info.jited_prog_len; print_string(PRINT_ANY, "tag", "tag %s ", hexstring_n2a(info.tag, sizeof(info.tag), tmp, sizeof(tmp))); print_uint(PRINT_JSON, "jited", NULL, jited); if (jited && !is_json_context()) fprintf(f, "jited "); dump_ok = 1; } close(fd); out: close_json_object(); return dump_ok; } static int bpf_parse_string(char *arg, bool from_file, __u16 *bpf_len, char **bpf_string, bool *need_release, const char separator) { char sp; if (from_file) { size_t tmp_len, op_len = sizeof("65535 255 255 4294967295,"); char *tmp_string, *pos, c_prev = ' '; FILE *fp; int c; tmp_len = sizeof("4096,") + BPF_MAXINSNS * op_len; tmp_string = pos = calloc(1, tmp_len); if (tmp_string == NULL) return -ENOMEM; fp = fopen(arg, "r"); if (fp == NULL) { perror("Cannot fopen"); free(tmp_string); return -ENOENT; } while ((c = fgetc(fp)) != EOF) { switch (c) { case '\n': if (c_prev != ',') *(pos++) = ','; c_prev = ','; break; case ' ': case '\t': if (c_prev != ' ') *(pos++) = c; c_prev = ' '; break; default: *(pos++) = c; c_prev = c; } if (pos - tmp_string == tmp_len) break; } if (!feof(fp)) { free(tmp_string); fclose(fp); return -E2BIG; } fclose(fp); *pos = 0; *need_release = true; *bpf_string = tmp_string; } else { *need_release = false; *bpf_string = arg; } if (sscanf(*bpf_string, "%hu%c", bpf_len, &sp) != 2 || sp != separator) { if (*need_release) free(*bpf_string); return -EINVAL; } return 0; } static int bpf_ops_parse(int argc, char **argv, struct sock_filter *bpf_ops, bool from_file) { char *bpf_string, *token, separator = ','; int ret = 0, i = 0; bool need_release; __u16 bpf_len = 0; if (argc < 1) return -EINVAL; if (bpf_parse_string(argv[0], from_file, &bpf_len, &bpf_string, &need_release, separator)) return -EINVAL; if (bpf_len == 0 || bpf_len > BPF_MAXINSNS) { ret = -EINVAL; goto out; } token = bpf_string; while ((token = strchr(token, separator)) && (++token)[0]) { if (i >= bpf_len) { fprintf(stderr, "Real program length exceeds encoded length parameter!\n"); ret = -EINVAL; goto out; } if (sscanf(token, "%hu %hhu %hhu %u,", &bpf_ops[i].code, &bpf_ops[i].jt, &bpf_ops[i].jf, &bpf_ops[i].k) != 4) { fprintf(stderr, "Error at instruction %d!\n", i); ret = -EINVAL; goto out; } i++; } if (i != bpf_len) { fprintf(stderr, "Parsed program length is less than encoded length parameter!\n"); ret = -EINVAL; goto out; } ret = bpf_len; out: if (need_release) free(bpf_string); return ret; } void bpf_print_ops(struct rtattr *bpf_ops, __u16 len) { struct sock_filter *ops = RTA_DATA(bpf_ops); int i; if (len == 0) return; open_json_object("bytecode"); print_uint(PRINT_ANY, "length", "bytecode \'%u,", len); open_json_array(PRINT_JSON, "insns"); for (i = 0; i < len; i++) { open_json_object(NULL); print_hu(PRINT_ANY, "code", "%hu ", ops[i].code); print_hhu(PRINT_ANY, "jt", "%hhu ", ops[i].jt); print_hhu(PRINT_ANY, "jf", "%hhu ", ops[i].jf); if (i == len - 1) print_uint(PRINT_ANY, "k", "%u\'", ops[i].k); else print_uint(PRINT_ANY, "k", "%u,", ops[i].k); close_json_object(); } close_json_array(PRINT_JSON, NULL); close_json_object(); } static void bpf_map_pin_report(const struct bpf_elf_map *pin, const struct bpf_elf_map *obj) { fprintf(stderr, "Map specification differs from pinned file!\n"); if (obj->type != pin->type) fprintf(stderr, " - Type: %u (obj) != %u (pin)\n", obj->type, pin->type); if (obj->size_key != pin->size_key) fprintf(stderr, " - Size key: %u (obj) != %u (pin)\n", obj->size_key, pin->size_key); if (obj->size_value != pin->size_value) fprintf(stderr, " - Size value: %u (obj) != %u (pin)\n", obj->size_value, pin->size_value); if (obj->max_elem != pin->max_elem) fprintf(stderr, " - Max elems: %u (obj) != %u (pin)\n", obj->max_elem, pin->max_elem); if (obj->flags != pin->flags) fprintf(stderr, " - Flags: %#x (obj) != %#x (pin)\n", obj->flags, pin->flags); fprintf(stderr, "\n"); } struct bpf_prog_data { unsigned int type; unsigned int jited; }; struct bpf_map_ext { struct bpf_prog_data owner; unsigned int btf_id_key; unsigned int btf_id_val; }; static int bpf_derive_elf_map_from_fdinfo(int fd, struct bpf_elf_map *map, struct bpf_map_ext *ext) { unsigned int val, owner_type = 0, owner_jited = 0; char *file = NULL; char buff[4096]; FILE *fp; int ret; ret = asprintf(&file, "/proc/%d/fdinfo/%d", getpid(), fd); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); free(file); return ret; } memset(map, 0, sizeof(*map)); fp = fopen(file, "r"); free(file); if (!fp) { fprintf(stderr, "No procfs support?!\n"); return -EIO; } while (fgets(buff, sizeof(buff), fp)) { if (sscanf(buff, "map_type:\t%u", &val) == 1) map->type = val; else if (sscanf(buff, "key_size:\t%u", &val) == 1) map->size_key = val; else if (sscanf(buff, "value_size:\t%u", &val) == 1) map->size_value = val; else if (sscanf(buff, "max_entries:\t%u", &val) == 1) map->max_elem = val; else if (sscanf(buff, "map_flags:\t%i", &val) == 1) map->flags = val; else if (sscanf(buff, "owner_prog_type:\t%i", &val) == 1) owner_type = val; else if (sscanf(buff, "owner_jited:\t%i", &val) == 1) owner_jited = val; } fclose(fp); if (ext) { memset(ext, 0, sizeof(*ext)); ext->owner.type = owner_type; ext->owner.jited = owner_jited; } return 0; } static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map, struct bpf_map_ext *ext, int length, enum bpf_prog_type type) { struct bpf_elf_map tmp, zero = {}; int ret; ret = bpf_derive_elf_map_from_fdinfo(fd, &tmp, ext); if (ret < 0) return ret; /* The decision to reject this is on kernel side eventually, but * at least give the user a chance to know what's wrong. */ if (ext->owner.type && ext->owner.type != type) fprintf(stderr, "Program array map owner types differ: %u (obj) != %u (pin)\n", type, ext->owner.type); if (!memcmp(&tmp, map, length)) { return 0; } else { /* If kernel doesn't have eBPF-related fdinfo, we cannot do much, * so just accept it. We know we do have an eBPF fd and in this * case, everything is 0. It is guaranteed that no such map exists * since map type of 0 is unloadable BPF_MAP_TYPE_UNSPEC. */ if (!memcmp(&tmp, &zero, length)) return 0; bpf_map_pin_report(&tmp, map); return -EINVAL; } } static int bpf_mnt_fs(const char *target) { bool bind_done = false; while (mount("", target, "none", MS_PRIVATE | MS_REC, NULL)) { if (errno != EINVAL || bind_done) { fprintf(stderr, "mount --make-private %s failed: %s\n", target, strerror(errno)); return -1; } if (mount(target, target, "none", MS_BIND, NULL)) { fprintf(stderr, "mount --bind %s %s failed: %s\n", target, target, strerror(errno)); return -1; } bind_done = true; } if (mount("bpf", target, "bpf", 0, "mode=0700")) { fprintf(stderr, "mount -t bpf bpf %s failed: %s\n", target, strerror(errno)); return -1; } return 0; } static int bpf_mnt_check_target(const char *target) { struct stat sb = {}; int ret; ret = stat(target, &sb); if (ret) { ret = mkdir(target, S_IRWXU); if (ret) { fprintf(stderr, "mkdir %s failed: %s\n", target, strerror(errno)); return ret; } } return 0; } static int bpf_valid_mntpt(const char *mnt, unsigned long magic) { struct statfs st_fs; if (statfs(mnt, &st_fs) < 0) return -ENOENT; if ((unsigned long)st_fs.f_type != magic) return -ENOENT; return 0; } static const char *bpf_find_mntpt_single(unsigned long magic, char *mnt, int len, const char *mntpt) { int ret; ret = bpf_valid_mntpt(mntpt, magic); if (!ret) { strlcpy(mnt, mntpt, len); return mnt; } return NULL; } static const char *bpf_find_mntpt(const char *fstype, unsigned long magic, char *mnt, int len, const char * const *known_mnts) { const char * const *ptr; char type[100]; FILE *fp; if (known_mnts) { ptr = known_mnts; while (*ptr) { if (bpf_find_mntpt_single(magic, mnt, len, *ptr)) return mnt; ptr++; } } if (len != PATH_MAX) return NULL; fp = fopen("/proc/mounts", "r"); if (fp == NULL) return NULL; while (fscanf(fp, "%*s %" textify(PATH_MAX) "s %99s %*s %*d %*d\n", mnt, type) == 2) { if (strcmp(type, fstype) == 0) break; } fclose(fp); if (strcmp(type, fstype) != 0) return NULL; return mnt; } int bpf_trace_pipe(void) { char tracefs_mnt[PATH_MAX] = TRACE_DIR_MNT; static const char * const tracefs_known_mnts[] = { TRACE_DIR_MNT, "/sys/kernel/debug/tracing", "/tracing", "/trace", 0, }; int fd_in, fd_out = STDERR_FILENO; char *tpipe = NULL; const char *mnt; int ret; mnt = bpf_find_mntpt("tracefs", TRACEFS_MAGIC, tracefs_mnt, sizeof(tracefs_mnt), tracefs_known_mnts); if (!mnt) { fprintf(stderr, "tracefs not mounted?\n"); return -1; } ret = asprintf(&tpipe, "%s/trace_pipe", mnt); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); free(tpipe); return ret; } fd_in = open(tpipe, O_RDONLY); free(tpipe); if (fd_in < 0) return -1; fprintf(stderr, "Running! Hang up with ^C!\n\n"); while (1) { static char buff[4096]; ssize_t ret; ret = read(fd_in, buff, sizeof(buff)); if (ret > 0 && write(fd_out, buff, ret) == ret) continue; break; } close(fd_in); return -1; } static int bpf_gen_global(const char *bpf_sub_dir) { char *bpf_glo_dir = NULL; int ret; ret = asprintf(&bpf_glo_dir, "%s/%s/", bpf_sub_dir, BPF_DIR_GLOBALS); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } ret = mkdir(bpf_glo_dir, S_IRWXU); if (ret && errno != EEXIST) { fprintf(stderr, "mkdir %s failed: %s\n", bpf_glo_dir, strerror(errno)); goto out; } ret = 0; out: free(bpf_glo_dir); return ret; } static int bpf_gen_master(const char *base, const char *name) { char *bpf_sub_dir = NULL; int ret; ret = asprintf(&bpf_sub_dir, "%s%s/", base, name); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } ret = mkdir(bpf_sub_dir, S_IRWXU); if (ret && errno != EEXIST) { fprintf(stderr, "mkdir %s failed: %s\n", bpf_sub_dir, strerror(errno)); goto out; } ret = bpf_gen_global(bpf_sub_dir); out: free(bpf_sub_dir); return ret; } static int bpf_slave_via_bind_mnt(const char *full_name, const char *full_link) { int ret; ret = mkdir(full_name, S_IRWXU); if (ret) { assert(errno != EEXIST); fprintf(stderr, "mkdir %s failed: %s\n", full_name, strerror(errno)); return ret; } ret = mount(full_link, full_name, "none", MS_BIND, NULL); if (ret) { rmdir(full_name); fprintf(stderr, "mount --bind %s %s failed: %s\n", full_link, full_name, strerror(errno)); } return ret; } static int bpf_gen_slave(const char *base, const char *name, const char *link) { char *bpf_lnk_dir = NULL; char *bpf_sub_dir = NULL; struct stat sb = {}; int ret; ret = asprintf(&bpf_lnk_dir, "%s%s/", base, link); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } ret = asprintf(&bpf_sub_dir, "%s%s", base, name); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } ret = symlink(bpf_lnk_dir, bpf_sub_dir); if (ret) { if (errno != EEXIST) { if (errno != EPERM) { fprintf(stderr, "symlink %s failed: %s\n", bpf_sub_dir, strerror(errno)); goto out; } ret = bpf_slave_via_bind_mnt(bpf_sub_dir, bpf_lnk_dir); goto out; } ret = lstat(bpf_sub_dir, &sb); if (ret) { fprintf(stderr, "lstat %s failed: %s\n", bpf_sub_dir, strerror(errno)); goto out; } if ((sb.st_mode & S_IFMT) != S_IFLNK) { ret = bpf_gen_global(bpf_sub_dir); goto out; } } out: free(bpf_lnk_dir); free(bpf_sub_dir); return ret; } static int bpf_gen_hierarchy(const char *base) { int ret, i; ret = bpf_gen_master(base, bpf_prog_to_subdir(__bpf_types[0])); for (i = 1; i < ARRAY_SIZE(__bpf_types) && !ret; i++) ret = bpf_gen_slave(base, bpf_prog_to_subdir(__bpf_types[i]), bpf_prog_to_subdir(__bpf_types[0])); return ret; } static const char *bpf_get_work_dir(enum bpf_prog_type type) { static char bpf_tmp[PATH_MAX] = BPF_DIR_MNT; static char *bpf_wrk_dir; static const char *mnt; static bool bpf_mnt_cached; const char *mnt_env = getenv(BPF_ENV_MNT); static const char * const bpf_known_mnts[] = { BPF_DIR_MNT, "/bpf", 0, }; int ret; if (bpf_mnt_cached) { const char *out = mnt; if (out && type) { snprintf(bpf_tmp, sizeof(bpf_tmp), "%s%s/", out, bpf_prog_to_subdir(type)); out = bpf_tmp; } return out; } if (mnt_env) mnt = bpf_find_mntpt_single(BPF_FS_MAGIC, bpf_tmp, sizeof(bpf_tmp), mnt_env); else mnt = bpf_find_mntpt("bpf", BPF_FS_MAGIC, bpf_tmp, sizeof(bpf_tmp), bpf_known_mnts); if (!mnt) { mnt = mnt_env ? : BPF_DIR_MNT; ret = bpf_mnt_check_target(mnt); if (!ret) ret = bpf_mnt_fs(mnt); if (ret) { mnt = NULL; goto out; } } ret = asprintf(&bpf_wrk_dir, "%s/", mnt); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); free(bpf_wrk_dir); goto out; } ret = bpf_gen_hierarchy(bpf_wrk_dir); if (ret) { mnt = NULL; goto out; } mnt = bpf_wrk_dir; out: bpf_mnt_cached = true; return mnt; } static int bpf_obj_get(const char *pathname, enum bpf_prog_type type) { union bpf_attr attr = {}; char tmp[PATH_MAX]; if (strlen(pathname) > 2 && pathname[0] == 'm' && pathname[1] == ':' && bpf_get_work_dir(type)) { snprintf(tmp, sizeof(tmp), "%s/%s", bpf_get_work_dir(type), pathname + 2); pathname = tmp; } attr.pathname = bpf_ptr_to_u64(pathname); return bpf(BPF_OBJ_GET, &attr, sizeof(attr)); } static int bpf_obj_pinned(const char *pathname, enum bpf_prog_type type) { int prog_fd = bpf_obj_get(pathname, type); if (prog_fd < 0) fprintf(stderr, "Couldn\'t retrieve pinned program \'%s\': %s\n", pathname, strerror(errno)); return prog_fd; } static int bpf_do_parse(struct bpf_cfg_in *cfg, const bool *opt_tbl) { const char *file, *section, *uds_name; bool verbose = false; int i, ret, argc; char **argv; argv = cfg->argv; argc = cfg->argc; if (opt_tbl[CBPF_BYTECODE] && (matches(*argv, "bytecode") == 0 || strcmp(*argv, "bc") == 0)) { cfg->mode = CBPF_BYTECODE; } else if (opt_tbl[CBPF_FILE] && (matches(*argv, "bytecode-file") == 0 || strcmp(*argv, "bcf") == 0)) { cfg->mode = CBPF_FILE; } else if (opt_tbl[EBPF_OBJECT] && (matches(*argv, "object-file") == 0 || strcmp(*argv, "obj") == 0)) { cfg->mode = EBPF_OBJECT; } else if (opt_tbl[EBPF_PINNED] && (matches(*argv, "object-pinned") == 0 || matches(*argv, "pinned") == 0 || matches(*argv, "fd") == 0)) { cfg->mode = EBPF_PINNED; } else { fprintf(stderr, "What mode is \"%s\"?\n", *argv); return -1; } NEXT_ARG(); file = section = uds_name = NULL; if (cfg->mode == EBPF_OBJECT || cfg->mode == EBPF_PINNED) { file = *argv; NEXT_ARG_FWD(); if (cfg->type == BPF_PROG_TYPE_UNSPEC) { if (argc > 0 && matches(*argv, "type") == 0) { NEXT_ARG(); for (i = 0; i < ARRAY_SIZE(__bpf_prog_meta); i++) { if (!__bpf_prog_meta[i].type) continue; if (!matches(*argv, __bpf_prog_meta[i].type)) { cfg->type = i; break; } } if (cfg->type == BPF_PROG_TYPE_UNSPEC) { fprintf(stderr, "What type is \"%s\"?\n", *argv); return -1; } NEXT_ARG_FWD(); } else { cfg->type = BPF_PROG_TYPE_SCHED_CLS; } } section = bpf_prog_to_default_section(cfg->type); if (argc > 0 && matches(*argv, "section") == 0) { NEXT_ARG(); section = *argv; NEXT_ARG_FWD(); } if (__bpf_prog_meta[cfg->type].may_uds_export) { uds_name = getenv(BPF_ENV_UDS); if (argc > 0 && !uds_name && matches(*argv, "export") == 0) { NEXT_ARG(); uds_name = *argv; NEXT_ARG_FWD(); } } if (argc > 0 && matches(*argv, "verbose") == 0) { verbose = true; NEXT_ARG_FWD(); } PREV_ARG(); } if (cfg->mode == CBPF_BYTECODE || cfg->mode == CBPF_FILE) { ret = bpf_ops_parse(argc, argv, cfg->opcodes, cfg->mode == CBPF_FILE); cfg->n_opcodes = ret; } else if (cfg->mode == EBPF_OBJECT) { ret = 0; /* program will be loaded by load stage */ } else if (cfg->mode == EBPF_PINNED) { ret = bpf_obj_pinned(file, cfg->type); cfg->prog_fd = ret; } else { return -1; } cfg->object = file; cfg->section = section; cfg->uds = uds_name; cfg->argc = argc; cfg->argv = argv; cfg->verbose = verbose; return ret; } static int bpf_do_load(struct bpf_cfg_in *cfg) { if (cfg->mode == EBPF_OBJECT) { cfg->prog_fd = bpf_obj_open(cfg->object, cfg->type, cfg->section, cfg->ifindex, cfg->verbose); return cfg->prog_fd; } return 0; } int bpf_load_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops, void *nl) { char annotation[256]; int ret; ret = bpf_do_load(cfg); if (ret < 0) return ret; if (cfg->mode == CBPF_BYTECODE || cfg->mode == CBPF_FILE) ops->cbpf_cb(nl, cfg->opcodes, cfg->n_opcodes); if (cfg->mode == EBPF_OBJECT || cfg->mode == EBPF_PINNED) { snprintf(annotation, sizeof(annotation), "%s:[%s]", basename(cfg->object), cfg->mode == EBPF_PINNED ? "*fsobj" : cfg->section); ops->ebpf_cb(nl, cfg->prog_fd, annotation); } return 0; } int bpf_parse_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops) { bool opt_tbl[BPF_MODE_MAX] = {}; if (ops->cbpf_cb) { opt_tbl[CBPF_BYTECODE] = true; opt_tbl[CBPF_FILE] = true; } if (ops->ebpf_cb) { opt_tbl[EBPF_OBJECT] = true; opt_tbl[EBPF_PINNED] = true; } return bpf_do_parse(cfg, opt_tbl); } int bpf_parse_and_load_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops, void *nl) { int ret; ret = bpf_parse_common(cfg, ops); if (ret < 0) return ret; return bpf_load_common(cfg, ops, nl); } int bpf_graft_map(const char *map_path, uint32_t *key, int argc, char **argv) { const bool opt_tbl[BPF_MODE_MAX] = { [EBPF_OBJECT] = true, [EBPF_PINNED] = true, }; const struct bpf_elf_map test = { .type = BPF_MAP_TYPE_PROG_ARRAY, .size_key = sizeof(int), .size_value = sizeof(int), }; struct bpf_cfg_in cfg = { .type = BPF_PROG_TYPE_UNSPEC, .argc = argc, .argv = argv, }; struct bpf_map_ext ext = {}; int ret, prog_fd, map_fd; uint32_t map_key; ret = bpf_do_parse(&cfg, opt_tbl); if (ret < 0) return ret; ret = bpf_do_load(&cfg); if (ret < 0) return ret; prog_fd = cfg.prog_fd; if (key) { map_key = *key; } else { ret = sscanf(cfg.section, "%*i/%i", &map_key); if (ret != 1) { fprintf(stderr, "Couldn\'t infer map key from section name! Please provide \'key\' argument!\n"); ret = -EINVAL; goto out_prog; } } map_fd = bpf_obj_get(map_path, cfg.type); if (map_fd < 0) { fprintf(stderr, "Couldn\'t retrieve pinned map \'%s\': %s\n", map_path, strerror(errno)); ret = map_fd; goto out_prog; } ret = bpf_map_selfcheck_pinned(map_fd, &test, &ext, offsetof(struct bpf_elf_map, max_elem), cfg.type); if (ret < 0) { fprintf(stderr, "Map \'%s\' self-check failed!\n", map_path); goto out_map; } ret = bpf_map_update(map_fd, &map_key, &prog_fd, BPF_ANY); if (ret < 0) fprintf(stderr, "Map update failed: %s\n", strerror(errno)); out_map: close(map_fd); out_prog: close(prog_fd); return ret; } int bpf_prog_attach_fd(int prog_fd, int target_fd, enum bpf_attach_type type) { union bpf_attr attr = {}; attr.target_fd = target_fd; attr.attach_bpf_fd = prog_fd; attr.attach_type = type; return bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)); } int bpf_prog_detach_fd(int target_fd, enum bpf_attach_type type) { union bpf_attr attr = {}; attr.target_fd = target_fd; attr.attach_type = type; return bpf(BPF_PROG_DETACH, &attr, sizeof(attr)); } static int bpf_prog_load_dev(enum bpf_prog_type type, const struct bpf_insn *insns, size_t size_insns, const char *license, __u32 ifindex, char *log, size_t size_log) { union bpf_attr attr = {}; attr.prog_type = type; attr.insns = bpf_ptr_to_u64(insns); attr.insn_cnt = size_insns / sizeof(struct bpf_insn); attr.license = bpf_ptr_to_u64(license); attr.prog_ifindex = ifindex; if (size_log > 0) { attr.log_buf = bpf_ptr_to_u64(log); attr.log_size = size_log; attr.log_level = 1; } return bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); } int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns, size_t size_insns, const char *license, char *log, size_t size_log) { return bpf_prog_load_dev(type, insns, size_insns, license, 0, log, size_log); } #ifdef HAVE_ELF struct bpf_elf_prog { enum bpf_prog_type type; struct bpf_insn *insns; unsigned int insns_num; size_t size; const char *license; }; struct bpf_hash_entry { unsigned int pinning; const char *subpath; struct bpf_hash_entry *next; }; struct bpf_config { unsigned int jit_enabled; }; struct bpf_btf { const struct btf_header *hdr; const void *raw; const char *strings; const struct btf_type **types; int types_num; }; struct bpf_elf_ctx { struct bpf_config cfg; Elf *elf_fd; GElf_Ehdr elf_hdr; Elf_Data *sym_tab; Elf_Data *str_tab; Elf_Data *btf_data; char obj_uid[64]; int obj_fd; int btf_fd; int map_fds[ELF_MAX_MAPS]; struct bpf_elf_map maps[ELF_MAX_MAPS]; struct bpf_map_ext maps_ext[ELF_MAX_MAPS]; struct bpf_elf_prog prog_text; struct bpf_btf btf; int sym_num; int map_num; int map_len; bool *sec_done; int sec_maps; int sec_text; int sec_btf; char license[ELF_MAX_LICENSE_LEN]; enum bpf_prog_type type; __u32 ifindex; bool verbose; bool noafalg; struct bpf_elf_st stat; struct bpf_hash_entry *ht[256]; char *log; size_t log_size; }; struct bpf_elf_sec_data { GElf_Shdr sec_hdr; Elf_Data *sec_data; const char *sec_name; }; struct bpf_map_data { int *fds; const char *obj; struct bpf_elf_st *st; struct bpf_elf_map *ent; }; static bool bpf_log_has_data(struct bpf_elf_ctx *ctx) { return ctx->log && ctx->log[0]; } static __check_format_string(2, 3) void bpf_dump_error(struct bpf_elf_ctx *ctx, const char *format, ...) { va_list vl; va_start(vl, format); vfprintf(stderr, format, vl); va_end(vl); if (bpf_log_has_data(ctx)) { if (ctx->verbose) { fprintf(stderr, "%s\n", ctx->log); } else { unsigned int off = 0, len = strlen(ctx->log); if (len > BPF_MAX_LOG) { off = len - BPF_MAX_LOG; fprintf(stderr, "Skipped %u bytes, use \'verb\' option for the full verbose log.\n[...]\n", off); } fprintf(stderr, "%s\n", ctx->log + off); } memset(ctx->log, 0, ctx->log_size); } } static int bpf_log_realloc(struct bpf_elf_ctx *ctx) { const size_t log_max = UINT_MAX >> 8; size_t log_size = ctx->log_size; char *ptr; if (!ctx->log) { log_size = 65536; } else if (log_size < log_max) { log_size <<= 1; if (log_size > log_max) log_size = log_max; } else { return -EINVAL; } ptr = realloc(ctx->log, log_size); if (!ptr) return -ENOMEM; ptr[0] = 0; ctx->log = ptr; ctx->log_size = log_size; return 0; } static int bpf_map_create(enum bpf_map_type type, uint32_t size_key, uint32_t size_value, uint32_t max_elem, uint32_t flags, int inner_fd, int btf_fd, uint32_t ifindex, uint32_t btf_id_key, uint32_t btf_id_val) { union bpf_attr attr = {}; attr.map_type = type; attr.key_size = size_key; attr.value_size = inner_fd ? sizeof(int) : size_value; attr.max_entries = max_elem; attr.map_flags = flags; attr.inner_map_fd = inner_fd; attr.map_ifindex = ifindex; attr.btf_fd = btf_fd; attr.btf_key_type_id = btf_id_key; attr.btf_value_type_id = btf_id_val; return bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); } static int bpf_btf_load(void *btf, size_t size_btf, char *log, size_t size_log) { union bpf_attr attr = {}; attr.btf = bpf_ptr_to_u64(btf); attr.btf_size = size_btf; if (size_log > 0) { attr.btf_log_buf = bpf_ptr_to_u64(log); attr.btf_log_size = size_log; attr.btf_log_level = 1; } return bpf(BPF_BTF_LOAD, &attr, sizeof(attr)); } static int bpf_obj_pin(int fd, const char *pathname) { union bpf_attr attr = {}; attr.pathname = bpf_ptr_to_u64(pathname); attr.bpf_fd = fd; return bpf(BPF_OBJ_PIN, &attr, sizeof(attr)); } static int bpf_obj_hash(const char *object, uint8_t *out, size_t len) { struct sockaddr_alg alg = { .salg_family = AF_ALG, .salg_type = "hash", .salg_name = "sha1", }; int ret, cfd, ofd, ffd; struct stat stbuff; ssize_t size; if (!object || len != 20) return -EINVAL; cfd = socket(AF_ALG, SOCK_SEQPACKET, 0); if (cfd < 0) return cfd; ret = bind(cfd, (struct sockaddr *)&alg, sizeof(alg)); if (ret < 0) goto out_cfd; ofd = accept(cfd, NULL, 0); if (ofd < 0) { ret = ofd; goto out_cfd; } ffd = open(object, O_RDONLY); if (ffd < 0) { fprintf(stderr, "Error opening object %s: %s\n", object, strerror(errno)); ret = ffd; goto out_ofd; } ret = fstat(ffd, &stbuff); if (ret < 0) { fprintf(stderr, "Error doing fstat: %s\n", strerror(errno)); goto out_ffd; } size = sendfile(ofd, ffd, NULL, stbuff.st_size); if (size != stbuff.st_size) { fprintf(stderr, "Error from sendfile (%zd vs %zu bytes): %s\n", size, stbuff.st_size, strerror(errno)); ret = -1; goto out_ffd; } size = read(ofd, out, len); if (size != len) { fprintf(stderr, "Error from read (%zd vs %zu bytes): %s\n", size, len, strerror(errno)); ret = -1; } else { ret = 0; } out_ffd: close(ffd); out_ofd: close(ofd); out_cfd: close(cfd); return ret; } static void bpf_init_env(void) { struct rlimit limit = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY, }; /* Don't bother in case we fail! */ setrlimit(RLIMIT_MEMLOCK, &limit); if (!bpf_get_work_dir(BPF_PROG_TYPE_UNSPEC)) fprintf(stderr, "Continuing without mounted eBPF fs. Too old kernel?\n"); } static const char *bpf_custom_pinning(const struct bpf_elf_ctx *ctx, uint32_t pinning) { struct bpf_hash_entry *entry; entry = ctx->ht[pinning & (ARRAY_SIZE(ctx->ht) - 1)]; while (entry && entry->pinning != pinning) entry = entry->next; return entry ? entry->subpath : NULL; } static bool bpf_no_pinning(const struct bpf_elf_ctx *ctx, uint32_t pinning) { switch (pinning) { case PIN_OBJECT_NS: case PIN_GLOBAL_NS: return false; case PIN_NONE: return true; default: return !bpf_custom_pinning(ctx, pinning); } } static void bpf_make_pathname(char *pathname, size_t len, const char *name, const struct bpf_elf_ctx *ctx, uint32_t pinning) { switch (pinning) { case PIN_OBJECT_NS: snprintf(pathname, len, "%s/%s/%s", bpf_get_work_dir(ctx->type), ctx->obj_uid, name); break; case PIN_GLOBAL_NS: snprintf(pathname, len, "%s/%s/%s", bpf_get_work_dir(ctx->type), BPF_DIR_GLOBALS, name); break; default: snprintf(pathname, len, "%s/../%s/%s", bpf_get_work_dir(ctx->type), bpf_custom_pinning(ctx, pinning), name); break; } } static int bpf_probe_pinned(const char *name, const struct bpf_elf_ctx *ctx, uint32_t pinning) { char pathname[PATH_MAX]; if (bpf_no_pinning(ctx, pinning) || !bpf_get_work_dir(ctx->type)) return 0; bpf_make_pathname(pathname, sizeof(pathname), name, ctx, pinning); return bpf_obj_get(pathname, ctx->type); } static int bpf_make_obj_path(const struct bpf_elf_ctx *ctx) { char *tmp = NULL; int ret; ret = asprintf(&tmp, "%s/%s", bpf_get_work_dir(ctx->type), ctx->obj_uid); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } ret = mkdir(tmp, S_IRWXU); if (ret && errno != EEXIST) { fprintf(stderr, "mkdir %s failed: %s\n", tmp, strerror(errno)); goto out; } ret = 0; out: free(tmp); return ret; } static int bpf_make_custom_path(const struct bpf_elf_ctx *ctx, const char *todo) { char *tmp = NULL; char *rem = NULL; char *sub; int ret; ret = asprintf(&tmp, "%s/../", bpf_get_work_dir(ctx->type)); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } ret = asprintf(&rem, "%s/", todo); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); goto out; } sub = strtok(rem, "/"); while (sub) { if (strlen(tmp) + strlen(sub) + 2 > PATH_MAX) return -EINVAL; strcat(tmp, sub); strcat(tmp, "/"); ret = mkdir(tmp, S_IRWXU); if (ret && errno != EEXIST) { fprintf(stderr, "mkdir %s failed: %s\n", tmp, strerror(errno)); goto out; } sub = strtok(NULL, "/"); } ret = 0; out: free(rem); free(tmp); return ret; } static int bpf_place_pinned(int fd, const char *name, const struct bpf_elf_ctx *ctx, uint32_t pinning) { char pathname[PATH_MAX]; const char *tmp; int ret = 0; if (bpf_no_pinning(ctx, pinning) || !bpf_get_work_dir(ctx->type)) return 0; if (pinning == PIN_OBJECT_NS) ret = bpf_make_obj_path(ctx); else if ((tmp = bpf_custom_pinning(ctx, pinning))) ret = bpf_make_custom_path(ctx, tmp); if (ret < 0) return ret; bpf_make_pathname(pathname, sizeof(pathname), name, ctx, pinning); return bpf_obj_pin(fd, pathname); } static void bpf_prog_report(int fd, const char *section, const struct bpf_elf_prog *prog, struct bpf_elf_ctx *ctx) { unsigned int insns = prog->size / sizeof(struct bpf_insn); fprintf(stderr, "\nProg section \'%s\' %s%s (%d)!\n", section, fd < 0 ? "rejected: " : "loaded", fd < 0 ? strerror(errno) : "", fd < 0 ? errno : fd); fprintf(stderr, " - Type: %u\n", prog->type); fprintf(stderr, " - Instructions: %u (%u over limit)\n", insns, insns > BPF_MAXINSNS ? insns - BPF_MAXINSNS : 0); fprintf(stderr, " - License: %s\n\n", prog->license); bpf_dump_error(ctx, "Verifier analysis:\n\n"); } static int bpf_prog_attach(const char *section, const struct bpf_elf_prog *prog, struct bpf_elf_ctx *ctx) { int tries = 0, fd; retry: errno = 0; fd = bpf_prog_load_dev(prog->type, prog->insns, prog->size, prog->license, ctx->ifindex, ctx->log, ctx->log_size); if (fd < 0 || ctx->verbose) { /* The verifier log is pretty chatty, sometimes so chatty * on larger programs, that we could fail to dump everything * into our buffer. Still, try to give a debuggable error * log for the user, so enlarge it and re-fail. */ if (fd < 0 && (errno == ENOSPC || !ctx->log_size)) { if (tries++ < 10 && !bpf_log_realloc(ctx)) goto retry; fprintf(stderr, "Log buffer too small to dump verifier log %zu bytes (%d tries)!\n", ctx->log_size, tries); return fd; } bpf_prog_report(fd, section, prog, ctx); } return fd; } static void bpf_map_report(int fd, const char *name, const struct bpf_elf_map *map, struct bpf_elf_ctx *ctx, int inner_fd) { fprintf(stderr, "Map object \'%s\' %s%s (%d)!\n", name, fd < 0 ? "rejected: " : "loaded", fd < 0 ? strerror(errno) : "", fd < 0 ? errno : fd); fprintf(stderr, " - Type: %u\n", map->type); fprintf(stderr, " - Identifier: %u\n", map->id); fprintf(stderr, " - Pinning: %u\n", map->pinning); fprintf(stderr, " - Size key: %u\n", map->size_key); fprintf(stderr, " - Size value: %u\n", inner_fd ? (int)sizeof(int) : map->size_value); fprintf(stderr, " - Max elems: %u\n", map->max_elem); fprintf(stderr, " - Flags: %#x\n\n", map->flags); } static int bpf_find_map_id(const struct bpf_elf_ctx *ctx, uint32_t id) { int i; for (i = 0; i < ctx->map_num; i++) { if (ctx->maps[i].id != id) continue; if (ctx->map_fds[i] < 0) return -EINVAL; return ctx->map_fds[i]; } return -ENOENT; } static void bpf_report_map_in_map(int outer_fd, uint32_t idx) { struct bpf_elf_map outer_map; int ret; fprintf(stderr, "Cannot insert map into map! "); ret = bpf_derive_elf_map_from_fdinfo(outer_fd, &outer_map, NULL); if (!ret) { if (idx >= outer_map.max_elem && outer_map.type == BPF_MAP_TYPE_ARRAY_OF_MAPS) { fprintf(stderr, "Outer map has %u elements, index %u is invalid!\n", outer_map.max_elem, idx); return; } } fprintf(stderr, "Different map specs used for outer and inner map?\n"); } static bool bpf_is_map_in_map_type(const struct bpf_elf_map *map) { return map->type == BPF_MAP_TYPE_ARRAY_OF_MAPS || map->type == BPF_MAP_TYPE_HASH_OF_MAPS; } static bool bpf_map_offload_neutral(enum bpf_map_type type) { return type == BPF_MAP_TYPE_PERF_EVENT_ARRAY; } static int bpf_map_attach(const char *name, struct bpf_elf_ctx *ctx, const struct bpf_elf_map *map, struct bpf_map_ext *ext, int *have_map_in_map) { int fd, ifindex, ret, map_inner_fd = 0; bool retried = false; probe: fd = bpf_probe_pinned(name, ctx, map->pinning); if (fd > 0) { ret = bpf_map_selfcheck_pinned(fd, map, ext, offsetof(struct bpf_elf_map, id), ctx->type); if (ret < 0) { close(fd); fprintf(stderr, "Map \'%s\' self-check failed!\n", name); return ret; } if (ctx->verbose) fprintf(stderr, "Map \'%s\' loaded as pinned!\n", name); return fd; } if (have_map_in_map && bpf_is_map_in_map_type(map)) { (*have_map_in_map)++; if (map->inner_id) return 0; fprintf(stderr, "Map \'%s\' cannot be created since no inner map ID defined!\n", name); return -EINVAL; } if (!have_map_in_map && bpf_is_map_in_map_type(map)) { map_inner_fd = bpf_find_map_id(ctx, map->inner_id); if (map_inner_fd < 0) { fprintf(stderr, "Map \'%s\' cannot be loaded. Inner map with ID %u not found!\n", name, map->inner_id); return -EINVAL; } } ifindex = bpf_map_offload_neutral(map->type) ? 0 : ctx->ifindex; errno = 0; fd = bpf_map_create(map->type, map->size_key, map->size_value, map->max_elem, map->flags, map_inner_fd, ctx->btf_fd, ifindex, ext->btf_id_key, ext->btf_id_val); if (fd < 0 || ctx->verbose) { bpf_map_report(fd, name, map, ctx, map_inner_fd); if (fd < 0) return fd; } ret = bpf_place_pinned(fd, name, ctx, map->pinning); if (ret < 0) { close(fd); if (!retried && errno == EEXIST) { retried = true; goto probe; } fprintf(stderr, "Could not pin %s map: %s\n", name, strerror(errno)); return ret; } return fd; } static const char *bpf_str_tab_name(const struct bpf_elf_ctx *ctx, const GElf_Sym *sym) { return ctx->str_tab->d_buf + sym->st_name; } static int bpf_btf_find(struct bpf_elf_ctx *ctx, const char *name) { const struct btf_type *type; const char *res; int id; for (id = 1; id < ctx->btf.types_num; id++) { type = ctx->btf.types[id]; if (type->name_off >= ctx->btf.hdr->str_len) continue; res = &ctx->btf.strings[type->name_off]; if (!strcmp(res, name)) return id; } return -ENOENT; } static int bpf_btf_find_kv(struct bpf_elf_ctx *ctx, const struct bpf_elf_map *map, const char *name, uint32_t *id_key, uint32_t *id_val) { const struct btf_member *key, *val; const struct btf_type *type; char btf_name[512]; const char *res; int id; snprintf(btf_name, sizeof(btf_name), "____btf_map_%s", name); id = bpf_btf_find(ctx, btf_name); if (id < 0) return id; type = ctx->btf.types[id]; if (BTF_INFO_KIND(type->info) != BTF_KIND_STRUCT) return -EINVAL; if (BTF_INFO_VLEN(type->info) != 2) return -EINVAL; key = ((void *) type) + sizeof(*type); val = key + 1; if (!key->type || key->type >= ctx->btf.types_num || !val->type || val->type >= ctx->btf.types_num) return -EINVAL; if (key->name_off >= ctx->btf.hdr->str_len || val->name_off >= ctx->btf.hdr->str_len) return -EINVAL; res = &ctx->btf.strings[key->name_off]; if (strcmp(res, "key")) return -EINVAL; res = &ctx->btf.strings[val->name_off]; if (strcmp(res, "value")) return -EINVAL; *id_key = key->type; *id_val = val->type; return 0; } static void bpf_btf_annotate(struct bpf_elf_ctx *ctx, int which, const char *name) { uint32_t id_key = 0, id_val = 0; if (!bpf_btf_find_kv(ctx, &ctx->maps[which], name, &id_key, &id_val)) { ctx->maps_ext[which].btf_id_key = id_key; ctx->maps_ext[which].btf_id_val = id_val; } } static const char *bpf_map_fetch_name(struct bpf_elf_ctx *ctx, int which) { const char *name; GElf_Sym sym; int i; for (i = 0; i < ctx->sym_num; i++) { int type; if (gelf_getsym(ctx->sym_tab, i, &sym) != &sym) continue; type = GELF_ST_TYPE(sym.st_info); if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL || (type != STT_NOTYPE && type != STT_OBJECT) || sym.st_shndx != ctx->sec_maps || sym.st_value / ctx->map_len != which) continue; name = bpf_str_tab_name(ctx, &sym); bpf_btf_annotate(ctx, which, name); return name; } return NULL; } static int bpf_maps_attach_all(struct bpf_elf_ctx *ctx) { int i, j, ret, fd, inner_fd, inner_idx, have_map_in_map = 0; const char *map_name; for (i = 0; i < ctx->map_num; i++) { if (ctx->maps[i].pinning == PIN_OBJECT_NS && ctx->noafalg) { fprintf(stderr, "Missing kernel AF_ALG support for PIN_OBJECT_NS!\n"); return -ENOTSUP; } map_name = bpf_map_fetch_name(ctx, i); if (!map_name) return -EIO; fd = bpf_map_attach(map_name, ctx, &ctx->maps[i], &ctx->maps_ext[i], &have_map_in_map); if (fd < 0) return fd; ctx->map_fds[i] = !fd ? -1 : fd; } for (i = 0; have_map_in_map && i < ctx->map_num; i++) { if (ctx->map_fds[i] >= 0) continue; map_name = bpf_map_fetch_name(ctx, i); if (!map_name) return -EIO; fd = bpf_map_attach(map_name, ctx, &ctx->maps[i], &ctx->maps_ext[i], NULL); if (fd < 0) return fd; ctx->map_fds[i] = fd; } for (i = 0; have_map_in_map && i < ctx->map_num; i++) { if (!ctx->maps[i].id || ctx->maps[i].inner_id || ctx->maps[i].inner_idx == -1) continue; inner_fd = ctx->map_fds[i]; inner_idx = ctx->maps[i].inner_idx; for (j = 0; j < ctx->map_num; j++) { if (!bpf_is_map_in_map_type(&ctx->maps[j])) continue; if (ctx->maps[j].inner_id != ctx->maps[i].id) continue; ret = bpf_map_update(ctx->map_fds[j], &inner_idx, &inner_fd, BPF_ANY); if (ret < 0) { bpf_report_map_in_map(ctx->map_fds[j], inner_idx); return ret; } } } return 0; } static int bpf_map_num_sym(struct bpf_elf_ctx *ctx) { int i, num = 0; GElf_Sym sym; for (i = 0; i < ctx->sym_num; i++) { int type; if (gelf_getsym(ctx->sym_tab, i, &sym) != &sym) continue; type = GELF_ST_TYPE(sym.st_info); if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL || (type != STT_NOTYPE && type != STT_OBJECT) || sym.st_shndx != ctx->sec_maps) continue; num++; } return num; } static int bpf_fill_section_data(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { Elf_Data *sec_edata; GElf_Shdr sec_hdr; Elf_Scn *sec_fd; char *sec_name; memset(data, 0, sizeof(*data)); sec_fd = elf_getscn(ctx->elf_fd, section); if (!sec_fd) return -EINVAL; if (gelf_getshdr(sec_fd, &sec_hdr) != &sec_hdr) return -EIO; sec_name = elf_strptr(ctx->elf_fd, ctx->elf_hdr.e_shstrndx, sec_hdr.sh_name); if (!sec_name || !sec_hdr.sh_size) return -ENOENT; sec_edata = elf_getdata(sec_fd, NULL); if (!sec_edata || elf_getdata(sec_fd, sec_edata)) return -EIO; memcpy(&data->sec_hdr, &sec_hdr, sizeof(sec_hdr)); data->sec_name = sec_name; data->sec_data = sec_edata; return 0; } struct bpf_elf_map_min { __u32 type; __u32 size_key; __u32 size_value; __u32 max_elem; }; static int bpf_fetch_maps_begin(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { ctx->map_num = data->sec_data->d_size; ctx->sec_maps = section; ctx->sec_done[section] = true; if (ctx->map_num > sizeof(ctx->maps)) { fprintf(stderr, "Too many BPF maps in ELF section!\n"); return -ENOMEM; } memcpy(ctx->maps, data->sec_data->d_buf, ctx->map_num); return 0; } static int bpf_map_verify_all_offs(struct bpf_elf_ctx *ctx, int end) { GElf_Sym sym; int off, i; for (off = 0; off < end; off += ctx->map_len) { /* Order doesn't need to be linear here, hence we walk * the table again. */ for (i = 0; i < ctx->sym_num; i++) { int type; if (gelf_getsym(ctx->sym_tab, i, &sym) != &sym) continue; type = GELF_ST_TYPE(sym.st_info); if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL || (type != STT_NOTYPE && type != STT_OBJECT) || sym.st_shndx != ctx->sec_maps) continue; if (sym.st_value == off) break; if (i == ctx->sym_num - 1) return -1; } } return off == end ? 0 : -1; } static int bpf_fetch_maps_end(struct bpf_elf_ctx *ctx) { struct bpf_elf_map fixup[ARRAY_SIZE(ctx->maps)] = {}; int i, sym_num = bpf_map_num_sym(ctx); __u8 *buff; if (sym_num == 0 || sym_num > ARRAY_SIZE(ctx->maps)) { fprintf(stderr, "%u maps not supported in current map section!\n", sym_num); return -EINVAL; } if (ctx->map_num % sym_num != 0 || ctx->map_num % sizeof(__u32) != 0) { fprintf(stderr, "Number BPF map symbols are not multiple of struct bpf_elf_map!\n"); return -EINVAL; } ctx->map_len = ctx->map_num / sym_num; if (bpf_map_verify_all_offs(ctx, ctx->map_num)) { fprintf(stderr, "Different struct bpf_elf_map in use!\n"); return -EINVAL; } if (ctx->map_len == sizeof(struct bpf_elf_map)) { ctx->map_num = sym_num; return 0; } else if (ctx->map_len > sizeof(struct bpf_elf_map)) { fprintf(stderr, "struct bpf_elf_map not supported, coming from future version?\n"); return -EINVAL; } else if (ctx->map_len < sizeof(struct bpf_elf_map_min)) { fprintf(stderr, "struct bpf_elf_map too small, not supported!\n"); return -EINVAL; } ctx->map_num = sym_num; for (i = 0, buff = (void *)ctx->maps; i < ctx->map_num; i++, buff += ctx->map_len) { /* The fixup leaves the rest of the members as zero, which * is fine currently, but option exist to set some other * default value as well when needed in future. */ memcpy(&fixup[i], buff, ctx->map_len); } memcpy(ctx->maps, fixup, sizeof(fixup)); if (ctx->verbose) printf("%zu bytes struct bpf_elf_map fixup performed due to size mismatch!\n", sizeof(struct bpf_elf_map) - ctx->map_len); return 0; } static int bpf_fetch_license(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { if (data->sec_data->d_size > sizeof(ctx->license)) return -ENOMEM; memcpy(ctx->license, data->sec_data->d_buf, data->sec_data->d_size); ctx->sec_done[section] = true; return 0; } static int bpf_fetch_symtab(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { ctx->sym_tab = data->sec_data; ctx->sym_num = data->sec_hdr.sh_size / data->sec_hdr.sh_entsize; ctx->sec_done[section] = true; return 0; } static int bpf_fetch_strtab(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { ctx->str_tab = data->sec_data; ctx->sec_done[section] = true; return 0; } static int bpf_fetch_text(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { ctx->sec_text = section; ctx->sec_done[section] = true; return 0; } static void bpf_btf_report(int fd, struct bpf_elf_ctx *ctx) { fprintf(stderr, "\nBTF debug data section \'.BTF\' %s%s (%d)!\n", fd < 0 ? "rejected: " : "loaded", fd < 0 ? strerror(errno) : "", fd < 0 ? errno : fd); fprintf(stderr, " - Length: %zu\n", ctx->btf_data->d_size); bpf_dump_error(ctx, "Verifier analysis:\n\n"); } static int bpf_btf_attach(struct bpf_elf_ctx *ctx) { int tries = 0, fd; retry: errno = 0; fd = bpf_btf_load(ctx->btf_data->d_buf, ctx->btf_data->d_size, ctx->log, ctx->log_size); if (fd < 0 || ctx->verbose) { if (fd < 0 && (errno == ENOSPC || !ctx->log_size)) { if (tries++ < 10 && !bpf_log_realloc(ctx)) goto retry; fprintf(stderr, "Log buffer too small to dump verifier log %zu bytes (%d tries)!\n", ctx->log_size, tries); return fd; } if (bpf_log_has_data(ctx)) bpf_btf_report(fd, ctx); } return fd; } static int bpf_fetch_btf_begin(struct bpf_elf_ctx *ctx, int section, struct bpf_elf_sec_data *data) { ctx->btf_data = data->sec_data; ctx->sec_btf = section; ctx->sec_done[section] = true; return 0; } static int bpf_btf_check_header(struct bpf_elf_ctx *ctx) { const struct btf_header *hdr = ctx->btf_data->d_buf; const char *str_start, *str_end; unsigned int data_len; if (hdr->magic != BTF_MAGIC) { fprintf(stderr, "Object has wrong BTF magic: %x, expected: %x!\n", hdr->magic, BTF_MAGIC); return -EINVAL; } if (hdr->version != BTF_VERSION) { fprintf(stderr, "Object has wrong BTF version: %u, expected: %u!\n", hdr->version, BTF_VERSION); return -EINVAL; } if (hdr->flags) { fprintf(stderr, "Object has unsupported BTF flags %x!\n", hdr->flags); return -EINVAL; } data_len = ctx->btf_data->d_size - sizeof(*hdr); if (data_len < hdr->type_off || data_len < hdr->str_off || data_len < hdr->type_len + hdr->str_len || hdr->type_off >= hdr->str_off || hdr->type_off + hdr->type_len != hdr->str_off || hdr->str_off + hdr->str_len != data_len || (hdr->type_off & (sizeof(uint32_t) - 1))) { fprintf(stderr, "Object has malformed BTF data!\n"); return -EINVAL; } ctx->btf.hdr = hdr; ctx->btf.raw = hdr + 1; str_start = ctx->btf.raw + hdr->str_off; str_end = str_start + hdr->str_len; if (!hdr->str_len || hdr->str_len - 1 > BTF_MAX_NAME_OFFSET || str_start[0] || str_end[-1]) { fprintf(stderr, "Object has malformed BTF string data!\n"); return -EINVAL; } ctx->btf.strings = str_start; return 0; } static int bpf_btf_register_type(struct bpf_elf_ctx *ctx, const struct btf_type *type) { int cur = ctx->btf.types_num, num = cur + 1; const struct btf_type **types; types = realloc(ctx->btf.types, num * sizeof(type)); if (!types) { free(ctx->btf.types); ctx->btf.types = NULL; ctx->btf.types_num = 0; return -ENOMEM; } ctx->btf.types = types; ctx->btf.types[cur] = type; ctx->btf.types_num = num; return 0; } static struct btf_type btf_type_void; static int bpf_btf_prep_type_data(struct bpf_elf_ctx *ctx) { const void *type_cur = ctx->btf.raw + ctx->btf.hdr->type_off; const void *type_end = ctx->btf.raw + ctx->btf.hdr->str_off; const struct btf_type *type; uint16_t var_len; int ret, kind; ret = bpf_btf_register_type(ctx, &btf_type_void); if (ret < 0) return ret; while (type_cur < type_end) { type = type_cur; type_cur += sizeof(*type); var_len = BTF_INFO_VLEN(type->info); kind = BTF_INFO_KIND(type->info); switch (kind) { case BTF_KIND_INT: type_cur += sizeof(int); break; case BTF_KIND_ARRAY: type_cur += sizeof(struct btf_array); break; case BTF_KIND_STRUCT: case BTF_KIND_UNION: type_cur += var_len * sizeof(struct btf_member); break; case BTF_KIND_ENUM: type_cur += var_len * sizeof(struct btf_enum); break; case BTF_KIND_FUNC_PROTO: type_cur += var_len * sizeof(struct btf_param); break; case BTF_KIND_TYPEDEF: case BTF_KIND_PTR: case BTF_KIND_FWD: case BTF_KIND_VOLATILE: case BTF_KIND_CONST: case BTF_KIND_RESTRICT: case BTF_KIND_FUNC: break; default: fprintf(stderr, "Object has unknown BTF type: %u!\n", kind); return -EINVAL; } ret = bpf_btf_register_type(ctx, type); if (ret < 0) return ret; } return 0; } static int bpf_btf_prep_data(struct bpf_elf_ctx *ctx) { int ret = bpf_btf_check_header(ctx); if (!ret) return bpf_btf_prep_type_data(ctx); return ret; } static void bpf_fetch_btf_end(struct bpf_elf_ctx *ctx) { int fd = bpf_btf_attach(ctx); if (fd < 0) return; ctx->btf_fd = fd; if (bpf_btf_prep_data(ctx) < 0) { close(ctx->btf_fd); ctx->btf_fd = 0; } } static bool bpf_has_map_data(const struct bpf_elf_ctx *ctx) { return ctx->sym_tab && ctx->str_tab && ctx->sec_maps; } static bool bpf_has_btf_data(const struct bpf_elf_ctx *ctx) { return ctx->sec_btf; } static bool bpf_has_call_data(const struct bpf_elf_ctx *ctx) { return ctx->sec_text; } static int bpf_fetch_ancillary(struct bpf_elf_ctx *ctx, bool check_text_sec) { struct bpf_elf_sec_data data; int i, ret = -1; for (i = 1; i < ctx->elf_hdr.e_shnum; i++) { ret = bpf_fill_section_data(ctx, i, &data); if (ret < 0) continue; if (data.sec_hdr.sh_type == SHT_PROGBITS && !strcmp(data.sec_name, ELF_SECTION_MAPS)) ret = bpf_fetch_maps_begin(ctx, i, &data); else if (data.sec_hdr.sh_type == SHT_PROGBITS && !strcmp(data.sec_name, ELF_SECTION_LICENSE)) ret = bpf_fetch_license(ctx, i, &data); else if (data.sec_hdr.sh_type == SHT_PROGBITS && (data.sec_hdr.sh_flags & SHF_EXECINSTR) && !strcmp(data.sec_name, ".text") && check_text_sec) ret = bpf_fetch_text(ctx, i, &data); else if (data.sec_hdr.sh_type == SHT_SYMTAB && !strcmp(data.sec_name, ".symtab")) ret = bpf_fetch_symtab(ctx, i, &data); else if (data.sec_hdr.sh_type == SHT_STRTAB && !strcmp(data.sec_name, ".strtab")) ret = bpf_fetch_strtab(ctx, i, &data); else if (data.sec_hdr.sh_type == SHT_PROGBITS && !strcmp(data.sec_name, ".BTF")) ret = bpf_fetch_btf_begin(ctx, i, &data); if (ret < 0) { fprintf(stderr, "Error parsing section %d! Perhaps check with readelf -a?\n", i); return ret; } } if (bpf_has_btf_data(ctx)) bpf_fetch_btf_end(ctx); if (bpf_has_map_data(ctx)) { ret = bpf_fetch_maps_end(ctx); if (ret < 0) { fprintf(stderr, "Error fixing up map structure, incompatible struct bpf_elf_map used?\n"); return ret; } ret = bpf_maps_attach_all(ctx); if (ret < 0) { fprintf(stderr, "Error loading maps into kernel!\n"); return ret; } } return ret; } static int bpf_fetch_prog(struct bpf_elf_ctx *ctx, const char *section, bool *sseen) { struct bpf_elf_sec_data data; struct bpf_elf_prog prog; int ret, i, fd = -1; for (i = 1; i < ctx->elf_hdr.e_shnum; i++) { if (ctx->sec_done[i]) continue; ret = bpf_fill_section_data(ctx, i, &data); if (ret < 0 || !(data.sec_hdr.sh_type == SHT_PROGBITS && (data.sec_hdr.sh_flags & SHF_EXECINSTR) && !strcmp(data.sec_name, section))) continue; *sseen = true; memset(&prog, 0, sizeof(prog)); prog.type = ctx->type; prog.license = ctx->license; prog.size = data.sec_data->d_size; prog.insns_num = prog.size / sizeof(struct bpf_insn); prog.insns = data.sec_data->d_buf; fd = bpf_prog_attach(section, &prog, ctx); if (fd < 0) return fd; ctx->sec_done[i] = true; break; } return fd; } struct bpf_relo_props { struct bpf_tail_call { unsigned int total; unsigned int jited; } tc; int main_num; }; static int bpf_apply_relo_map(struct bpf_elf_ctx *ctx, struct bpf_elf_prog *prog, GElf_Rel *relo, GElf_Sym *sym, struct bpf_relo_props *props) { unsigned int insn_off = relo->r_offset / sizeof(struct bpf_insn); unsigned int map_idx = sym->st_value / ctx->map_len; if (insn_off >= prog->insns_num) return -EINVAL; if (prog->insns[insn_off].code != (BPF_LD | BPF_IMM | BPF_DW)) { fprintf(stderr, "ELF contains relo data for non ld64 instruction at offset %u! Compiler bug?!\n", insn_off); return -EINVAL; } if (map_idx >= ARRAY_SIZE(ctx->map_fds)) return -EINVAL; if (!ctx->map_fds[map_idx]) return -EINVAL; if (ctx->maps[map_idx].type == BPF_MAP_TYPE_PROG_ARRAY) { props->tc.total++; if (ctx->maps_ext[map_idx].owner.jited || (ctx->maps_ext[map_idx].owner.type == 0 && ctx->cfg.jit_enabled)) props->tc.jited++; } prog->insns[insn_off].src_reg = BPF_PSEUDO_MAP_FD; prog->insns[insn_off].imm = ctx->map_fds[map_idx]; return 0; } static int bpf_apply_relo_call(struct bpf_elf_ctx *ctx, struct bpf_elf_prog *prog, GElf_Rel *relo, GElf_Sym *sym, struct bpf_relo_props *props) { unsigned int insn_off = relo->r_offset / sizeof(struct bpf_insn); struct bpf_elf_prog *prog_text = &ctx->prog_text; if (insn_off >= prog->insns_num) return -EINVAL; if (prog->insns[insn_off].code != (BPF_JMP | BPF_CALL) && prog->insns[insn_off].src_reg != BPF_PSEUDO_CALL) { fprintf(stderr, "ELF contains relo data for non call instruction at offset %u! Compiler bug?!\n", insn_off); return -EINVAL; } if (!props->main_num) { struct bpf_insn *insns = realloc(prog->insns, prog->size + prog_text->size); if (!insns) return -ENOMEM; memcpy(insns + prog->insns_num, prog_text->insns, prog_text->size); props->main_num = prog->insns_num; prog->insns = insns; prog->insns_num += prog_text->insns_num; prog->size += prog_text->size; } prog->insns[insn_off].imm += props->main_num - insn_off; return 0; } static int bpf_apply_relo_data(struct bpf_elf_ctx *ctx, struct bpf_elf_sec_data *data_relo, struct bpf_elf_prog *prog, struct bpf_relo_props *props) { GElf_Shdr *rhdr = &data_relo->sec_hdr; int relo_ent, relo_num = rhdr->sh_size / rhdr->sh_entsize; for (relo_ent = 0; relo_ent < relo_num; relo_ent++) { GElf_Rel relo; GElf_Sym sym; int ret = -EIO; if (gelf_getrel(data_relo->sec_data, relo_ent, &relo) != &relo) return -EIO; if (gelf_getsym(ctx->sym_tab, GELF_R_SYM(relo.r_info), &sym) != &sym) return -EIO; if (sym.st_shndx == ctx->sec_maps) ret = bpf_apply_relo_map(ctx, prog, &relo, &sym, props); else if (sym.st_shndx == ctx->sec_text) ret = bpf_apply_relo_call(ctx, prog, &relo, &sym, props); else fprintf(stderr, "ELF contains non-{map,call} related relo data in entry %u pointing to section %u! Compiler bug?!\n", relo_ent, sym.st_shndx); if (ret < 0) return ret; } return 0; } static int bpf_fetch_prog_relo(struct bpf_elf_ctx *ctx, const char *section, bool *lderr, bool *sseen, struct bpf_elf_prog *prog) { struct bpf_elf_sec_data data_relo, data_insn; int ret, idx, i, fd = -1; for (i = 1; i < ctx->elf_hdr.e_shnum; i++) { struct bpf_relo_props props = {}; ret = bpf_fill_section_data(ctx, i, &data_relo); if (ret < 0 || data_relo.sec_hdr.sh_type != SHT_REL) continue; idx = data_relo.sec_hdr.sh_info; ret = bpf_fill_section_data(ctx, idx, &data_insn); if (ret < 0 || !(data_insn.sec_hdr.sh_type == SHT_PROGBITS && (data_insn.sec_hdr.sh_flags & SHF_EXECINSTR) && !strcmp(data_insn.sec_name, section))) continue; if (sseen) *sseen = true; memset(prog, 0, sizeof(*prog)); prog->type = ctx->type; prog->license = ctx->license; prog->size = data_insn.sec_data->d_size; prog->insns_num = prog->size / sizeof(struct bpf_insn); prog->insns = malloc(prog->size); if (!prog->insns) { *lderr = true; return -ENOMEM; } memcpy(prog->insns, data_insn.sec_data->d_buf, prog->size); ret = bpf_apply_relo_data(ctx, &data_relo, prog, &props); if (ret < 0) { *lderr = true; if (ctx->sec_text != idx) free(prog->insns); return ret; } if (ctx->sec_text == idx) { fd = 0; goto out; } fd = bpf_prog_attach(section, prog, ctx); free(prog->insns); if (fd < 0) { *lderr = true; if (props.tc.total) { if (ctx->cfg.jit_enabled && props.tc.total != props.tc.jited) fprintf(stderr, "JIT enabled, but only %u/%u tail call maps in the program have JITed owner!\n", props.tc.jited, props.tc.total); if (!ctx->cfg.jit_enabled && props.tc.jited) fprintf(stderr, "JIT disabled, but %u/%u tail call maps in the program have JITed owner!\n", props.tc.jited, props.tc.total); } return fd; } out: ctx->sec_done[i] = true; ctx->sec_done[idx] = true; break; } return fd; } static int bpf_fetch_prog_sec(struct bpf_elf_ctx *ctx, const char *section) { bool lderr = false, sseen = false; struct bpf_elf_prog prog; int ret = -1; if (bpf_has_call_data(ctx)) { ret = bpf_fetch_prog_relo(ctx, ".text", &lderr, NULL, &ctx->prog_text); if (ret < 0) return ret; } if (bpf_has_map_data(ctx) || bpf_has_call_data(ctx)) ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen, &prog); if (ret < 0 && !lderr) ret = bpf_fetch_prog(ctx, section, &sseen); if (ret < 0 && !sseen) fprintf(stderr, "Program section \'%s\' not found in ELF file!\n", section); return ret; } static int bpf_find_map_by_id(struct bpf_elf_ctx *ctx, uint32_t id) { int i; for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++) if (ctx->map_fds[i] && ctx->maps[i].id == id && ctx->maps[i].type == BPF_MAP_TYPE_PROG_ARRAY) return i; return -1; } struct bpf_jited_aux { int prog_fd; int map_fd; struct bpf_prog_data prog; struct bpf_map_ext map; }; static int bpf_derive_prog_from_fdinfo(int fd, struct bpf_prog_data *prog) { char *file = NULL; char buff[4096]; unsigned int val; FILE *fp; int ret; ret = asprintf(&file, "/proc/%d/fdinfo/%d", getpid(), fd); if (ret < 0) { fprintf(stderr, "asprintf failed: %s\n", strerror(errno)); free(file); return ret; } memset(prog, 0, sizeof(*prog)); fp = fopen(file, "r"); free(file); if (!fp) { fprintf(stderr, "No procfs support?!\n"); return -EIO; } while (fgets(buff, sizeof(buff), fp)) { if (sscanf(buff, "prog_type:\t%u", &val) == 1) prog->type = val; else if (sscanf(buff, "prog_jited:\t%u", &val) == 1) prog->jited = val; } fclose(fp); return 0; } static int bpf_tail_call_get_aux(struct bpf_jited_aux *aux) { struct bpf_elf_map tmp; int ret; ret = bpf_derive_elf_map_from_fdinfo(aux->map_fd, &tmp, &aux->map); if (!ret) ret = bpf_derive_prog_from_fdinfo(aux->prog_fd, &aux->prog); return ret; } static int bpf_fill_prog_arrays(struct bpf_elf_ctx *ctx) { struct bpf_elf_sec_data data; uint32_t map_id, key_id; int fd, i, ret, idx; for (i = 1; i < ctx->elf_hdr.e_shnum; i++) { if (ctx->sec_done[i]) continue; ret = bpf_fill_section_data(ctx, i, &data); if (ret < 0) continue; ret = sscanf(data.sec_name, "%i/%i", &map_id, &key_id); if (ret != 2) continue; idx = bpf_find_map_by_id(ctx, map_id); if (idx < 0) continue; fd = bpf_fetch_prog_sec(ctx, data.sec_name); if (fd < 0) return -EIO; ret = bpf_map_update(ctx->map_fds[idx], &key_id, &fd, BPF_ANY); if (ret < 0) { struct bpf_jited_aux aux = {}; ret = -errno; if (errno == E2BIG) { fprintf(stderr, "Tail call key %u for map %u out of bounds?\n", key_id, map_id); return ret; } aux.map_fd = ctx->map_fds[idx]; aux.prog_fd = fd; if (bpf_tail_call_get_aux(&aux)) return ret; if (!aux.map.owner.type) return ret; if (aux.prog.type != aux.map.owner.type) fprintf(stderr, "Tail call map owned by prog type %u, but prog type is %u!\n", aux.map.owner.type, aux.prog.type); if (aux.prog.jited != aux.map.owner.jited) fprintf(stderr, "Tail call map %s jited, but prog %s!\n", aux.map.owner.jited ? "is" : "not", aux.prog.jited ? "is" : "not"); return ret; } ctx->sec_done[i] = true; } return 0; } static void bpf_save_finfo(struct bpf_elf_ctx *ctx) { struct stat st; int ret; memset(&ctx->stat, 0, sizeof(ctx->stat)); ret = fstat(ctx->obj_fd, &st); if (ret < 0) { fprintf(stderr, "Stat of elf file failed: %s\n", strerror(errno)); return; } ctx->stat.st_dev = st.st_dev; ctx->stat.st_ino = st.st_ino; } static int bpf_read_pin_mapping(FILE *fp, uint32_t *id, char *path) { char buff[PATH_MAX]; while (fgets(buff, sizeof(buff), fp)) { char *ptr = buff; while (*ptr == ' ' || *ptr == '\t') ptr++; if (*ptr == '#' || *ptr == '\n' || *ptr == 0) continue; if (sscanf(ptr, "%i %s\n", id, path) != 2 && sscanf(ptr, "%i %s #", id, path) != 2) { strcpy(path, ptr); return -1; } return 1; } return 0; } static bool bpf_pinning_reserved(uint32_t pinning) { switch (pinning) { case PIN_NONE: case PIN_OBJECT_NS: case PIN_GLOBAL_NS: return true; default: return false; } } static void bpf_hash_init(struct bpf_elf_ctx *ctx, const char *db_file) { struct bpf_hash_entry *entry; char subpath[PATH_MAX] = {}; uint32_t pinning; FILE *fp; int ret; fp = fopen(db_file, "r"); if (!fp) return; while ((ret = bpf_read_pin_mapping(fp, &pinning, subpath))) { if (ret == -1) { fprintf(stderr, "Database %s is corrupted at: %s\n", db_file, subpath); fclose(fp); return; } if (bpf_pinning_reserved(pinning)) { fprintf(stderr, "Database %s, id %u is reserved - ignoring!\n", db_file, pinning); continue; } entry = malloc(sizeof(*entry)); if (!entry) { fprintf(stderr, "No memory left for db entry!\n"); continue; } entry->pinning = pinning; entry->subpath = strdup(subpath); if (!entry->subpath) { fprintf(stderr, "No memory left for db entry!\n"); free(entry); continue; } entry->next = ctx->ht[pinning & (ARRAY_SIZE(ctx->ht) - 1)]; ctx->ht[pinning & (ARRAY_SIZE(ctx->ht) - 1)] = entry; } fclose(fp); } static void bpf_hash_destroy(struct bpf_elf_ctx *ctx) { struct bpf_hash_entry *entry; int i; for (i = 0; i < ARRAY_SIZE(ctx->ht); i++) { while ((entry = ctx->ht[i]) != NULL) { ctx->ht[i] = entry->next; free((char *)entry->subpath); free(entry); } } } static int bpf_elf_check_ehdr(const struct bpf_elf_ctx *ctx) { if (ctx->elf_hdr.e_type != ET_REL || (ctx->elf_hdr.e_machine != EM_NONE && ctx->elf_hdr.e_machine != EM_BPF) || ctx->elf_hdr.e_version != EV_CURRENT) { fprintf(stderr, "ELF format error, ELF file not for eBPF?\n"); return -EINVAL; } switch (ctx->elf_hdr.e_ident[EI_DATA]) { default: fprintf(stderr, "ELF format error, wrong endianness info?\n"); return -EINVAL; case ELFDATA2LSB: if (htons(1) == 1) { fprintf(stderr, "We are big endian, eBPF object is little endian!\n"); return -EIO; } break; case ELFDATA2MSB: if (htons(1) != 1) { fprintf(stderr, "We are little endian, eBPF object is big endian!\n"); return -EIO; } break; } return 0; } static void bpf_get_cfg(struct bpf_elf_ctx *ctx) { static const char *path_jit = "/proc/sys/net/core/bpf_jit_enable"; int fd; fd = open(path_jit, O_RDONLY); if (fd > 0) { char tmp[16] = {}; if (read(fd, tmp, sizeof(tmp)) > 0) ctx->cfg.jit_enabled = atoi(tmp); close(fd); } } static int bpf_elf_ctx_init(struct bpf_elf_ctx *ctx, const char *pathname, enum bpf_prog_type type, __u32 ifindex, bool verbose) { uint8_t tmp[20]; int ret; if (elf_version(EV_CURRENT) == EV_NONE) return -EINVAL; bpf_init_env(); memset(ctx, 0, sizeof(*ctx)); bpf_get_cfg(ctx); ret = bpf_obj_hash(pathname, tmp, sizeof(tmp)); if (ret) ctx->noafalg = true; else hexstring_n2a(tmp, sizeof(tmp), ctx->obj_uid, sizeof(ctx->obj_uid)); ctx->verbose = verbose; ctx->type = type; ctx->ifindex = ifindex; ctx->obj_fd = open(pathname, O_RDONLY); if (ctx->obj_fd < 0) return ctx->obj_fd; ctx->elf_fd = elf_begin(ctx->obj_fd, ELF_C_READ, NULL); if (!ctx->elf_fd) { ret = -EINVAL; goto out_fd; } if (elf_kind(ctx->elf_fd) != ELF_K_ELF) { ret = -EINVAL; goto out_fd; } if (gelf_getehdr(ctx->elf_fd, &ctx->elf_hdr) != &ctx->elf_hdr) { ret = -EIO; goto out_elf; } ret = bpf_elf_check_ehdr(ctx); if (ret < 0) goto out_elf; ctx->sec_done = calloc(ctx->elf_hdr.e_shnum, sizeof(*(ctx->sec_done))); if (!ctx->sec_done) { ret = -ENOMEM; goto out_elf; } if (ctx->verbose && bpf_log_realloc(ctx)) { ret = -ENOMEM; goto out_free; } bpf_save_finfo(ctx); bpf_hash_init(ctx, CONFDIR "/bpf_pinning"); return 0; out_free: free(ctx->sec_done); out_elf: elf_end(ctx->elf_fd); out_fd: close(ctx->obj_fd); return ret; } static int bpf_maps_count(struct bpf_elf_ctx *ctx) { int i, count = 0; for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++) { if (!ctx->map_fds[i]) break; count++; } return count; } static void bpf_maps_teardown(struct bpf_elf_ctx *ctx) { int i; for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++) { if (ctx->map_fds[i]) close(ctx->map_fds[i]); } if (ctx->btf_fd) close(ctx->btf_fd); free(ctx->btf.types); } static void bpf_elf_ctx_destroy(struct bpf_elf_ctx *ctx, bool failure) { if (failure) bpf_maps_teardown(ctx); bpf_hash_destroy(ctx); free(ctx->prog_text.insns); free(ctx->sec_done); free(ctx->log); elf_end(ctx->elf_fd); close(ctx->obj_fd); } static struct bpf_elf_ctx __ctx; static int bpf_obj_open(const char *pathname, enum bpf_prog_type type, const char *section, __u32 ifindex, bool verbose) { struct bpf_elf_ctx *ctx = &__ctx; int fd = 0, ret; ret = bpf_elf_ctx_init(ctx, pathname, type, ifindex, verbose); if (ret < 0) { fprintf(stderr, "Cannot initialize ELF context!\n"); return ret; } ret = bpf_fetch_ancillary(ctx, strcmp(section, ".text")); if (ret < 0) { fprintf(stderr, "Error fetching ELF ancillary data!\n"); goto out; } fd = bpf_fetch_prog_sec(ctx, section); if (fd < 0) { fprintf(stderr, "Error fetching program/map!\n"); ret = fd; goto out; } ret = bpf_fill_prog_arrays(ctx); if (ret < 0) fprintf(stderr, "Error filling program arrays!\n"); out: bpf_elf_ctx_destroy(ctx, ret < 0); if (ret < 0) { if (fd) close(fd); return ret; } return fd; } static int bpf_map_set_send(int fd, struct sockaddr_un *addr, unsigned int addr_len, const struct bpf_map_data *aux, unsigned int entries) { struct bpf_map_set_msg msg = { .aux.uds_ver = BPF_SCM_AUX_VER, .aux.num_ent = entries, }; int *cmsg_buf, min_fd; char *amsg_buf; int i; strlcpy(msg.aux.obj_name, aux->obj, sizeof(msg.aux.obj_name)); memcpy(&msg.aux.obj_st, aux->st, sizeof(msg.aux.obj_st)); cmsg_buf = bpf_map_set_init(&msg, addr, addr_len); amsg_buf = (char *)msg.aux.ent; for (i = 0; i < entries; i += min_fd) { int ret; min_fd = min(BPF_SCM_MAX_FDS * 1U, entries - i); bpf_map_set_init_single(&msg, min_fd); memcpy(cmsg_buf, &aux->fds[i], sizeof(aux->fds[0]) * min_fd); memcpy(amsg_buf, &aux->ent[i], sizeof(aux->ent[0]) * min_fd); ret = sendmsg(fd, &msg.hdr, 0); if (ret <= 0) return ret ? : -1; } return 0; } static int bpf_map_set_recv(int fd, int *fds, struct bpf_map_aux *aux, unsigned int entries) { struct bpf_map_set_msg msg; int *cmsg_buf, min_fd; char *amsg_buf, *mmsg_buf; unsigned int needed = 1; int i; cmsg_buf = bpf_map_set_init(&msg, NULL, 0); amsg_buf = (char *)msg.aux.ent; mmsg_buf = (char *)&msg.aux; for (i = 0; i < min(entries, needed); i += min_fd) { struct cmsghdr *cmsg; int ret; min_fd = min(entries, entries - i); bpf_map_set_init_single(&msg, min_fd); ret = recvmsg(fd, &msg.hdr, 0); if (ret <= 0) return ret ? : -1; cmsg = CMSG_FIRSTHDR(&msg.hdr); if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) return -EINVAL; if (msg.hdr.msg_flags & MSG_CTRUNC) return -EIO; if (msg.aux.uds_ver != BPF_SCM_AUX_VER) return -ENOSYS; min_fd = (cmsg->cmsg_len - sizeof(*cmsg)) / sizeof(fd); if (min_fd > entries || min_fd <= 0) return -EINVAL; memcpy(&fds[i], cmsg_buf, sizeof(fds[0]) * min_fd); memcpy(&aux->ent[i], amsg_buf, sizeof(aux->ent[0]) * min_fd); memcpy(aux, mmsg_buf, offsetof(struct bpf_map_aux, ent)); needed = aux->num_ent; } return 0; } int bpf_send_map_fds(const char *path, const char *obj) { struct bpf_elf_ctx *ctx = &__ctx; struct sockaddr_un addr = { .sun_family = AF_UNIX }; struct bpf_map_data bpf_aux = { .fds = ctx->map_fds, .ent = ctx->maps, .st = &ctx->stat, .obj = obj, }; int fd, ret; fd = socket(AF_UNIX, SOCK_DGRAM, 0); if (fd < 0) { fprintf(stderr, "Cannot open socket: %s\n", strerror(errno)); return -1; } strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) { fprintf(stderr, "Cannot connect to %s: %s\n", path, strerror(errno)); return -1; } ret = bpf_map_set_send(fd, &addr, sizeof(addr), &bpf_aux, bpf_maps_count(ctx)); if (ret < 0) fprintf(stderr, "Cannot send fds to %s: %s\n", path, strerror(errno)); bpf_maps_teardown(ctx); close(fd); return ret; } int bpf_recv_map_fds(const char *path, int *fds, struct bpf_map_aux *aux, unsigned int entries) { struct sockaddr_un addr = { .sun_family = AF_UNIX }; int fd, ret; fd = socket(AF_UNIX, SOCK_DGRAM, 0); if (fd < 0) { fprintf(stderr, "Cannot open socket: %s\n", strerror(errno)); return -1; } strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) { fprintf(stderr, "Cannot bind to socket: %s\n", strerror(errno)); return -1; } ret = bpf_map_set_recv(fd, fds, aux, entries); if (ret < 0) fprintf(stderr, "Cannot recv fds from %s: %s\n", path, strerror(errno)); unlink(addr.sun_path); close(fd); return ret; } #endif /* HAVE_ELF */