bpf, libbpf: Add type match support
authorDaniel Müller <deso@posteo.net>
Tue, 28 Jun 2022 16:01:21 +0000 (16:01 +0000)
committerAndrii Nakryiko <andrii@kernel.org>
Wed, 6 Jul 2022 04:14:25 +0000 (21:14 -0700)
This patch adds support for the proposed type match relation to
relo_core where it is shared between userspace and kernel. It plumbs
through both kernel-side and libbpf-side support.

The matching relation is defined as follows (copy from source):
- modifiers and typedefs are stripped (and, hence, effectively ignored)
- generally speaking types need to be of same kind (struct vs. struct, union
  vs. union, etc.)
  - exceptions are struct/union behind a pointer which could also match a
    forward declaration of a struct or union, respectively, and enum vs.
    enum64 (see below)
Then, depending on type:
- integers:
  - match if size and signedness match
- arrays & pointers:
  - target types are recursively matched
- structs & unions:
  - local members need to exist in target with the same name
  - for each member we recursively check match unless it is already behind a
    pointer, in which case we only check matching names and compatible kind
- enums:
  - local variants have to have a match in target by symbolic name (but not
    numeric value)
  - size has to match (but enum may match enum64 and vice versa)
- function pointers:
  - number and position of arguments in local type has to match target
  - for each argument and the return value we recursively check match

Signed-off-by: Daniel Müller <deso@posteo.net>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20220628160127.607834-5-deso@posteo.net
kernel/bpf/btf.c
tools/lib/bpf/libbpf.c
tools/lib/bpf/relo_core.c
tools/lib/bpf/relo_core.h

index 8d3c7ab..4f2408a 100644 (file)
@@ -7443,6 +7443,15 @@ int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
                                           MAX_TYPES_ARE_COMPAT_DEPTH);
 }
 
+#define MAX_TYPES_MATCH_DEPTH 2
+
+int bpf_core_types_match(const struct btf *local_btf, u32 local_id,
+                        const struct btf *targ_btf, u32 targ_id)
+{
+       return __bpf_core_types_match(local_btf, local_id, targ_btf, targ_id, false,
+                                     MAX_TYPES_MATCH_DEPTH);
+}
+
 static bool bpf_core_is_flavor_sep(const char *s)
 {
        /* check X___Y name pattern, where X and Y are not underscores */
index 8a45a84..64c4cc6 100644 (file)
@@ -5470,6 +5470,12 @@ int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
        return __bpf_core_types_are_compat(local_btf, local_id, targ_btf, targ_id, 32);
 }
 
+int bpf_core_types_match(const struct btf *local_btf, __u32 local_id,
+                        const struct btf *targ_btf, __u32 targ_id)
+{
+       return __bpf_core_types_match(local_btf, local_id, targ_btf, targ_id, false, 32);
+}
+
 static size_t bpf_core_hash_fn(const void *key, void *ctx)
 {
        return (size_t)key;
index e070123..fe25330 100644 (file)
@@ -95,6 +95,7 @@ static const char *core_relo_kind_str(enum bpf_core_relo_kind kind)
        case BPF_CORE_TYPE_ID_LOCAL: return "local_type_id";
        case BPF_CORE_TYPE_ID_TARGET: return "target_type_id";
        case BPF_CORE_TYPE_EXISTS: return "type_exists";
+       case BPF_CORE_TYPE_MATCHES: return "type_matches";
        case BPF_CORE_TYPE_SIZE: return "type_size";
        case BPF_CORE_ENUMVAL_EXISTS: return "enumval_exists";
        case BPF_CORE_ENUMVAL_VALUE: return "enumval_value";
@@ -123,6 +124,7 @@ static bool core_relo_is_type_based(enum bpf_core_relo_kind kind)
        case BPF_CORE_TYPE_ID_LOCAL:
        case BPF_CORE_TYPE_ID_TARGET:
        case BPF_CORE_TYPE_EXISTS:
+       case BPF_CORE_TYPE_MATCHES:
        case BPF_CORE_TYPE_SIZE:
                return true;
        default:
@@ -251,7 +253,7 @@ recur:
  *   - field 'a' access (corresponds to '2' in low-level spec);
  *   - array element #3 access (corresponds to '3' in low-level spec).
  *
- * Type-based relocations (TYPE_EXISTS/TYPE_SIZE,
+ * Type-based relocations (TYPE_EXISTS/TYPE_MATCHES/TYPE_SIZE,
  * TYPE_ID_LOCAL/TYPE_ID_TARGET) don't capture any field information. Their
  * spec and raw_spec are kept empty.
  *
@@ -568,9 +570,14 @@ static int bpf_core_spec_match(struct bpf_core_spec *local_spec,
        targ_spec->relo_kind = local_spec->relo_kind;
 
        if (core_relo_is_type_based(local_spec->relo_kind)) {
-               return bpf_core_types_are_compat(local_spec->btf,
-                                                local_spec->root_type_id,
-                                                targ_btf, targ_id);
+               if (local_spec->relo_kind == BPF_CORE_TYPE_MATCHES)
+                       return bpf_core_types_match(local_spec->btf,
+                                                   local_spec->root_type_id,
+                                                   targ_btf, targ_id);
+               else
+                       return bpf_core_types_are_compat(local_spec->btf,
+                                                        local_spec->root_type_id,
+                                                        targ_btf, targ_id);
        }
 
        local_acc = &local_spec->spec[0];
@@ -819,6 +826,7 @@ static int bpf_core_calc_type_relo(const struct bpf_core_relo *relo,
                        *validate = false;
                break;
        case BPF_CORE_TYPE_EXISTS:
+       case BPF_CORE_TYPE_MATCHES:
                *val = 1;
                break;
        case BPF_CORE_TYPE_SIZE:
@@ -1410,3 +1418,266 @@ int bpf_core_calc_relo_insn(const char *prog_name,
 
        return 0;
 }
+
+static bool bpf_core_names_match(const struct btf *local_btf, size_t local_name_off,
+                                const struct btf *targ_btf, size_t targ_name_off)
+{
+       const char *local_n, *targ_n;
+       size_t local_len, targ_len;
+
+       local_n = btf__name_by_offset(local_btf, local_name_off);
+       targ_n = btf__name_by_offset(targ_btf, targ_name_off);
+
+       if (str_is_empty(targ_n))
+               return str_is_empty(local_n);
+
+       targ_len = bpf_core_essential_name_len(targ_n);
+       local_len = bpf_core_essential_name_len(local_n);
+
+       return targ_len == local_len && strncmp(local_n, targ_n, local_len) == 0;
+}
+
+static int bpf_core_enums_match(const struct btf *local_btf, const struct btf_type *local_t,
+                               const struct btf *targ_btf, const struct btf_type *targ_t)
+{
+       __u16 local_vlen = btf_vlen(local_t);
+       __u16 targ_vlen = btf_vlen(targ_t);
+       int i, j;
+
+       if (local_t->size != targ_t->size)
+               return 0;
+
+       if (local_vlen > targ_vlen)
+               return 0;
+
+       /* iterate over the local enum's variants and make sure each has
+        * a symbolic name correspondent in the target
+        */
+       for (i = 0; i < local_vlen; i++) {
+               bool matched = false;
+               __u32 local_n_off, targ_n_off;
+
+               local_n_off = btf_is_enum(local_t) ? btf_enum(local_t)[i].name_off :
+                                                    btf_enum64(local_t)[i].name_off;
+
+               for (j = 0; j < targ_vlen; j++) {
+                       targ_n_off = btf_is_enum(targ_t) ? btf_enum(targ_t)[j].name_off :
+                                                          btf_enum64(targ_t)[j].name_off;
+
+                       if (bpf_core_names_match(local_btf, local_n_off, targ_btf, targ_n_off)) {
+                               matched = true;
+                               break;
+                       }
+               }
+
+               if (!matched)
+                       return 0;
+       }
+       return 1;
+}
+
+static int bpf_core_composites_match(const struct btf *local_btf, const struct btf_type *local_t,
+                                    const struct btf *targ_btf, const struct btf_type *targ_t,
+                                    bool behind_ptr, int level)
+{
+       const struct btf_member *local_m = btf_members(local_t);
+       __u16 local_vlen = btf_vlen(local_t);
+       __u16 targ_vlen = btf_vlen(targ_t);
+       int i, j, err;
+
+       if (local_vlen > targ_vlen)
+               return 0;
+
+       /* check that all local members have a match in the target */
+       for (i = 0; i < local_vlen; i++, local_m++) {
+               const struct btf_member *targ_m = btf_members(targ_t);
+               bool matched = false;
+
+               for (j = 0; j < targ_vlen; j++, targ_m++) {
+                       if (!bpf_core_names_match(local_btf, local_m->name_off,
+                                                 targ_btf, targ_m->name_off))
+                               continue;
+
+                       err = __bpf_core_types_match(local_btf, local_m->type, targ_btf,
+                                                    targ_m->type, behind_ptr, level - 1);
+                       if (err > 0) {
+                               matched = true;
+                               break;
+                       }
+               }
+
+               if (!matched)
+                       return 0;
+       }
+       return 1;
+}
+
+/* Check that two types "match".
+ *
+ * The matching relation is defined as follows:
+ * - modifiers and typedefs are stripped (and, hence, effectively ignored)
+ * - generally speaking types need to be of same kind (struct vs. struct, union
+ *   vs. union, etc.)
+ *   - exceptions are struct/union behind a pointer which could also match a
+ *     forward declaration of a struct or union, respectively, and enum vs.
+ *     enum64 (see below)
+ * Then, depending on type:
+ * - integers:
+ *   - match if size and signedness match
+ * - arrays & pointers:
+ *   - target types are recursively matched
+ * - structs & unions:
+ *   - local members need to exist in target with the same name
+ *   - for each member we recursively check match unless it is already behind a
+ *     pointer, in which case we only check matching names and compatible kind
+ * - enums:
+ *   - local variants have to have a match in target by symbolic name (but not
+ *     numeric value)
+ *   - size has to match (but enum may match enum64 and vice versa)
+ * - function pointers:
+ *   - number and position of arguments in local type has to match target
+ *   - for each argument and the return value we recursively check match
+ */
+int __bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
+                          __u32 targ_id, bool behind_ptr, int level)
+{
+       const struct btf_type *local_t, *targ_t;
+       int depth = 32; /* max recursion depth */
+       __u16 local_k, targ_k;
+
+       if (level <= 0)
+               return -EINVAL;
+
+       local_t = btf_type_by_id(local_btf, local_id);
+       targ_t = btf_type_by_id(targ_btf, targ_id);
+
+recur:
+       depth--;
+       if (depth < 0)
+               return -EINVAL;
+
+       local_t = skip_mods_and_typedefs(local_btf, local_id, &local_id);
+       targ_t = skip_mods_and_typedefs(targ_btf, targ_id, &targ_id);
+       if (!local_t || !targ_t)
+               return -EINVAL;
+
+       if (!bpf_core_names_match(local_btf, local_t->name_off, targ_btf, targ_t->name_off))
+               return 0;
+
+       local_k = btf_kind(local_t);
+       targ_k = btf_kind(targ_t);
+
+       switch (local_k) {
+       case BTF_KIND_UNKN:
+               return local_k == targ_k;
+       case BTF_KIND_FWD: {
+               bool local_f = BTF_INFO_KFLAG(local_t->info);
+
+               if (behind_ptr) {
+                       if (local_k == targ_k)
+                               return local_f == BTF_INFO_KFLAG(targ_t->info);
+
+                       /* for forward declarations kflag dictates whether the
+                        * target is a struct (0) or union (1)
+                        */
+                       return (targ_k == BTF_KIND_STRUCT && !local_f) ||
+                              (targ_k == BTF_KIND_UNION && local_f);
+               } else {
+                       if (local_k != targ_k)
+                               return 0;
+
+                       /* match if the forward declaration is for the same kind */
+                       return local_f == BTF_INFO_KFLAG(targ_t->info);
+               }
+       }
+       case BTF_KIND_ENUM:
+       case BTF_KIND_ENUM64:
+               if (!btf_is_any_enum(targ_t))
+                       return 0;
+
+               return bpf_core_enums_match(local_btf, local_t, targ_btf, targ_t);
+       case BTF_KIND_STRUCT:
+       case BTF_KIND_UNION:
+               if (behind_ptr) {
+                       bool targ_f = BTF_INFO_KFLAG(targ_t->info);
+
+                       if (local_k == targ_k)
+                               return 1;
+
+                       if (targ_k != BTF_KIND_FWD)
+                               return 0;
+
+                       return (local_k == BTF_KIND_UNION) == targ_f;
+               } else {
+                       if (local_k != targ_k)
+                               return 0;
+
+                       return bpf_core_composites_match(local_btf, local_t, targ_btf, targ_t,
+                                                        behind_ptr, level);
+               }
+       case BTF_KIND_INT: {
+               __u8 local_sgn;
+               __u8 targ_sgn;
+
+               if (local_k != targ_k)
+                       return 0;
+
+               local_sgn = btf_int_encoding(local_t) & BTF_INT_SIGNED;
+               targ_sgn = btf_int_encoding(targ_t) & BTF_INT_SIGNED;
+
+               return local_t->size == targ_t->size && local_sgn == targ_sgn;
+       }
+       case BTF_KIND_PTR:
+               if (local_k != targ_k)
+                       return 0;
+
+               behind_ptr = true;
+
+               local_id = local_t->type;
+               targ_id = targ_t->type;
+               goto recur;
+       case BTF_KIND_ARRAY: {
+               const struct btf_array *local_array = btf_array(local_t);
+               const struct btf_array *targ_array = btf_array(targ_t);
+
+               if (local_k != targ_k)
+                       return 0;
+
+               if (local_array->nelems != targ_array->nelems)
+                       return 0;
+
+               local_id = local_array->type;
+               targ_id = targ_array->type;
+               goto recur;
+       }
+       case BTF_KIND_FUNC_PROTO: {
+               struct btf_param *local_p = btf_params(local_t);
+               struct btf_param *targ_p = btf_params(targ_t);
+               __u16 local_vlen = btf_vlen(local_t);
+               __u16 targ_vlen = btf_vlen(targ_t);
+               int i, err;
+
+               if (local_k != targ_k)
+                       return 0;
+
+               if (local_vlen != targ_vlen)
+                       return 0;
+
+               for (i = 0; i < local_vlen; i++, local_p++, targ_p++) {
+                       err = __bpf_core_types_match(local_btf, local_p->type, targ_btf,
+                                                    targ_p->type, behind_ptr, level - 1);
+                       if (err <= 0)
+                               return err;
+               }
+
+               /* tail recurse for return type check */
+               local_id = local_t->type;
+               targ_id = targ_t->type;
+               goto recur;
+       }
+       default:
+               pr_warn("unexpected kind %s relocated, local [%d], target [%d]\n",
+                       btf_kind_str(local_t), local_id, targ_id);
+               return 0;
+       }
+}
index 3fd3842..1c0566d 100644 (file)
@@ -72,6 +72,10 @@ int __bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
                                const struct btf *targ_btf, __u32 targ_id, int level);
 int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
                              const struct btf *targ_btf, __u32 targ_id);
+int __bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
+                          __u32 targ_id, bool behind_ptr, int level);
+int bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
+                        __u32 targ_id);
 
 size_t bpf_core_essential_name_len(const char *name);