PTR_TO_TP_BUFFER,        /* reg points to a writable raw tp's buffer */
        PTR_TO_XDP_SOCK,         /* reg points to struct xdp_sock */
        PTR_TO_BTF_ID,           /* reg points to kernel struct */
+       PTR_TO_BTF_ID_OR_NULL,   /* reg points to kernel struct or NULL */
 };
 
 /* The information passed from prog-specific *_is_valid_access
        bool offload_requested;
        bool attach_btf_trace; /* true if attaching to BTF-enabled raw tp */
        bool func_proto_unreliable;
+       bool btf_id_or_null_non0_off;
        enum bpf_tramp_prog_type trampoline_prog_type;
        struct bpf_trampoline *trampoline;
        struct hlist_node tramp_hlist;
 
        return type == PTR_TO_MAP_VALUE_OR_NULL ||
               type == PTR_TO_SOCKET_OR_NULL ||
               type == PTR_TO_SOCK_COMMON_OR_NULL ||
-              type == PTR_TO_TCP_SOCK_OR_NULL;
+              type == PTR_TO_TCP_SOCK_OR_NULL ||
+              type == PTR_TO_BTF_ID_OR_NULL;
 }
 
 static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)
        [PTR_TO_TP_BUFFER]      = "tp_buffer",
        [PTR_TO_XDP_SOCK]       = "xdp_sock",
        [PTR_TO_BTF_ID]         = "ptr_",
+       [PTR_TO_BTF_ID_OR_NULL] = "ptr_or_null_",
 };
 
 static char slot_type_char[] = {
                        /* reg->off should be 0 for SCALAR_VALUE */
                        verbose(env, "%lld", reg->var_off.value + reg->off);
                } else {
-                       if (t == PTR_TO_BTF_ID)
+                       if (t == PTR_TO_BTF_ID || t == PTR_TO_BTF_ID_OR_NULL)
                                verbose(env, "%s", kernel_type_name(reg->btf_id));
                        verbose(env, "(id=%d", reg->id);
                        if (reg_type_may_be_refcounted_or_null(t))
        case PTR_TO_TCP_SOCK_OR_NULL:
        case PTR_TO_XDP_SOCK:
        case PTR_TO_BTF_ID:
+       case PTR_TO_BTF_ID_OR_NULL:
                return true;
        default:
                return false;
                 */
                *reg_type = info.reg_type;
 
-               if (*reg_type == PTR_TO_BTF_ID)
+               if (*reg_type == PTR_TO_BTF_ID || *reg_type == PTR_TO_BTF_ID_OR_NULL)
                        *btf_id = info.btf_id;
                else
                        env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
                                 * a sub-register.
                                 */
                                regs[value_regno].subreg_def = DEF_NOT_SUBREG;
-                               if (reg_type == PTR_TO_BTF_ID)
+                               if (reg_type == PTR_TO_BTF_ID ||
+                                   reg_type == PTR_TO_BTF_ID_OR_NULL)
                                        regs[value_regno].btf_id = btf_id;
                        }
                        regs[value_regno].type = reg_type;
                        reg->type = PTR_TO_SOCK_COMMON;
                } else if (reg->type == PTR_TO_TCP_SOCK_OR_NULL) {
                        reg->type = PTR_TO_TCP_SOCK;
+               } else if (reg->type == PTR_TO_BTF_ID_OR_NULL) {
+                       reg->type = PTR_TO_BTF_ID;
                }
                if (is_null) {
                        /* We don't need id and ref_obj_id from this point
        case PTR_TO_TCP_SOCK_OR_NULL:
        case PTR_TO_XDP_SOCK:
        case PTR_TO_BTF_ID:
+       case PTR_TO_BTF_ID_OR_NULL:
                return false;
        default:
                return true;
                prog->aux->attach_func_proto = t;
                if (!bpf_iter_prog_supported(prog))
                        return -EINVAL;
+               prog->aux->btf_id_or_null_non0_off = true;
                ret = btf_distill_func_proto(&env->log, btf, t,
                                             tname, &fmodel);
                return ret;