BPF List
 help / color / mirror / Atom feed
From: Martin KaFai Lau <martin.lau@linux.dev>
To: thinker.li@gmail.com
Cc: sinquersw@gmail.com, kuifeng@meta.com, bpf@vger.kernel.org,
	ast@kernel.org, song@kernel.org, kernel-team@meta.com,
	andrii@kernel.org
Subject: Re: [PATCH bpf-next v4 2/3] bpf: Check cfi_stubs before registering a struct_ops type.
Date: Wed, 21 Feb 2024 10:25:22 -0800	[thread overview]
Message-ID: <8e6e79d6-e003-446b-bc36-b6a4500f802b@linux.dev> (raw)
In-Reply-To: <20240221075213.2071454-3-thinker.li@gmail.com>

On 2/20/24 11:52 PM, thinker.li@gmail.com wrote:
> From: Kui-Feng Lee <thinker.li@gmail.com>
> 
> Recently, cfi_stubs were introduced. However, existing struct_ops types
> that are not in the upstream may not be aware of this, resulting in kernel
> crashes. By rejecting struct_ops types that do not provide cfi_stubs during
> registration, these crashes can be avoided.
> 
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
>   kernel/bpf/bpf_struct_ops.c | 17 +++++++++++++++++
>   1 file changed, 17 insertions(+)
> 
> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> index 0d7be97a2411..c1c502caae08 100644
> --- a/kernel/bpf/bpf_struct_ops.c
> +++ b/kernel/bpf/bpf_struct_ops.c
> @@ -302,6 +302,11 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>   	}
>   	sprintf(value_name, "%s%s", VALUE_PREFIX, st_ops->name);
>   
> +	if (!st_ops->cfi_stubs) {
> +		pr_warn("struct %s has no cfi_stubs\n", st_ops->name);
> +		return -EINVAL;
> +	}
> +
>   	type_id = btf_find_by_name_kind(btf, st_ops->name,
>   					BTF_KIND_STRUCT);
>   	if (type_id < 0) {
> @@ -339,6 +344,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>   
>   	for_each_member(i, t, member) {
>   		const struct btf_type *func_proto;
> +		u32 moff;
>   
>   		mname = btf_name_by_offset(btf, member->name_off);
>   		if (!*mname) {
> @@ -361,6 +367,17 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>   		if (!func_proto)
>   			continue;
>   
> +		moff = __btf_member_bit_offset(t, member) / 8;
> +		err = st_ops->check_member ?
> +			st_ops->check_member(t, member, NULL) : 0;

I don't think it is necessary to make check_member more complicated by taking
NULL prog. The struct_ops implementer then needs to handle this extra NULL
prog case.

Have you thought about Alexei's earlier suggestion in v3 to reuse the NULL
member in cfi_stubs to flag unsupported member and remove the unsupported_ops[]
from bpf_tcp_ca.c?

If the verifier can consistently reject loading unsupported bpf prog, it will
not reach the bpf_struct_ops_map_update_elem and then hits the NULL member
in cfi_stubs during map_update_elem.

Untested code:

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 011d54a1dc53..c57cb0e2a8a7 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20370,6 +20370,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
  	u32 btf_id, member_idx;
  	struct btf *btf;
  	const char *mname;
+	u32 moff;
  
  	if (!prog->gpl_compatible) {
  		verbose(env, "struct ops programs must have a GPL compatible license\n");
@@ -20417,11 +20418,18 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
  		return -EINVAL;
  	}
  
+	moff = __btf_member_bit_offset(t, member) / 8;
+	if (!*(void **)(st_ops->cfi_stubs + moff)) {
+		verbose(env, "attach to unsupported member %s of struct %s\n",
+			mname, st_ops->name);
+		return -ENOTSUPP;
+	}
+
  	if (st_ops->check_member) {
  		int err = st_ops->check_member(t, member, prog);
  
  		if (err) {
-			verbose(env, "attach to unsupported member %s of struct %s\n",
+			verbose(env, "cannot attach to member %s of struct %s\n",
  				mname, st_ops->name);
  			return err;
  		}
diff --git a/net/ipv4/bpf_tcp_ca.c b/net/ipv4/bpf_tcp_ca.c
index 7f518ea5f4ac..bcb1fcd00973 100644
--- a/net/ipv4/bpf_tcp_ca.c
+++ b/net/ipv4/bpf_tcp_ca.c
@@ -14,10 +14,6 @@
  /* "extern" is to avoid sparse warning.  It is only used in bpf_struct_ops.c. */
  static struct bpf_struct_ops bpf_tcp_congestion_ops;
  
-static u32 unsupported_ops[] = {
-	offsetof(struct tcp_congestion_ops, get_info),
-};
-
  static const struct btf_type *tcp_sock_type;
  static u32 tcp_sock_id, sock_id;
  static const struct btf_type *tcp_congestion_ops_type;
@@ -45,18 +41,6 @@ static int bpf_tcp_ca_init(struct btf *btf)
  	return 0;
  }
  
-static bool is_unsupported(u32 member_offset)
-{
-	unsigned int i;
-
-	for (i = 0; i < ARRAY_SIZE(unsupported_ops); i++) {
-		if (member_offset == unsupported_ops[i])
-			return true;
-	}
-
-	return false;
-}
-
  static bool bpf_tcp_ca_is_valid_access(int off, int size,
  				       enum bpf_access_type type,
  				       const struct bpf_prog *prog,
@@ -248,15 +232,6 @@ static int bpf_tcp_ca_init_member(const struct btf_type *t,
  	return 0;
  }
  
-static int bpf_tcp_ca_check_member(const struct btf_type *t,
-				   const struct btf_member *member,
-				   const struct bpf_prog *prog)
-{
-	if (is_unsupported(__btf_member_bit_offset(t, member) / 8))
-		return -ENOTSUPP;
-	return 0;
-}
-
  static int bpf_tcp_ca_reg(void *kdata)
  {
  	return tcp_register_congestion_control(kdata);
@@ -350,7 +325,6 @@ static struct bpf_struct_ops bpf_tcp_congestion_ops = {
  	.reg = bpf_tcp_ca_reg,
  	.unreg = bpf_tcp_ca_unreg,
  	.update = bpf_tcp_ca_update,
-	.check_member = bpf_tcp_ca_check_member,
  	.init_member = bpf_tcp_ca_init_member,
  	.init = bpf_tcp_ca_init,
  	.validate = bpf_tcp_ca_validate,
-- 
2.34.1




> +
> +		if (!err && !*(void **)(st_ops->cfi_stubs + moff)) {
> +			pr_warn("member %s in struct %s has no cfi stub function\n",
> +				mname, st_ops->name);
> +			err = -EINVAL;
> +			goto errout;
> +		}
> +
>   		if (btf_distill_func_proto(log, btf,
>   					   func_proto, mname,
>   					   &st_ops->func_models[i])) {


  reply	other threads:[~2024-02-21 18:25 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-02-21  7:52 [PATCH bpf-next v4 0/3] Check cfi_stubs before registering a struct_ops type thinker.li
2024-02-21  7:52 ` [PATCH bpf-next v4 1/3] bpf, net: allow passing NULL prog to check_member thinker.li
2024-02-21  7:52 ` [PATCH bpf-next v4 2/3] bpf: Check cfi_stubs before registering a struct_ops type thinker.li
2024-02-21 18:25   ` Martin KaFai Lau [this message]
2024-02-21 23:13     ` Kui-Feng Lee
2024-02-22  1:11       ` Kui-Feng Lee
2024-02-21  7:52 ` [PATCH bpf-next v4 3/3] selftests/bpf: Test case for lacking CFI stub functions thinker.li

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=8e6e79d6-e003-446b-bc36-b6a4500f802b@linux.dev \
    --to=martin.lau@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=kernel-team@meta.com \
    --cc=kuifeng@meta.com \
    --cc=sinquersw@gmail.com \
    --cc=song@kernel.org \
    --cc=thinker.li@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