#endif
 };
 
+/* Returns true if struct is composed of scalars, 4 levels of nesting allowed */
+static bool __btf_type_is_scalar_struct(struct bpf_verifier_log *log,
+                                       const struct btf *btf,
+                                       const struct btf_type *t, int rec)
+{
+       const struct btf_type *member_type;
+       const struct btf_member *member;
+       u32 i;
+
+       if (!btf_type_is_struct(t))
+               return false;
+
+       for_each_member(i, t, member) {
+               const struct btf_array *array;
+
+               member_type = btf_type_skip_modifiers(btf, member->type, NULL);
+               if (btf_type_is_struct(member_type)) {
+                       if (rec >= 3) {
+                               bpf_log(log, "max struct nesting depth exceeded\n");
+                               return false;
+                       }
+                       if (!__btf_type_is_scalar_struct(log, btf, member_type, rec + 1))
+                               return false;
+                       continue;
+               }
+               if (btf_type_is_array(member_type)) {
+                       array = btf_type_array(member_type);
+                       if (!array->nelems)
+                               return false;
+                       member_type = btf_type_skip_modifiers(btf, array->type, NULL);
+                       if (!btf_type_is_scalar(member_type))
+                               return false;
+                       continue;
+               }
+               if (!btf_type_is_scalar(member_type))
+                       return false;
+       }
+       return true;
+}
+
 static int btf_check_func_arg_match(struct bpf_verifier_env *env,
                                    const struct btf *btf, u32 func_id,
                                    struct bpf_reg_state *regs,
                                    bool ptr_to_mem_ok)
 {
        struct bpf_verifier_log *log = &env->log;
+       bool is_kfunc = btf_is_kernel(btf);
        const char *func_name, *ref_tname;
        const struct btf_type *t, *ref_t;
        const struct btf_param *args;
 
                ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
                ref_tname = btf_name_by_offset(btf, ref_t->name_off);
-               if (btf_is_kernel(btf)) {
+               if (btf_get_prog_ctx_type(log, btf, t,
+                                         env->prog->type, i)) {
+                       /* If function expects ctx type in BTF check that caller
+                        * is passing PTR_TO_CTX.
+                        */
+                       if (reg->type != PTR_TO_CTX) {
+                               bpf_log(log,
+                                       "arg#%d expected pointer to ctx, but got %s\n",
+                                       i, btf_type_str(t));
+                               return -EINVAL;
+                       }
+                       if (check_ctx_reg(env, reg, regno))
+                               return -EINVAL;
+               } else if (is_kfunc && (reg->type == PTR_TO_BTF_ID || reg2btf_ids[reg->type])) {
                        const struct btf_type *reg_ref_t;
                        const struct btf *reg_btf;
                        const char *reg_ref_tname;
                        if (reg->type == PTR_TO_BTF_ID) {
                                reg_btf = reg->btf;
                                reg_ref_id = reg->btf_id;
-                       } else if (reg2btf_ids[reg->type]) {
+                       } else {
                                reg_btf = btf_vmlinux;
                                reg_ref_id = *reg2btf_ids[reg->type];
-                       } else {
-                               bpf_log(log, "kernel function %s args#%d expected pointer to %s %s but R%d is not a pointer to btf_id\n",
-                                       func_name, i,
-                                       btf_type_str(ref_t), ref_tname, regno);
-                               return -EINVAL;
                        }
 
                        reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id,
                                        reg_ref_tname);
                                return -EINVAL;
                        }
-               } else if (btf_get_prog_ctx_type(log, btf, t,
-                                                env->prog->type, i)) {
-                       /* If function expects ctx type in BTF check that caller
-                        * is passing PTR_TO_CTX.
-                        */
-                       if (reg->type != PTR_TO_CTX) {
-                               bpf_log(log,
-                                       "arg#%d expected pointer to ctx, but got %s\n",
-                                       i, btf_type_str(t));
-                               return -EINVAL;
-                       }
-                       if (check_ctx_reg(env, reg, regno))
-                               return -EINVAL;
                } else if (ptr_to_mem_ok) {
                        const struct btf_type *resolve_ret;
                        u32 type_size;
 
+                       if (is_kfunc) {
+                               /* Permit pointer to mem, but only when argument
+                                * type is pointer to scalar, or struct composed
+                                * (recursively) of scalars.
+                                */
+                               if (!btf_type_is_scalar(ref_t) &&
+                                   !__btf_type_is_scalar_struct(log, btf, ref_t, 0)) {
+                                       bpf_log(log,
+                                               "arg#%d pointer type %s %s must point to scalar or struct with scalar\n",
+                                               i, btf_type_str(ref_t), ref_tname);
+                                       return -EINVAL;
+                               }
+                       }
+
                        resolve_ret = btf_resolve_size(btf, ref_t, &type_size);
                        if (IS_ERR(resolve_ret)) {
                                bpf_log(log,
                        if (check_mem_reg(env, reg, regno, type_size))
                                return -EINVAL;
                } else {
+                       bpf_log(log, "reg type unsupported for arg#%d %sfunction %s#%d\n", i,
+                               is_kfunc ? "kernel " : "", func_name, func_id);
                        return -EINVAL;
                }
        }
                              const struct btf *btf, u32 func_id,
                              struct bpf_reg_state *regs)
 {
-       return btf_check_func_arg_match(env, btf, func_id, regs, false);
+       return btf_check_func_arg_match(env, btf, func_id, regs, true);
 }
 
 /* Convert BTF of a function into bpf_reg_state if possible