netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Amery Hung <ameryhung@gmail.com>
To: netdev@vger.kernel.org
Cc: bpf@vger.kernel.org, daniel@iogearbox.net, andrii@kernel.org,
	alexei.starovoitov@gmail.com, martin.lau@kernel.org,
	kuba@kernel.org, edumazet@google.com, xiyou.wangcong@gmail.com,
	cong.wang@bytedance.com, jhs@mojatatu.com, sinquersw@gmail.com,
	toke@redhat.com, jiri@resnulli.us, stfomichev@gmail.com,
	ekarani.silvestre@ccc.ufcg.edu.br, yangpeihao@sjtu.edu.cn,
	yepeilin.cs@gmail.com, ameryhung@gmail.com, ming.lei@redhat.com,
	kernel-team@meta.com
Subject: [PATCH bpf-next v3 04/18] bpf: Allow struct_ops prog to return referenced kptr
Date: Fri, 31 Jan 2025 11:28:43 -0800	[thread overview]
Message-ID: <20250131192912.133796-5-ameryhung@gmail.com> (raw)
In-Reply-To: <20250131192912.133796-1-ameryhung@gmail.com>

From: Amery Hung <amery.hung@bytedance.com>

Allow a struct_ops program to return a referenced kptr if the struct_ops
operator's return type is a struct pointer. To make sure the returned
pointer continues to be valid in the kernel, several constraints are
required:

1) The type of the pointer must matches the return type
2) The pointer originally comes from the kernel (not locally allocated)
3) The pointer is in its unmodified form

Implementation wise, a referenced kptr first needs to be allowed to _leak_
in check_reference_leak() if it is in the return register. Then, in
check_return_code(), constraints 1-3 are checked. During struct_ops
registration, a check is also added to warn about operators with
non-struct pointer return.

In addition, since the first user, Qdisc_ops::dequeue, allows a NULL
pointer to be returned when there is no skb to be dequeued, we will allow
a scalar value with value equals to NULL to be returned.

In the future when there is a struct_ops user that always expects a valid
pointer to be returned from an operator, we may extend tagging to the
return value. We can tell the verifier to only allow NULL pointer return
if the return value is tagged with MAY_BE_NULL.

Signed-off-by: Amery Hung <amery.hung@bytedance.com>
---
 kernel/bpf/bpf_struct_ops.c | 12 +++++++++++-
 kernel/bpf/verifier.c       | 36 ++++++++++++++++++++++++++++++++----
 2 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 68df8d8b6db3..8df5e8045d07 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -389,7 +389,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
 
 	for_each_member(i, t, member) {
-		const struct btf_type *func_proto;
+		const struct btf_type *func_proto, *ret_type;
 		void **stub_func_addr;
 		u32 moff;
 
@@ -426,6 +426,16 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 		if (!func_proto || bpf_struct_ops_supported(st_ops, moff))
 			continue;
 
+		if (func_proto->type) {
+			ret_type = btf_type_resolve_ptr(btf, func_proto->type, NULL);
+			if (ret_type && !__btf_type_is_struct(ret_type)) {
+				pr_warn("func ptr %s in struct %s returns non-struct pointer, which is not supported\n",
+					mname, st_ops->name);
+				err = -EOPNOTSUPP;
+				goto errout;
+			}
+		}
+
 		if (btf_distill_func_proto(log, btf,
 					   func_proto, mname,
 					   &st_ops->func_models[i])) {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index a0f51903e977..5bcf095e8d0c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10758,6 +10758,8 @@ record_func_key(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exit)
 {
 	struct bpf_verifier_state *state = env->cur_state;
+	enum bpf_prog_type type = resolve_prog_type(env->prog);
+	struct bpf_reg_state *reg = reg_state(env, BPF_REG_0);
 	bool refs_lingering = false;
 	int i;
 
@@ -10767,6 +10769,12 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
 	for (i = 0; i < state->acquired_refs; i++) {
 		if (state->refs[i].type != REF_TYPE_PTR)
 			continue;
+		/* Allow struct_ops programs to return a referenced kptr back to
+		 * kernel. Type checks are performed later in check_return_code.
+		 */
+		if (type == BPF_PROG_TYPE_STRUCT_OPS && !exception_exit &&
+		    reg->ref_obj_id == state->refs[i].id)
+			continue;
 		verbose(env, "Unreleased reference id=%d alloc_insn=%d\n",
 			state->refs[i].id, state->refs[i].insn_idx);
 		refs_lingering = true;
@@ -16405,13 +16413,14 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
 	const char *exit_ctx = "At program exit";
 	struct tnum enforce_attach_type_range = tnum_unknown;
 	const struct bpf_prog *prog = env->prog;
-	struct bpf_reg_state *reg;
+	struct bpf_reg_state *reg = reg_state(env, regno);
 	struct bpf_retval_range range = retval_range(0, 1);
 	enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
 	int err;
 	struct bpf_func_state *frame = env->cur_state->frame[0];
 	const bool is_subprog = frame->subprogno;
 	bool return_32bit = false;
+	const struct btf_type *reg_type, *ret_type = NULL;
 
 	/* LSM and struct_ops func-ptr's return type could be "void" */
 	if (!is_subprog || frame->in_exception_callback_fn) {
@@ -16420,10 +16429,26 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
 			if (prog->expected_attach_type == BPF_LSM_CGROUP)
 				/* See below, can be 0 or 0-1 depending on hook. */
 				break;
-			fallthrough;
+			if (!prog->aux->attach_func_proto->type)
+				return 0;
+			break;
 		case BPF_PROG_TYPE_STRUCT_OPS:
 			if (!prog->aux->attach_func_proto->type)
 				return 0;
+
+			if (frame->in_exception_callback_fn)
+				break;
+
+			/* Allow a struct_ops program to return a referenced kptr if it
+			 * matches the operator's return type and is in its unmodified
+			 * form. A scalar zero (i.e., a null pointer) is also allowed.
+			 */
+			reg_type = reg->btf ? btf_type_by_id(reg->btf, reg->btf_id) : NULL;
+			ret_type = btf_type_resolve_ptr(prog->aux->attach_btf,
+							prog->aux->attach_func_proto->type,
+							NULL);
+			if (ret_type && ret_type == reg_type && reg->ref_obj_id)
+				return __check_ptr_off_reg(env, reg, regno, false);
 			break;
 		default:
 			break;
@@ -16445,8 +16470,6 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
 		return -EACCES;
 	}
 
-	reg = cur_regs(env) + regno;
-
 	if (frame->in_async_callback_fn) {
 		/* enforce return zero from async callbacks like timer */
 		exit_ctx = "At async callback return";
@@ -16545,6 +16568,11 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
 	case BPF_PROG_TYPE_NETFILTER:
 		range = retval_range(NF_DROP, NF_ACCEPT);
 		break;
+	case BPF_PROG_TYPE_STRUCT_OPS:
+		if (!ret_type)
+			return 0;
+		range = retval_range(0, 0);
+		break;
 	case BPF_PROG_TYPE_EXT:
 		/* freplace program can return anything as its return value
 		 * depends on the to-be-replaced kernel func or bpf program.
-- 
2.47.1


  parent reply	other threads:[~2025-01-31 19:29 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-31 19:28 [PATCH bpf-next v3 00/18] bpf qdisc Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 01/18] bpf: Make every prog keep a copy of ctx_arg_info Amery Hung
2025-02-04  5:57   ` Eduard Zingerman
2025-01-31 19:28 ` [PATCH bpf-next v3 02/18] bpf: Support getting referenced kptr from struct_ops argument Amery Hung
2025-02-04  5:57   ` Eduard Zingerman
2025-01-31 19:28 ` [PATCH bpf-next v3 03/18] selftests/bpf: Test referenced kptr arguments of struct_ops programs Amery Hung
2025-02-04  5:58   ` Eduard Zingerman
2025-01-31 19:28 ` Amery Hung [this message]
2025-02-04  5:58   ` [PATCH bpf-next v3 04/18] bpf: Allow struct_ops prog to return referenced kptr Eduard Zingerman
2025-01-31 19:28 ` [PATCH bpf-next v3 05/18] selftests/bpf: Test returning referenced kptr from struct_ops programs Amery Hung
2025-02-04  5:58   ` Eduard Zingerman
2025-01-31 19:28 ` [PATCH bpf-next v3 06/18] bpf: Prepare to reuse get_ctx_arg_idx Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 07/18] bpf: Generalize finding member offset of struct_ops prog Amery Hung
2025-02-04  5:59   ` Eduard Zingerman
2025-01-31 19:28 ` [PATCH bpf-next v3 08/18] bpf: net_sched: Support implementation of Qdisc_ops in bpf Amery Hung
2025-02-04 22:18   ` Jakub Kicinski
2025-02-04 23:21     ` Amery Hung
2025-02-05  1:27       ` Jakub Kicinski
2025-02-05  4:13         ` Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 09/18] bpf: net_sched: Add basic bpf qdisc kfuncs Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 10/18] bpf: Search and add kfuncs in struct_ops prologue and epilogue Amery Hung
2025-02-04  5:59   ` Eduard Zingerman
2025-01-31 19:28 ` [PATCH bpf-next v3 11/18] bpf: net_sched: Add a qdisc watchdog timer Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 12/18] bpf: net_sched: Support updating bstats Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 13/18] bpf: net_sched: Support updating qstats Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 14/18] bpf: net_sched: Allow writing to more Qdisc members Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 15/18] libbpf: Support creating and destroying qdisc Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 16/18] selftests/bpf: Add a basic fifo qdisc test Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 17/18] selftests/bpf: Add a bpf fq qdisc to selftest Amery Hung
2025-01-31 19:28 ` [PATCH bpf-next v3 18/18] selftests/bpf: Test attaching bpf qdisc to mq and non root Amery Hung
2025-02-01  0:45   ` Amery Hung
2025-02-04  5:58   ` Eduard Zingerman
2025-02-04 18:17     ` Amery Hung

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250131192912.133796-5-ameryhung@gmail.com \
    --to=ameryhung@gmail.com \
    --cc=alexei.starovoitov@gmail.com \
    --cc=andrii@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=cong.wang@bytedance.com \
    --cc=daniel@iogearbox.net \
    --cc=edumazet@google.com \
    --cc=ekarani.silvestre@ccc.ufcg.edu.br \
    --cc=jhs@mojatatu.com \
    --cc=jiri@resnulli.us \
    --cc=kernel-team@meta.com \
    --cc=kuba@kernel.org \
    --cc=martin.lau@kernel.org \
    --cc=ming.lei@redhat.com \
    --cc=netdev@vger.kernel.org \
    --cc=sinquersw@gmail.com \
    --cc=stfomichev@gmail.com \
    --cc=toke@redhat.com \
    --cc=xiyou.wangcong@gmail.com \
    --cc=yangpeihao@sjtu.edu.cn \
    --cc=yepeilin.cs@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).