This tool takes vmlinux object and whitelist directory on input and produces C source object with BPF whitelist data. The vmlinux object needs to have a BTF information compiled in. The whitelist directory is expected to contain files with helper names, where each file contains list of functions/probes that helper is allowed to be called from - whitelist. The bpfwl tool has following output: $ bpfwl vmlinux dir unsigned long d_path[] __attribute__((section(".BTF_whitelist_d_path"))) = \ { 24507, 24511, 24537, 24539, 24545, 24588, 24602, 24920 }; Each array are sorted BTF ids of the functions provided in the helper file. Each array will be compiled into kernel and used during the helper check in verifier. Signed-off-by: Jiri Olsa <jolsa@xxxxxxxxxx> --- tools/bpf/bpfwl/Build | 11 ++ tools/bpf/bpfwl/Makefile | 60 +++++++++ tools/bpf/bpfwl/bpfwl.c | 285 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 tools/bpf/bpfwl/Build create mode 100644 tools/bpf/bpfwl/Makefile create mode 100644 tools/bpf/bpfwl/bpfwl.c diff --git a/tools/bpf/bpfwl/Build b/tools/bpf/bpfwl/Build new file mode 100644 index 000000000000..667e30d6ce79 --- /dev/null +++ b/tools/bpf/bpfwl/Build @@ -0,0 +1,11 @@ +bpfwl-y += bpfwl.o +bpfwl-y += rbtree.o +bpfwl-y += zalloc.o + +$(OUTPUT)rbtree.o: ../../lib/rbtree.c FORCE + $(call rule_mkdir) + $(call if_changed_dep,cc_o_c) + +$(OUTPUT)zalloc.o: ../../lib/zalloc.c FORCE + $(call rule_mkdir) + $(call if_changed_dep,cc_o_c) diff --git a/tools/bpf/bpfwl/Makefile b/tools/bpf/bpfwl/Makefile new file mode 100644 index 000000000000..8174eeb7eea6 --- /dev/null +++ b/tools/bpf/bpfwl/Makefile @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0-only +include ../../scripts/Makefile.include + +MAKEFLAGS=--no-print-directory + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +ifeq ($(V),1) + Q = +else + Q = @ +endif + +BPF_DIR = $(srctree)/tools/lib/bpf/ + +ifneq ($(OUTPUT),) + LIBBPF_PATH = $(OUTPUT)/libbpf/ +else + LIBBPF_PATH = $(BPF_DIR) +endif + +LIBBPF = $(LIBBPF_PATH)libbpf.a +BPFWL = $(OUTPUT)bpfwl +BPFWL_IN = $(BPFWL)-in.o + +all: $(OUTPUT)bpfwl + +$(LIBBPF): FORCE + $(if $(LIBBPF_PATH),@mkdir -p $(LIBBPF_PATH)) + $(Q)$(MAKE) -C $(BPF_DIR) OUTPUT=$(LIBBPF_PATH) $(LIBBPF_PATH)libbpf.a + +$(LIBBPF)-clean: + $(call QUIET_CLEAN, libbpf) + $(Q)$(MAKE) -C $(BPF_DIR) OUTPUT=$(LIBBPF_PATH) clean >/dev/null + +CFLAGS := -g -I$(srctree)/tools/include -I$(BPF_DIR) + +LIBS = -lelf -lz + +export srctree OUTPUT CFLAGS +include $(srctree)/tools/build/Makefile.include + +$(BPFWL_IN): fixdep FORCE + $(Q)$(MAKE) $(build)=bpfwl + +$(BPFWL): $(LIBBPF) $(BPFWL_IN) + $(QUIET_LINK)$(CC) $(BPFWL_IN) $(LDFLAGS) -o $@ $(LIBBPF) $(LIBS) + +clean: $(LIBBPF)-clean + $(call QUIET_CLEAN, bpfwl) + $(Q)$(RM) -f $(BPFWL) + $(Q)find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete + +FORCE: + +.PHONY: all FORCE clean diff --git a/tools/bpf/bpfwl/bpfwl.c b/tools/bpf/bpfwl/bpfwl.c new file mode 100644 index 000000000000..495c2bcf620a --- /dev/null +++ b/tools/bpf/bpfwl/bpfwl.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <dirent.h> +#include <stdlib.h> +#include <linux/rbtree.h> +#include <linux/list.h> +#include <linux/kernel.h> +#include <linux/zalloc.h> +#include <linux/limits.h> +#include <btf.h> +#include <libbpf.h> + +struct func { + char *name; + unsigned long id; + struct rb_node rb_node; + struct list_head list[]; +}; + +struct helper { + char *name; + int idx; + int count; + struct list_head node; + struct list_head funcs; +}; + +static struct rb_root funcs; +static LIST_HEAD(helpers); +static int idxs; + +static struct func *func__new(const char *name) +{ + size_t size = idxs * sizeof(struct list_head); + struct func *func; + int i; + + func = zalloc(sizeof(*func) + size); + if (func) { + func->name = strdup(name); + for (i = 0; i < idxs; i++) + INIT_LIST_HEAD(&func->list[i]); + } + return func; +} + +static struct helper *helper__new(const char *name) +{ + struct helper *helper = zalloc(sizeof(*helper)); + + if (helper) { + helper->idx = idxs++; + helper->name = strdup(name); + INIT_LIST_HEAD(&helper->funcs); + list_add_tail(&helper->node, &helpers); + } + return helper; +} + +static struct func *func__add(char *name) +{ + struct rb_node **p = &funcs.rb_node; + struct rb_node *parent = NULL; + struct func *func; + int cmp; + + while (*p != NULL) { + parent = *p; + func = rb_entry(parent, struct func, rb_node); + cmp = strcmp(func->name, name); + if (cmp < 0) + p = &(*p)->rb_left; + else if (cmp > 0) + p = &(*p)->rb_right; + else + return func; + } + + func = func__new(name); + if (func) { + rb_link_node(&func->rb_node, parent, p); + rb_insert_color(&func->rb_node, &funcs); + } + return func; +} + +static struct func *func__find(const char *name) +{ + struct rb_node *p = funcs.rb_node; + struct func *func; + int cmp; + + while (p != NULL) { + func = rb_entry(p, struct func, rb_node); + cmp = strcmp(func->name, name); + if (cmp < 0) + p = p->rb_left; + else if (cmp > 0) + p = p->rb_right; + else + return func; + } + return NULL; +} + +static int helper__read(struct helper *helper, const char *base) +{ + char path[PATH_MAX]; + char *line = NULL; + size_t len = 0; + ssize_t nread; + int err = -1; + FILE *file; + + snprintf(path, sizeof(path), "%s/%s", base, helper->name); + + file = fopen(path, "r"); + if (file == NULL) { + perror("FAILED fopen whitelist"); + return -1; + } + + while ((nread = getline(&line, &len, file)) != -1) { + struct func *func; + char *p; + + p = strchr(line, '\n'); + if (p) + *p = 0; + + func = func__add(line); + if (!func) + goto err; + + list_add_tail(&func->list[helper->idx], &helper->funcs); + helper->count++; + } + + err = 0; +err: + free(line); + fclose(file); + return err; +} + +static int comp(const void *a, const void *b) +{ + const unsigned long *pa = a; + const unsigned long *pb = b; + + return *pa - *pb; +} + +int main(int argc, char **argv) +{ + struct helper *helper; + const char *wl_path; + const char *vmlinux; + struct dirent *d; + struct btf *btf; + int id, err; + DIR *dir; + __u32 nr; + + if (argc != 3) { + fprintf(stderr, "%s <vmlinux> <dir>", argv[0]); + return -1; + } + + vmlinux = argv[1]; + wl_path = argv[2]; + + dir = opendir(wl_path); + if (dir == NULL) { + perror("FAILED: open directory"); + return -1; + } + + /* + * Scan the whitelist directory and create 'struct helper' + * object for every file. Read and put all the functions + * into funcs rb tree and link them to each helper. + */ + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || + !strcmp(d->d_name, "..")) + continue; + + helper = helper__new(d->d_name); + if (!helper) { + fprintf(stderr, "FAILED: not enough memory\n"); + return -1; + } + + if (helper__read(helper, wl_path)) + return -1; + } + + closedir(dir); + + btf = btf__parse_elf(vmlinux, NULL); + err = libbpf_get_error(btf); + if (err) { + fprintf(stderr, "FAILED: load BTF from %s: %s", + vmlinux, strerror(err)); + return -1; + } + + nr = btf__get_nr_types(btf); + + /* Iterate all the BTF types and resolve all the function IDs. */ + for (id = 0; id < nr; id++) { + const struct btf_type *type; + struct func *func; + const char *str; + + type = btf__type_by_id(btf, id); + if (!type) + continue; + + if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) + continue; + + str = btf__name_by_offset(btf, type->name_off); + if (!str) + continue; + + func = func__find(str); + if (func) + func->id = id; + } + + /* + * Load BTF IDs for each helper into array, sort it, + * and dump the C code for the helper array. + */ + list_for_each_entry(helper, &helpers, node) { + unsigned long *ids; + bool first = true; + struct func *func; + int idx = 0; + + ids = malloc(helper->count * sizeof(unsigned long)); + if (!ids) { + fprintf(stderr, "FAILED: not enough memory\n"); + return -1; + } + + list_for_each_entry(func, &helper->funcs, list[helper->idx]) { + if (!func->id) { + fprintf(stderr, "FAILED: '%s' function not found in BTF data\n", + func->name); + return -1; + } + + ids[idx++] = func->id; + } + + qsort(ids, helper->count, sizeof(unsigned long), comp); + + fprintf(stdout, + "unsigned long %s[] __attribute__((section(\".BTF_whitelist_%s\"))) = { ", + helper->name, helper->name); + + for (idx = 0; idx < helper->count; idx++, first = false) + fprintf(stdout, "%s%lu", first ? "" : ", ", ids[idx]); + + fprintf(stdout, " };\n"); + + free(ids); + } + + /* + * Not releasing anything intentionaly.. + * + * btf__free(btf) + * free helpers/funcs + */ + return 0; +} -- 2.25.4