/* Size is known at compile time. */
        MEM_FIXED_SIZE          = BIT(10 + BPF_BASE_TYPE_BITS),
 
+       /* MEM is of an allocated object of type in program BTF. This is used to
+        * tag PTR_TO_BTF_ID allocated using bpf_obj_new.
+        */
+       MEM_ALLOC               = BIT(11 + BPF_BASE_TYPE_BITS),
+
        __BPF_TYPE_FLAG_MAX,
        __BPF_TYPE_LAST_FLAG    = __BPF_TYPE_FLAG_MAX - 1,
 };
        bool has_ref;
 };
 #endif /* CONFIG_KEYS */
+
+static inline bool type_is_alloc(u32 type)
+{
+       return type & MEM_ALLOC;
+}
+
 #endif /* _LINUX_BPF_H */
 
                return -EACCES;
        }
 
-       if (env->ops->btf_struct_access) {
+       if (env->ops->btf_struct_access && !type_is_alloc(reg->type)) {
+               if (!btf_is_kernel(reg->btf)) {
+                       verbose(env, "verifier internal error: reg->btf must be kernel btf\n");
+                       return -EFAULT;
+               }
                ret = env->ops->btf_struct_access(&env->log, reg, off, size, atype, &btf_id, &flag);
        } else {
-               if (atype != BPF_READ) {
+               /* Writes are permitted with default btf_struct_access for
+                * program allocated objects (which always have ref_obj_id > 0),
+                * but not for untrusted PTR_TO_BTF_ID | MEM_ALLOC.
+                */
+               if (atype != BPF_READ && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
                        verbose(env, "only read is supported\n");
                        return -EACCES;
                }
 
+               if (type_is_alloc(reg->type) && !reg->ref_obj_id) {
+                       verbose(env, "verifier internal error: ref_obj_id for allocated object must be non-zero\n");
+                       return -EFAULT;
+               }
+
                ret = btf_struct_access(&env->log, reg, off, size, atype, &btf_id, &flag);
        }
 
         * fixed offset.
         */
        case PTR_TO_BTF_ID:
+       case PTR_TO_BTF_ID | MEM_ALLOC:
                /* When referenced PTR_TO_BTF_ID is passed to release function,
                 * it's fixed offset must be 0. In the other cases, fixed offset
                 * can be non-zero.
                        break;
                case PTR_TO_BTF_ID:
                case PTR_TO_BTF_ID | PTR_UNTRUSTED:
+               /* PTR_TO_BTF_ID | MEM_ALLOC always has a valid lifetime, unlike
+                * PTR_TO_BTF_ID, and an active ref_obj_id, but the same cannot
+                * be said once it is marked PTR_UNTRUSTED, hence we must handle
+                * any faults for loads into such types. BPF_WRITE is disallowed
+                * for this case.
+                */
+               case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
                        if (type == BPF_READ) {
                                insn->code = BPF_LDX | BPF_PROBE_MEM |
                                        BPF_SIZE((insn)->code);