libbpf: Add support for extracting kernel symbol addresses
authorAndrii Nakryiko <andriin@fb.com>
Fri, 19 Jun 2020 23:16:56 +0000 (16:16 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 23 Jun 2020 00:01:48 +0000 (17:01 -0700)
Add support for another (in addition to existing Kconfig) special kind of
externs in BPF code, kernel symbol externs. Such externs allow BPF code to
"know" kernel symbol address and either use it for comparisons with kernel
data structures (e.g., struct file's f_op pointer, to distinguish different
kinds of file), or, with the help of bpf_probe_user_kernel(), to follow
pointers and read data from global variables. Kernel symbol addresses are
found through /proc/kallsyms, which should be present in the system.

Currently, such kernel symbol variables are typeless: they have to be defined
as `extern const void <symbol>` and the only operation you can do (in C code)
with them is to take its address. Such extern should reside in a special
section '.ksyms'. bpf_helpers.h header provides __ksym macro for this. Strong
vs weak semantics stays the same as with Kconfig externs. If symbol is not
found in /proc/kallsyms, this will be a failure for strong (non-weak) extern,
but will be defaulted to 0 for weak externs.

If the same symbol is defined multiple times in /proc/kallsyms, then it will
be error if any of the associated addresses differs. In that case, address is
ambiguous, so libbpf falls on the side of caution, rather than confusing user
with randomly chosen address.

In the future, once kernel is extended with variables BTF information, such
ksym externs will be supported in a typed version, which will allow BPF
program to read variable's contents directly, similarly to how it's done for
fentry/fexit input arguments.

Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Hao Luo <haoluo@google.com>
Link: https://lore.kernel.org/bpf/20200619231703.738941-3-andriin@fb.com
tools/lib/bpf/bpf_helpers.h
tools/lib/bpf/btf.h
tools/lib/bpf/libbpf.c

index f67dce2af80224a98141917c74581b0298400197..a510d8ed716fb2e534a7522f03c558878b0aa120 100644 (file)
@@ -75,5 +75,6 @@ enum libbpf_tristate {
 };
 
 #define __kconfig __attribute__((section(".kconfig")))
+#define __ksym __attribute__((section(".ksyms")))
 
 #endif
index 70c1b7ec2bd0352d6a0193bae0a694403c31d62a..06cd1731c1547b228706a5d5ca4b382e7121c7e9 100644 (file)
@@ -168,6 +168,11 @@ static inline bool btf_kflag(const struct btf_type *t)
        return BTF_INFO_KFLAG(t->info);
 }
 
+static inline bool btf_is_void(const struct btf_type *t)
+{
+       return btf_kind(t) == BTF_KIND_UNKN;
+}
+
 static inline bool btf_is_int(const struct btf_type *t)
 {
        return btf_kind(t) == BTF_KIND_INT;
index ffccb5af32a55563cbb06c31ae120e32147560ff..18461deb1b193a2998f7749064d22e1e3e4fe7c8 100644 (file)
@@ -285,6 +285,7 @@ struct bpf_struct_ops {
 #define BSS_SEC ".bss"
 #define RODATA_SEC ".rodata"
 #define KCONFIG_SEC ".kconfig"
+#define KSYMS_SEC ".ksyms"
 #define STRUCT_OPS_SEC ".struct_ops"
 
 enum libbpf_map_type {
@@ -331,6 +332,7 @@ struct bpf_map {
 enum extern_type {
        EXT_UNKNOWN,
        EXT_KCFG,
+       EXT_KSYM,
 };
 
 enum kcfg_type {
@@ -358,6 +360,9 @@ struct extern_desc {
                        int data_off;
                        bool is_signed;
                } kcfg;
+               struct {
+                       unsigned long long addr;
+               } ksym;
        };
 };
 
@@ -2817,9 +2822,25 @@ static int cmp_externs(const void *_a, const void *_b)
        return strcmp(a->name, b->name);
 }
 
+static int find_int_btf_id(const struct btf *btf)
+{
+       const struct btf_type *t;
+       int i, n;
+
+       n = btf__get_nr_types(btf);
+       for (i = 1; i <= n; i++) {
+               t = btf__type_by_id(btf, i);
+
+               if (btf_is_int(t) && btf_int_bits(t) == 32)
+                       return i;
+       }
+
+       return 0;
+}
+
 static int bpf_object__collect_externs(struct bpf_object *obj)
 {
-       struct btf_type *sec, *kcfg_sec = NULL;
+       struct btf_type *sec, *kcfg_sec = NULL, *ksym_sec = NULL;
        const struct btf_type *t;
        struct extern_desc *ext;
        int i, n, off;
@@ -2900,6 +2921,17 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
                                pr_warn("extern (kcfg) '%s' type is unsupported\n", ext_name);
                                return -ENOTSUP;
                        }
+               } else if (strcmp(sec_name, KSYMS_SEC) == 0) {
+                       const struct btf_type *vt;
+
+                       ksym_sec = sec;
+                       ext->type = EXT_KSYM;
+
+                       vt = skip_mods_and_typedefs(obj->btf, t->type, NULL);
+                       if (!btf_is_void(vt)) {
+                               pr_warn("extern (ksym) '%s' is not typeless (void)\n", ext_name);
+                               return -ENOTSUP;
+                       }
                } else {
                        pr_warn("unrecognized extern section '%s'\n", sec_name);
                        return -ENOTSUP;
@@ -2913,6 +2945,46 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
        /* sort externs by type, for kcfg ones also by (align, size, name) */
        qsort(obj->externs, obj->nr_extern, sizeof(*ext), cmp_externs);
 
+       /* for .ksyms section, we need to turn all externs into allocated
+        * variables in BTF to pass kernel verification; we do this by
+        * pretending that each extern is a 8-byte variable
+        */
+       if (ksym_sec) {
+               /* find existing 4-byte integer type in BTF to use for fake
+                * extern variables in DATASEC
+                */
+               int int_btf_id = find_int_btf_id(obj->btf);
+
+               for (i = 0; i < obj->nr_extern; i++) {
+                       ext = &obj->externs[i];
+                       if (ext->type != EXT_KSYM)
+                               continue;
+                       pr_debug("extern (ksym) #%d: symbol %d, name %s\n",
+                                i, ext->sym_idx, ext->name);
+               }
+
+               sec = ksym_sec;
+               n = btf_vlen(sec);
+               for (i = 0, off = 0; i < n; i++, off += sizeof(int)) {
+                       struct btf_var_secinfo *vs = btf_var_secinfos(sec) + i;
+                       struct btf_type *vt;
+
+                       vt = (void *)btf__type_by_id(obj->btf, vs->type);
+                       ext_name = btf__name_by_offset(obj->btf, vt->name_off);
+                       ext = find_extern_by_name(obj, ext_name);
+                       if (!ext) {
+                               pr_warn("failed to find extern definition for BTF var '%s'\n",
+                                       ext_name);
+                               return -ESRCH;
+                       }
+                       btf_var(vt)->linkage = BTF_VAR_GLOBAL_ALLOCATED;
+                       vt->type = int_btf_id;
+                       vs->offset = off;
+                       vs->size = sizeof(int);
+               }
+               sec->size = off;
+       }
+
        if (kcfg_sec) {
                sec = kcfg_sec;
                /* for kcfg externs calculate their offsets within a .kconfig map */
@@ -2924,7 +2996,7 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
 
                        ext->kcfg.data_off = roundup(off, ext->kcfg.align);
                        off = ext->kcfg.data_off + ext->kcfg.sz;
-                       pr_debug("extern #%d (kcfg): symbol %d, off %u, name %s\n",
+                       pr_debug("extern (kcfg) #%d: symbol %d, off %u, name %s\n",
                                 i, ext->sym_idx, ext->kcfg.data_off, ext->name);
                }
                sec->size = off;
@@ -5022,9 +5094,14 @@ bpf_program__relocate(struct bpf_program *prog, struct bpf_object *obj)
                        break;
                case RELO_EXTERN:
                        ext = &obj->externs[relo->sym_off];
-                       insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
-                       insn[0].imm = obj->maps[obj->kconfig_map_idx].fd;
-                       insn[1].imm = ext->kcfg.data_off;
+                       if (ext->type == EXT_KCFG) {
+                               insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
+                               insn[0].imm = obj->maps[obj->kconfig_map_idx].fd;
+                               insn[1].imm = ext->kcfg.data_off;
+                       } else /* EXT_KSYM */ {
+                               insn[0].imm = (__u32)ext->ksym.addr;
+                               insn[1].imm = ext->ksym.addr >> 32;
+                       }
                        break;
                case RELO_CALL:
                        err = bpf_program__reloc_text(prog, obj, relo);
@@ -5643,10 +5720,58 @@ static int bpf_object__sanitize_maps(struct bpf_object *obj)
        return 0;
 }
 
+static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
+{
+       char sym_type, sym_name[500];
+       unsigned long long sym_addr;
+       struct extern_desc *ext;
+       int ret, err = 0;
+       FILE *f;
+
+       f = fopen("/proc/kallsyms", "r");
+       if (!f) {
+               err = -errno;
+               pr_warn("failed to open /proc/kallsyms: %d\n", err);
+               return err;
+       }
+
+       while (true) {
+               ret = fscanf(f, "%llx %c %499s%*[^\n]\n",
+                            &sym_addr, &sym_type, sym_name);
+               if (ret == EOF && feof(f))
+                       break;
+               if (ret != 3) {
+                       pr_warn("failed to read kallasyms entry: %d\n", ret);
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               ext = find_extern_by_name(obj, sym_name);
+               if (!ext || ext->type != EXT_KSYM)
+                       continue;
+
+               if (ext->is_set && ext->ksym.addr != sym_addr) {
+                       pr_warn("extern (ksym) '%s' resolution is ambiguous: 0x%llx or 0x%llx\n",
+                               sym_name, ext->ksym.addr, sym_addr);
+                       err = -EINVAL;
+                       goto out;
+               }
+               if (!ext->is_set) {
+                       ext->is_set = true;
+                       ext->ksym.addr = sym_addr;
+                       pr_debug("extern (ksym) %s=0x%llx\n", sym_name, sym_addr);
+               }
+       }
+
+out:
+       fclose(f);
+       return err;
+}
+
 static int bpf_object__resolve_externs(struct bpf_object *obj,
                                       const char *extra_kconfig)
 {
-       bool need_config = false;
+       bool need_config = false, need_kallsyms = false;
        struct extern_desc *ext;
        void *kcfg_data = NULL;
        int err, i;
@@ -5676,6 +5801,8 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
                } else if (ext->type == EXT_KCFG &&
                           strncmp(ext->name, "CONFIG_", 7) == 0) {
                        need_config = true;
+               } else if (ext->type == EXT_KSYM) {
+                       need_kallsyms = true;
                } else {
                        pr_warn("unrecognized extern '%s'\n", ext->name);
                        return -EINVAL;
@@ -5699,6 +5826,11 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
                if (err)
                        return -EINVAL;
        }
+       if (need_kallsyms) {
+               err = bpf_object__read_kallsyms_file(obj);
+               if (err)
+                       return -EINVAL;
+       }
        for (i = 0; i < obj->nr_extern; i++) {
                ext = &obj->externs[i];