[PATCH bpf-next 2/3] bpf: Support pointer to struct in global func args

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add an ability to pass a pointer to a struct in arguments for a global
function. The struct may not have any pointers as it isn't possible to
verify them in a general case.

Passing a struct pointer to a global function allows to overcome the
limit on maximum number of arguments and avoid expensive and tricky
workarounds.

The implementation consists of two parts: if a global function has an
argument that is a pointer to struct then:
  1) In btf_check_func_arg_match(): check that the corresponding
register points to NULL or to a valid memory region that is large enough
to contain the struct.
  2) In btf_prepare_func_args(): set the corresponding register type to
PTR_TO_MEM_OR_NULL and its size to the size of the struct.

Signed-off-by: Dmitrii Banshchikov <me@xxxxxxxxxxxxx>
---
 include/linux/bpf_verifier.h |  2 ++
 kernel/bpf/btf.c             | 59 +++++++++++++++++++++++++++++++-----
 kernel/bpf/verifier.c        | 30 ++++++++++++++++++
 3 files changed, 83 insertions(+), 8 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index e941fe1484e5..dbd00a7743d8 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -467,6 +467,8 @@ bpf_prog_offload_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt);
 
 int check_ctx_reg(struct bpf_verifier_env *env,
 		  const struct bpf_reg_state *reg, int regno);
+int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+		  int regno, u32 mem_size);
 
 /* this lives here instead of in bpf.h because it needs to dereference tgt_prog */
 static inline u64 bpf_trampoline_compute_key(const struct bpf_prog *tgt_prog,
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 8d6bdb4f4d61..0bb5ea523486 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -5352,10 +5352,6 @@ int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 			goto out;
 		}
 		if (btf_type_is_ptr(t)) {
-			if (reg[i + 1].type == SCALAR_VALUE) {
-				bpf_log(log, "R%d is not a pointer\n", i + 1);
-				goto out;
-			}
 			/* If function expects ctx type in BTF check that caller
 			 * is passing PTR_TO_CTX.
 			 */
@@ -5370,6 +5366,30 @@ int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 					goto out;
 				continue;
 			}
+
+			t = btf_type_by_id(btf, t->type);
+			while (btf_type_is_modifier(t))
+				t = btf_type_by_id(btf, t->type);
+			if (btf_type_is_struct(t)) {
+				u32 mem_size;
+				const struct btf_type *ret =
+					btf_resolve_size(btf, t, &mem_size);
+
+				if (IS_ERR(ret)) {
+					bpf_log(log,
+						"unable to resolve the size of type '%s': %ld\n",
+						btf_name_by_offset(btf,
+								   t->name_off),
+						PTR_ERR(ret));
+					return -EINVAL;
+				}
+
+				if (check_mem_reg(env, &reg[i + 1], i + 1,
+						  mem_size))
+					goto out;
+
+				continue;
+			}
 		}
 		bpf_log(log, "Unrecognized arg#%d type %s\n",
 			i, btf_kind_str[BTF_INFO_KIND(t->info)]);
@@ -5471,10 +5491,33 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog,
 			reg[i + 1].type = SCALAR_VALUE;
 			continue;
 		}
-		if (btf_type_is_ptr(t) &&
-		    btf_get_prog_ctx_type(log, btf, t, prog_type, i)) {
-			reg[i + 1].type = PTR_TO_CTX;
-			continue;
+		if (btf_type_is_ptr(t)) {
+			if (btf_get_prog_ctx_type(log, btf, t, prog_type, i)) {
+				reg[i + 1].type = PTR_TO_CTX;
+				continue;
+			}
+
+			t = btf_type_by_id(btf, t->type);
+			while (btf_type_is_modifier(t))
+				t = btf_type_by_id(btf, t->type);
+			if (btf_type_is_struct(t)) {
+				const struct btf_type *ret = btf_resolve_size(
+					btf, t, &reg[i + 1].mem_size);
+
+				if (IS_ERR(ret)) {
+					const char *tname = btf_name_by_offset(
+						btf, t->name_off);
+					bpf_log(log,
+						"unable to resolve the size of type '%s': %ld\n",
+						tname, PTR_ERR(ret));
+					return -EINVAL;
+				}
+
+				reg[i + 1].type = PTR_TO_MEM_OR_NULL;
+				reg[i + 1].id = i + 1;
+
+				continue;
+			}
 		}
 		bpf_log(log, "Arg#%d type %s in %s() is not supported yet.\n",
 			i, btf_kind_str[BTF_INFO_KIND(t->info)], tname);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index dee296dbc7a1..a08f85fffdb2 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3886,6 +3886,29 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
 	}
 }
 
+int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+		  int regno, u32 mem_size)
+{
+	if (register_is_null(reg))
+		return 0;
+
+	if (reg_type_may_be_null(reg->type)) {
+		const struct bpf_reg_state saved_reg = *reg;
+		int rv;
+
+		if (mark_ptr_not_null_reg(reg)) {
+			verbose(env, "R%d type=%s expected nullable\n", regno,
+				reg_type_str[reg->type]);
+			return -EINVAL;
+		}
+		rv = check_helper_mem_access(env, regno, mem_size, 1, NULL);
+		*reg = saved_reg;
+		return rv;
+	}
+
+	return check_helper_mem_access(env, regno, mem_size, 1, NULL);
+}
+
 /* Implementation details:
  * bpf_map_lookup returns PTR_TO_MAP_VALUE_OR_NULL
  * Two bpf_map_lookups (even with the same key) will have different reg->id.
@@ -11435,6 +11458,13 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 				mark_reg_known_zero(env, regs, i);
 			else if (regs[i].type == SCALAR_VALUE)
 				mark_reg_unknown(env, regs, i);
+			else if (regs[i].type == PTR_TO_MEM_OR_NULL) {
+				const u32 mem_size = regs[i].mem_size;
+
+				mark_reg_known_zero(env, regs, i);
+				regs[i].mem_size = mem_size;
+				regs[i].id = i;
+			}
 		}
 	} else {
 		/* 1st arg to a function */
-- 
2.25.1




[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux