OSDN Git Service

bpf: Introduce BPF_PROG_TYPE_STRUCT_OPS
authorMartin KaFai Lau <kafai@fb.com>
Thu, 9 Jan 2020 00:35:03 +0000 (16:35 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Thu, 9 Jan 2020 16:46:18 +0000 (08:46 -0800)
This patch allows the kernel's struct ops (i.e. func ptr) to be
implemented in BPF.  The first use case in this series is the
"struct tcp_congestion_ops" which will be introduced in a
latter patch.

This patch introduces a new prog type BPF_PROG_TYPE_STRUCT_OPS.
The BPF_PROG_TYPE_STRUCT_OPS prog is verified against a particular
func ptr of a kernel struct.  The attr->attach_btf_id is the btf id
of a kernel struct.  The attr->expected_attach_type is the member
"index" of that kernel struct.  The first member of a struct starts
with member index 0.  That will avoid ambiguity when a kernel struct
has multiple func ptrs with the same func signature.

For example, a BPF_PROG_TYPE_STRUCT_OPS prog is written
to implement the "init" func ptr of the "struct tcp_congestion_ops".
The attr->attach_btf_id is the btf id of the "struct tcp_congestion_ops"
of the _running_ kernel.  The attr->expected_attach_type is 3.

The ctx of BPF_PROG_TYPE_STRUCT_OPS is an array of u64 args saved
by arch_prepare_bpf_trampoline that will be done in the next
patch when introducing BPF_MAP_TYPE_STRUCT_OPS.

"struct bpf_struct_ops" is introduced as a common interface for the kernel
struct that supports BPF_PROG_TYPE_STRUCT_OPS prog.  The supporting kernel
struct will need to implement an instance of the "struct bpf_struct_ops".

The supporting kernel struct also needs to implement a bpf_verifier_ops.
During BPF_PROG_LOAD, bpf_struct_ops_find() will find the right
bpf_verifier_ops by searching the attr->attach_btf_id.

A new "btf_struct_access" is also added to the bpf_verifier_ops such
that the supporting kernel struct can optionally provide its own specific
check on accessing the func arg (e.g. provide limited write access).

After btf_vmlinux is parsed, the new bpf_struct_ops_init() is called
to initialize some values (e.g. the btf id of the supporting kernel
struct) and it can only be done once the btf_vmlinux is available.

The R0 checks at BPF_EXIT is excluded for the BPF_PROG_TYPE_STRUCT_OPS prog
if the return type of the prog->aux->attach_func_proto is "void".

Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Andrii Nakryiko <andriin@fb.com>
Acked-by: Yonghong Song <yhs@fb.com>
Link: https://lore.kernel.org/bpf/20200109003503.3855825-1-kafai@fb.com
include/linux/bpf.h
include/linux/bpf_types.h
include/linux/btf.h
include/uapi/linux/bpf.h
kernel/bpf/Makefile
kernel/bpf/bpf_struct_ops.c [new file with mode: 0644]
kernel/bpf/bpf_struct_ops_types.h [new file with mode: 0644]
kernel/bpf/btf.c
kernel/bpf/syscall.c
kernel/bpf/verifier.c

index b14e51d..50f3b20 100644 (file)
@@ -349,6 +349,10 @@ struct bpf_verifier_ops {
                                  const struct bpf_insn *src,
                                  struct bpf_insn *dst,
                                  struct bpf_prog *prog, u32 *target_size);
+       int (*btf_struct_access)(struct bpf_verifier_log *log,
+                                const struct btf_type *t, int off, int size,
+                                enum bpf_access_type atype,
+                                u32 *next_btf_id);
 };
 
 struct bpf_prog_offload_ops {
@@ -668,6 +672,32 @@ struct bpf_array_aux {
        struct work_struct work;
 };
 
+struct btf_type;
+struct btf_member;
+
+#define BPF_STRUCT_OPS_MAX_NR_MEMBERS 64
+struct bpf_struct_ops {
+       const struct bpf_verifier_ops *verifier_ops;
+       int (*init)(struct btf *btf);
+       int (*check_member)(const struct btf_type *t,
+                           const struct btf_member *member);
+       const struct btf_type *type;
+       const char *name;
+       struct btf_func_model func_models[BPF_STRUCT_OPS_MAX_NR_MEMBERS];
+       u32 type_id;
+};
+
+#if defined(CONFIG_BPF_JIT) && defined(CONFIG_BPF_SYSCALL)
+const struct bpf_struct_ops *bpf_struct_ops_find(u32 type_id);
+void bpf_struct_ops_init(struct btf *btf);
+#else
+static inline const struct bpf_struct_ops *bpf_struct_ops_find(u32 type_id)
+{
+       return NULL;
+}
+static inline void bpf_struct_ops_init(struct btf *btf) { }
+#endif
+
 struct bpf_array {
        struct bpf_map map;
        u32 elem_size;
index 93740b3..fadd243 100644 (file)
@@ -65,6 +65,10 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_LIRC_MODE2, lirc_mode2,
 BPF_PROG_TYPE(BPF_PROG_TYPE_SK_REUSEPORT, sk_reuseport,
              struct sk_reuseport_md, struct sk_reuseport_kern)
 #endif
+#if defined(CONFIG_BPF_JIT)
+BPF_PROG_TYPE(BPF_PROG_TYPE_STRUCT_OPS, bpf_struct_ops,
+             void *, void *)
+#endif
 
 BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
index 79d4abc..f74a09a 100644 (file)
@@ -53,6 +53,18 @@ bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s,
                           u32 expected_offset, u32 expected_size);
 int btf_find_spin_lock(const struct btf *btf, const struct btf_type *t);
 bool btf_type_is_void(const struct btf_type *t);
+s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind);
+const struct btf_type *btf_type_skip_modifiers(const struct btf *btf,
+                                              u32 id, u32 *res_id);
+const struct btf_type *btf_type_resolve_ptr(const struct btf *btf,
+                                           u32 id, u32 *res_id);
+const struct btf_type *btf_type_resolve_func_ptr(const struct btf *btf,
+                                                u32 id, u32 *res_id);
+
+#define for_each_member(i, struct_type, member)                        \
+       for (i = 0, member = btf_type_member(struct_type);      \
+            i < btf_type_vlen(struct_type);                    \
+            i++, member++)
 
 static inline bool btf_type_is_ptr(const struct btf_type *t)
 {
@@ -84,6 +96,28 @@ static inline bool btf_type_is_func_proto(const struct btf_type *t)
        return BTF_INFO_KIND(t->info) == BTF_KIND_FUNC_PROTO;
 }
 
+static inline u16 btf_type_vlen(const struct btf_type *t)
+{
+       return BTF_INFO_VLEN(t->info);
+}
+
+static inline bool btf_type_kflag(const struct btf_type *t)
+{
+       return BTF_INFO_KFLAG(t->info);
+}
+
+static inline u32 btf_member_bitfield_size(const struct btf_type *struct_type,
+                                          const struct btf_member *member)
+{
+       return btf_type_kflag(struct_type) ? BTF_MEMBER_BITFIELD_SIZE(member->offset)
+                                          : 0;
+}
+
+static inline const struct btf_member *btf_type_member(const struct btf_type *t)
+{
+       return (const struct btf_member *)(t + 1);
+}
+
 #ifdef CONFIG_BPF_SYSCALL
 const struct btf_type *btf_type_by_id(const struct btf *btf, u32 type_id);
 const char *btf_name_by_offset(const struct btf *btf, u32 offset);
index 7df436d..c1eeb3e 100644 (file)
@@ -174,6 +174,7 @@ enum bpf_prog_type {
        BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
        BPF_PROG_TYPE_CGROUP_SOCKOPT,
        BPF_PROG_TYPE_TRACING,
+       BPF_PROG_TYPE_STRUCT_OPS,
 };
 
 enum bpf_attach_type {
index d4f3303..046ce5d 100644 (file)
@@ -27,3 +27,6 @@ endif
 ifeq ($(CONFIG_SYSFS),y)
 obj-$(CONFIG_DEBUG_INFO_BTF) += sysfs_btf.o
 endif
+ifeq ($(CONFIG_BPF_JIT),y)
+obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o
+endif
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
new file mode 100644 (file)
index 0000000..2ea68fe
--- /dev/null
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2019 Facebook */
+
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/filter.h>
+#include <linux/slab.h>
+#include <linux/numa.h>
+#include <linux/seq_file.h>
+#include <linux/refcount.h>
+
+#define BPF_STRUCT_OPS_TYPE(_name)                             \
+extern struct bpf_struct_ops bpf_##_name;
+#include "bpf_struct_ops_types.h"
+#undef BPF_STRUCT_OPS_TYPE
+
+enum {
+#define BPF_STRUCT_OPS_TYPE(_name) BPF_STRUCT_OPS_TYPE_##_name,
+#include "bpf_struct_ops_types.h"
+#undef BPF_STRUCT_OPS_TYPE
+       __NR_BPF_STRUCT_OPS_TYPE,
+};
+
+static struct bpf_struct_ops * const bpf_struct_ops[] = {
+#define BPF_STRUCT_OPS_TYPE(_name)                             \
+       [BPF_STRUCT_OPS_TYPE_##_name] = &bpf_##_name,
+#include "bpf_struct_ops_types.h"
+#undef BPF_STRUCT_OPS_TYPE
+};
+
+const struct bpf_verifier_ops bpf_struct_ops_verifier_ops = {
+};
+
+const struct bpf_prog_ops bpf_struct_ops_prog_ops = {
+};
+
+void bpf_struct_ops_init(struct btf *btf)
+{
+       const struct btf_member *member;
+       struct bpf_struct_ops *st_ops;
+       struct bpf_verifier_log log = {};
+       const struct btf_type *t;
+       const char *mname;
+       s32 type_id;
+       u32 i, j;
+
+       for (i = 0; i < ARRAY_SIZE(bpf_struct_ops); i++) {
+               st_ops = bpf_struct_ops[i];
+
+               type_id = btf_find_by_name_kind(btf, st_ops->name,
+                                               BTF_KIND_STRUCT);
+               if (type_id < 0) {
+                       pr_warn("Cannot find struct %s in btf_vmlinux\n",
+                               st_ops->name);
+                       continue;
+               }
+               t = btf_type_by_id(btf, type_id);
+               if (btf_type_vlen(t) > BPF_STRUCT_OPS_MAX_NR_MEMBERS) {
+                       pr_warn("Cannot support #%u members in struct %s\n",
+                               btf_type_vlen(t), st_ops->name);
+                       continue;
+               }
+
+               for_each_member(j, t, member) {
+                       const struct btf_type *func_proto;
+
+                       mname = btf_name_by_offset(btf, member->name_off);
+                       if (!*mname) {
+                               pr_warn("anon member in struct %s is not supported\n",
+                                       st_ops->name);
+                               break;
+                       }
+
+                       if (btf_member_bitfield_size(t, member)) {
+                               pr_warn("bit field member %s in struct %s is not supported\n",
+                                       mname, st_ops->name);
+                               break;
+                       }
+
+                       func_proto = btf_type_resolve_func_ptr(btf,
+                                                              member->type,
+                                                              NULL);
+                       if (func_proto &&
+                           btf_distill_func_proto(&log, btf,
+                                                  func_proto, mname,
+                                                  &st_ops->func_models[j])) {
+                               pr_warn("Error in parsing func ptr %s in struct %s\n",
+                                       mname, st_ops->name);
+                               break;
+                       }
+               }
+
+               if (j == btf_type_vlen(t)) {
+                       if (st_ops->init(btf)) {
+                               pr_warn("Error in init bpf_struct_ops %s\n",
+                                       st_ops->name);
+                       } else {
+                               st_ops->type_id = type_id;
+                               st_ops->type = t;
+                       }
+               }
+       }
+}
+
+extern struct btf *btf_vmlinux;
+
+const struct bpf_struct_ops *bpf_struct_ops_find(u32 type_id)
+{
+       unsigned int i;
+
+       if (!type_id || !btf_vmlinux)
+               return NULL;
+
+       for (i = 0; i < ARRAY_SIZE(bpf_struct_ops); i++) {
+               if (bpf_struct_ops[i]->type_id == type_id)
+                       return bpf_struct_ops[i];
+       }
+
+       return NULL;
+}
diff --git a/kernel/bpf/bpf_struct_ops_types.h b/kernel/bpf/bpf_struct_ops_types.h
new file mode 100644 (file)
index 0000000..7bb13ff
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* internal file - do not include directly */
+
+/* To be filled in a later patch */
index 48bbde2..12af4a1 100644 (file)
  */
 #define BTF_MAX_SIZE (16 * 1024 * 1024)
 
-#define for_each_member(i, struct_type, member)                        \
-       for (i = 0, member = btf_type_member(struct_type);      \
-            i < btf_type_vlen(struct_type);                    \
-            i++, member++)
-
 #define for_each_member_from(i, from, struct_type, member)             \
        for (i = from, member = btf_type_member(struct_type) + from;    \
             i < btf_type_vlen(struct_type);                            \
@@ -382,6 +377,65 @@ static bool btf_type_is_datasec(const struct btf_type *t)
        return BTF_INFO_KIND(t->info) == BTF_KIND_DATASEC;
 }
 
+s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind)
+{
+       const struct btf_type *t;
+       const char *tname;
+       u32 i;
+
+       for (i = 1; i <= btf->nr_types; i++) {
+               t = btf->types[i];
+               if (BTF_INFO_KIND(t->info) != kind)
+                       continue;
+
+               tname = btf_name_by_offset(btf, t->name_off);
+               if (!strcmp(tname, name))
+                       return i;
+       }
+
+       return -ENOENT;
+}
+
+const struct btf_type *btf_type_skip_modifiers(const struct btf *btf,
+                                              u32 id, u32 *res_id)
+{
+       const struct btf_type *t = btf_type_by_id(btf, id);
+
+       while (btf_type_is_modifier(t)) {
+               id = t->type;
+               t = btf_type_by_id(btf, t->type);
+       }
+
+       if (res_id)
+               *res_id = id;
+
+       return t;
+}
+
+const struct btf_type *btf_type_resolve_ptr(const struct btf *btf,
+                                           u32 id, u32 *res_id)
+{
+       const struct btf_type *t;
+
+       t = btf_type_skip_modifiers(btf, id, NULL);
+       if (!btf_type_is_ptr(t))
+               return NULL;
+
+       return btf_type_skip_modifiers(btf, t->type, res_id);
+}
+
+const struct btf_type *btf_type_resolve_func_ptr(const struct btf *btf,
+                                                u32 id, u32 *res_id)
+{
+       const struct btf_type *ptype;
+
+       ptype = btf_type_resolve_ptr(btf, id, res_id);
+       if (ptype && btf_type_is_func_proto(ptype))
+               return ptype;
+
+       return NULL;
+}
+
 /* Types that act only as a source, not sink or intermediate
  * type when resolving.
  */
@@ -446,16 +500,6 @@ static const char *btf_int_encoding_str(u8 encoding)
                return "UNKN";
 }
 
-static u16 btf_type_vlen(const struct btf_type *t)
-{
-       return BTF_INFO_VLEN(t->info);
-}
-
-static bool btf_type_kflag(const struct btf_type *t)
-{
-       return BTF_INFO_KFLAG(t->info);
-}
-
 static u32 btf_member_bit_offset(const struct btf_type *struct_type,
                             const struct btf_member *member)
 {
@@ -463,13 +507,6 @@ static u32 btf_member_bit_offset(const struct btf_type *struct_type,
                                           : member->offset;
 }
 
-static u32 btf_member_bitfield_size(const struct btf_type *struct_type,
-                                   const struct btf_member *member)
-{
-       return btf_type_kflag(struct_type) ? BTF_MEMBER_BITFIELD_SIZE(member->offset)
-                                          : 0;
-}
-
 static u32 btf_type_int(const struct btf_type *t)
 {
        return *(u32 *)(t + 1);
@@ -480,11 +517,6 @@ static const struct btf_array *btf_type_array(const struct btf_type *t)
        return (const struct btf_array *)(t + 1);
 }
 
-static const struct btf_member *btf_type_member(const struct btf_type *t)
-{
-       return (const struct btf_member *)(t + 1);
-}
-
 static const struct btf_enum *btf_type_enum(const struct btf_type *t)
 {
        return (const struct btf_enum *)(t + 1);
@@ -3605,6 +3637,8 @@ struct btf *btf_parse_vmlinux(void)
                goto errout;
        }
 
+       bpf_struct_ops_init(btf);
+
        btf_verifier_env_free(env);
        refcount_set(&btf->refcnt, 1);
        return btf;
index 81ee859..03a02ef 100644 (file)
@@ -1672,17 +1672,22 @@ bpf_prog_load_check_attach(enum bpf_prog_type prog_type,
                           enum bpf_attach_type expected_attach_type,
                           u32 btf_id, u32 prog_fd)
 {
-       switch (prog_type) {
-       case BPF_PROG_TYPE_TRACING:
+       if (btf_id) {
                if (btf_id > BTF_MAX_TYPE)
                        return -EINVAL;
-               break;
-       default:
-               if (btf_id || prog_fd)
+
+               switch (prog_type) {
+               case BPF_PROG_TYPE_TRACING:
+               case BPF_PROG_TYPE_STRUCT_OPS:
+                       break;
+               default:
                        return -EINVAL;
-               break;
+               }
        }
 
+       if (prog_fd && prog_type != BPF_PROG_TYPE_TRACING)
+               return -EINVAL;
+
        switch (prog_type) {
        case BPF_PROG_TYPE_CGROUP_SOCK:
                switch (expected_attach_type) {
index d433d70..586ed3c 100644 (file)
@@ -2859,11 +2859,6 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
        u32 btf_id;
        int ret;
 
-       if (atype != BPF_READ) {
-               verbose(env, "only read is supported\n");
-               return -EACCES;
-       }
-
        if (off < 0) {
                verbose(env,
                        "R%d is ptr_%s invalid negative access: off=%d\n",
@@ -2880,17 +2875,32 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
                return -EACCES;
        }
 
-       ret = btf_struct_access(&env->log, t, off, size, atype, &btf_id);
+       if (env->ops->btf_struct_access) {
+               ret = env->ops->btf_struct_access(&env->log, t, off, size,
+                                                 atype, &btf_id);
+       } else {
+               if (atype != BPF_READ) {
+                       verbose(env, "only read is supported\n");
+                       return -EACCES;
+               }
+
+               ret = btf_struct_access(&env->log, t, off, size, atype,
+                                       &btf_id);
+       }
+
        if (ret < 0)
                return ret;
 
-       if (ret == SCALAR_VALUE) {
-               mark_reg_unknown(env, regs, value_regno);
-               return 0;
+       if (atype == BPF_READ) {
+               if (ret == SCALAR_VALUE) {
+                       mark_reg_unknown(env, regs, value_regno);
+                       return 0;
+               }
+               mark_reg_known_zero(env, regs, value_regno);
+               regs[value_regno].type = PTR_TO_BTF_ID;
+               regs[value_regno].btf_id = btf_id;
        }
-       mark_reg_known_zero(env, regs, value_regno);
-       regs[value_regno].type = PTR_TO_BTF_ID;
-       regs[value_regno].btf_id = btf_id;
+
        return 0;
 }
 
@@ -6349,8 +6359,30 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
 static int check_return_code(struct bpf_verifier_env *env)
 {
        struct tnum enforce_attach_type_range = tnum_unknown;
+       const struct bpf_prog *prog = env->prog;
        struct bpf_reg_state *reg;
        struct tnum range = tnum_range(0, 1);
+       int err;
+
+       /* The struct_ops func-ptr's return type could be "void" */
+       if (env->prog->type == BPF_PROG_TYPE_STRUCT_OPS &&
+           !prog->aux->attach_func_proto->type)
+               return 0;
+
+       /* eBPF calling convetion is such that R0 is used
+        * to return the value from eBPF program.
+        * Make sure that it's readable at this time
+        * of bpf_exit, which means that program wrote
+        * something into it earlier
+        */
+       err = check_reg_arg(env, BPF_REG_0, SRC_OP);
+       if (err)
+               return err;
+
+       if (is_pointer_value(env, BPF_REG_0)) {
+               verbose(env, "R0 leaks addr as return value\n");
+               return -EACCES;
+       }
 
        switch (env->prog->type) {
        case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
@@ -8016,21 +8048,6 @@ static int do_check(struct bpf_verifier_env *env)
                                if (err)
                                        return err;
 
-                               /* eBPF calling convetion is such that R0 is used
-                                * to return the value from eBPF program.
-                                * Make sure that it's readable at this time
-                                * of bpf_exit, which means that program wrote
-                                * something into it earlier
-                                */
-                               err = check_reg_arg(env, BPF_REG_0, SRC_OP);
-                               if (err)
-                                       return err;
-
-                               if (is_pointer_value(env, BPF_REG_0)) {
-                                       verbose(env, "R0 leaks addr as return value\n");
-                                       return -EACCES;
-                               }
-
                                err = check_return_code(env);
                                if (err)
                                        return err;
@@ -8829,12 +8846,14 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
                        convert_ctx_access = bpf_xdp_sock_convert_ctx_access;
                        break;
                case PTR_TO_BTF_ID:
-                       if (type == BPF_WRITE) {
+                       if (type == BPF_READ) {
+                               insn->code = BPF_LDX | BPF_PROBE_MEM |
+                                       BPF_SIZE((insn)->code);
+                               env->prog->aux->num_exentries++;
+                       } else if (env->prog->type != BPF_PROG_TYPE_STRUCT_OPS) {
                                verbose(env, "Writes through BTF pointers are not allowed\n");
                                return -EINVAL;
                        }
-                       insn->code = BPF_LDX | BPF_PROBE_MEM | BPF_SIZE((insn)->code);
-                       env->prog->aux->num_exentries++;
                        continue;
                default:
                        continue;
@@ -9502,6 +9521,58 @@ static void print_verification_stats(struct bpf_verifier_env *env)
                env->peak_states, env->longest_mark_read_walk);
 }
 
+static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
+{
+       const struct btf_type *t, *func_proto;
+       const struct bpf_struct_ops *st_ops;
+       const struct btf_member *member;
+       struct bpf_prog *prog = env->prog;
+       u32 btf_id, member_idx;
+       const char *mname;
+
+       btf_id = prog->aux->attach_btf_id;
+       st_ops = bpf_struct_ops_find(btf_id);
+       if (!st_ops) {
+               verbose(env, "attach_btf_id %u is not a supported struct\n",
+                       btf_id);
+               return -ENOTSUPP;
+       }
+
+       t = st_ops->type;
+       member_idx = prog->expected_attach_type;
+       if (member_idx >= btf_type_vlen(t)) {
+               verbose(env, "attach to invalid member idx %u of struct %s\n",
+                       member_idx, st_ops->name);
+               return -EINVAL;
+       }
+
+       member = &btf_type_member(t)[member_idx];
+       mname = btf_name_by_offset(btf_vmlinux, member->name_off);
+       func_proto = btf_type_resolve_func_ptr(btf_vmlinux, member->type,
+                                              NULL);
+       if (!func_proto) {
+               verbose(env, "attach to invalid member %s(@idx %u) of struct %s\n",
+                       mname, member_idx, st_ops->name);
+               return -EINVAL;
+       }
+
+       if (st_ops->check_member) {
+               int err = st_ops->check_member(t, member);
+
+               if (err) {
+                       verbose(env, "attach to unsupported member %s of struct %s\n",
+                               mname, st_ops->name);
+                       return err;
+               }
+       }
+
+       prog->aux->attach_func_proto = func_proto;
+       prog->aux->attach_func_name = mname;
+       env->ops = st_ops->verifier_ops;
+
+       return 0;
+}
+
 static int check_attach_btf_id(struct bpf_verifier_env *env)
 {
        struct bpf_prog *prog = env->prog;
@@ -9517,6 +9588,9 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
        long addr;
        u64 key;
 
+       if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
+               return check_struct_ops_btf_id(env);
+
        if (prog->type != BPF_PROG_TYPE_TRACING)
                return 0;