[PATCH 3/9] bpf: Add bpfwl tool to construct bpf whitelists

Jiri Olsa
2020-05-06T13:30:12Z

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@kernel.org>
---
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> ", 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