mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Emil Tsalapatis" <emil@etsalapatis.com>
To: "Pu Lehui" <pulehui@huaweicloud.com>, <bpf@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>,
	"Amery Hung" <ameryhung@gmail.com>,
	"Emil Tsalapatis" <emil@etsalapatis.com>,
	"Mykyta Yatsenko" <mykyta.yatsenko5@gmail.com>
Cc: "Alexei Starovoitov" <ast@kernel.org>,
	"Daniel Borkmann" <daniel@iogearbox.net>,
	"Andrii Nakryiko" <andrii@kernel.org>,
	"Eduard Zingerman" <eddyz87@gmail.com>,
	"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
	"Martin KaFai Lau" <martin.lau@linux.dev>,
	"Yonghong Song" <yonghong.song@linux.dev>,
	"Song Liu" <song@kernel.org>, "Jiri Olsa" <jolsa@kernel.org>,
	"Pu Lehui" <pulehui@huawei.com>
Subject: Re: [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog
Date: Wed, 22 Jul 2026 12:25:44 -0400	[thread overview]
Message-ID: <DK58NPCIRLIH.SMOILLVYTZ1Q@etsalapatis.com> (raw)
In-Reply-To: <20260722072326.1545677-3-pulehui@huaweicloud.com>

On Wed Jul 22, 2026 at 3:23 AM EDT, Pu Lehui wrote:
> From: Pu Lehui <pulehui@huawei.com>
>
> In bpf_mprog_link, the code does not check the link->type first before
> dereferencing link->prog->type. This missing validation allows a user to
> pass an abnormal non-netkit or non-tcx link via relative_fd. If doing
> BPF_LINK_UPDATE on the abnormal link, it can trigger a UAF issue.
>
> CPU0                                      CPU1
> netkit_link_prog_attach
> bpf_mprog_attach
> bpf_mprog_tuple_relative
> bpf_mprog_link
>   /* non-netkit or non-tcx link */
>   link = bpf_link_get_from_fd(id_or_fd);
>                                           BPF_LINK_UPDATE on relative link
>                                           ...
>                                           old_prog = xchg(&link->link.prog, new_prog);
>                                           bpf_prog_put(old_prog);
>   if (type && link->prog->type != type) <-- trigger UAF
>
> The reason for the UAF is that each subsystem provides its own
> protection for link->prog. Since there is no cross subsystem protection
> (if not considering the RCU of prog tear down), dereferencing the prog
> of an anchor link that does not belong to the current subsystem is not
> safe: it may have been freed. Therefore, we need to validate link->type
> to reject foreign anchors.
>
> Fix this by strictly validating link->type in bpf_mprog_link against the
> expected link type. mprog APIs is also adjusted to accept and pass down
> the expected link type. Meanwhile, add a comment explaining that when
> ptype == UNSPEC in bpf_mprog_detach, it acts as a wildcard.
>
> Fixes: 053c8e1f235d ("bpf: Add generic attach/detach/query API for multi-progs")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Reviewed-by: Amery Hung <ameryhung@gmail.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>

Unless I'm missing something, at the very least there's no need for the
extra argument in detach() since we pass UNSPEC to bpf_mprog_link() and
never use it otherwise.

Even for the attach case, aren't we required to provide a new link
for attachment, and so already have the ltype available to test
against?

> ---
>  drivers/net/netkit.c      | 13 ++++++-------
>  include/linux/bpf_mprog.h |  6 ++++--
>  kernel/bpf/mprog.c        | 23 ++++++++++++++---------
>  kernel/bpf/tcx.c          | 13 ++++++-------
>  4 files changed, 30 insertions(+), 25 deletions(-)
>
> diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
> index a3931cd82132..99ddf2befb23 100644
> --- a/drivers/net/netkit.c
> +++ b/drivers/net/netkit.c
> @@ -768,7 +768,7 @@ int netkit_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
>  	}
>  	ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
>  			       attr->attach_flags, attr->relative_fd,
> -			       attr->expected_revision);
> +			       attr->expected_revision, BPF_LINK_TYPE_NETKIT);
>  	if (!ret) {
>  		if (entry != entry_new) {
>  			netkit_entry_update(dev, entry_new);
> @@ -802,7 +802,7 @@ int netkit_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
>  		goto out;
>  	}
>  	ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
> -			       attr->relative_fd, attr->expected_revision);
> +			       attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_NETKIT);
>  	if (!ret) {
>  		if (!bpf_mprog_total(entry_new))
>  			entry_new = NULL;
> @@ -850,7 +850,7 @@ static int netkit_link_prog_attach(struct bpf_link *link, u32 flags,
>  	ASSERT_RTNL();
>  	entry = netkit_entry_fetch(dev, true);
>  	ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
> -			       id_or_fd, revision);
> +			       id_or_fd, revision, BPF_LINK_TYPE_NETKIT);
>  	if (!ret) {
>  		if (entry != entry_new) {
>  			netkit_entry_update(dev, entry_new);
> @@ -877,7 +877,7 @@ static void netkit_link_release(struct bpf_link *link)
>  		ret = -ENOENT;
>  		goto out;
>  	}
> -	ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
> +	ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_NETKIT);
>  	if (!ret) {
>  		if (!bpf_mprog_total(entry_new))
>  			entry_new = NULL;
> @@ -919,9 +919,8 @@ static int netkit_link_update(struct bpf_link *link, struct bpf_prog *nprog,
>  		ret = -ENOENT;
>  		goto out;
>  	}
> -	ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
> -			       BPF_F_REPLACE | BPF_F_ID,
> -			       link->prog->aux->id, 0);
> +	ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
> +			       link->prog->aux->id, 0, BPF_LINK_TYPE_NETKIT);
>  	if (!ret) {
>  		WARN_ON_ONCE(entry != entry_new);
>  		oprog = xchg(&link->prog, nprog);
> diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h
> index 0b9f4caeeb0a..1fbe1a923968 100644
> --- a/include/linux/bpf_mprog.h
> +++ b/include/linux/bpf_mprog.h
> @@ -321,12 +321,14 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>  		     struct bpf_mprog_entry **entry_new,
>  		     struct bpf_prog *prog_new, struct bpf_link *link,
>  		     struct bpf_prog *prog_old,
> -		     u32 flags, u32 id_or_fd, u64 revision);
> +		     u32 flags, u32 id_or_fd, u64 revision,
> +		     enum bpf_link_type expected_link_type);
>  
>  int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>  		     struct bpf_mprog_entry **entry_new,
>  		     struct bpf_prog *prog, struct bpf_link *link,
> -		     u32 flags, u32 id_or_fd, u64 revision);
> +		     u32 flags, u32 id_or_fd, u64 revision,
> +		     enum bpf_link_type expected_link_type);
>  
>  int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr __user *uattr,
>  		    struct bpf_mprog_entry *entry);
> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
> index 1394168062e8..b4a1b35ff569 100644
> --- a/kernel/bpf/mprog.c
> +++ b/kernel/bpf/mprog.c
> @@ -6,7 +6,7 @@
>  
>  static int bpf_mprog_link(struct bpf_tuple *tuple,
>  			  u32 id_or_fd, u32 flags,
> -			  enum bpf_prog_type type)
> +			  enum bpf_link_type type)
>  {
>  	struct bpf_link *link = ERR_PTR(-EINVAL);
>  	bool id = flags & BPF_F_ID;
> @@ -17,7 +17,7 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
>  		link = bpf_link_get_from_fd(id_or_fd);
>  	if (IS_ERR(link))
>  		return PTR_ERR(link);
> -	if (type && link->prog->type != type) {
> +	if (type && link->type != type) {
>  		bpf_link_put(link);
>  		return -EINVAL;
>  	}
> @@ -52,21 +52,22 @@ static int bpf_mprog_prog(struct bpf_tuple *tuple,
>  
>  static int bpf_mprog_tuple_relative(struct bpf_tuple *tuple,
>  				    u32 id_or_fd, u32 flags,
> -				    enum bpf_prog_type type)
> +				    enum bpf_link_type ltype,
> +				    enum bpf_prog_type ptype)
>  {
>  	bool link = flags & BPF_F_LINK;
>  	bool id = flags & BPF_F_ID;
>  
>  	memset(tuple, 0, sizeof(*tuple));
>  	if (link)
> -		return bpf_mprog_link(tuple, id_or_fd, flags, type);
> +		return bpf_mprog_link(tuple, id_or_fd, flags, ltype);
>  	/* If no relevant flag is set and no id_or_fd was passed, then
>  	 * tuple link/prog is just NULLed. This is the case when before/
>  	 * after selects first/last position without passing fd.
>  	 */
>  	if (!id && !id_or_fd)
>  		return 0;
> -	return bpf_mprog_prog(tuple, id_or_fd, flags, type);
> +	return bpf_mprog_prog(tuple, id_or_fd, flags, ptype);
>  }
>  
>  static void bpf_mprog_tuple_put(struct bpf_tuple *tuple)
> @@ -226,7 +227,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>  		     struct bpf_mprog_entry **entry_new,
>  		     struct bpf_prog *prog_new, struct bpf_link *link,
>  		     struct bpf_prog *prog_old,
> -		     u32 flags, u32 id_or_fd, u64 revision)
> +		     u32 flags, u32 id_or_fd, u64 revision,
> +		     enum bpf_link_type expected_link_type)
>  {
>  	struct bpf_tuple rtuple, ntuple = {
>  		.prog = prog_new,
> @@ -243,6 +245,7 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>  		return -EEXIST;
>  	ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
>  				       flags & ~BPF_F_REPLACE,
> +				       expected_link_type,
>  				       prog_new->type);
>  	if (ret)
>  		return ret;
> @@ -328,7 +331,8 @@ static int bpf_mprog_fetch(struct bpf_mprog_entry *entry,
>  int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>  		     struct bpf_mprog_entry **entry_new,
>  		     struct bpf_prog *prog, struct bpf_link *link,
> -		     u32 flags, u32 id_or_fd, u64 revision)
> +		     u32 flags, u32 id_or_fd, u64 revision,
> +		     enum bpf_link_type expected_link_type)
>  {
>  	struct bpf_tuple rtuple, dtuple = {
>  		.prog = prog,
> @@ -343,8 +347,9 @@ int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>  	if (!bpf_mprog_total(entry))
>  		return -ENOENT;
>  	ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd, flags,
> -				       prog ? prog->type :
> -				       BPF_PROG_TYPE_UNSPEC);
> +				       expected_link_type,
> +				       /* Use UNSPEC as wildcard when prog is NULL */
> +				       prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
>  	if (ret)
>  		return ret;
>  	if (dtuple.prog) {
> diff --git a/kernel/bpf/tcx.c b/kernel/bpf/tcx.c
> index 02db0113b8e7..f208cef13a98 100644
> --- a/kernel/bpf/tcx.c
> +++ b/kernel/bpf/tcx.c
> @@ -38,7 +38,7 @@ int tcx_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
>  	}
>  	ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
>  			       attr->attach_flags, attr->relative_fd,
> -			       attr->expected_revision);
> +			       attr->expected_revision, BPF_LINK_TYPE_TCX);
>  	if (!ret) {
>  		if (entry != entry_new) {
>  			tcx_entry_update(dev, entry_new, ingress);
> @@ -76,7 +76,7 @@ int tcx_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
>  		goto out;
>  	}
>  	ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
> -			       attr->relative_fd, attr->expected_revision);
> +			       attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_TCX);
>  	if (!ret) {
>  		if (!tcx_entry_is_active(entry_new))
>  			entry_new = NULL;
> @@ -152,7 +152,7 @@ static int tcx_link_prog_attach(struct bpf_link *link, u32 flags, u32 id_or_fd,
>  	if (!entry)
>  		return -ENOMEM;
>  	ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
> -			       id_or_fd, revision);
> +			       id_or_fd, revision, BPF_LINK_TYPE_TCX);
>  	if (!ret) {
>  		if (entry != entry_new) {
>  			tcx_entry_update(dev, entry_new, ingress);
> @@ -183,7 +183,7 @@ static void tcx_link_release(struct bpf_link *link)
>  		ret = -ENOENT;
>  		goto out;
>  	}
> -	ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
> +	ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_TCX);
>  	if (!ret) {
>  		if (!tcx_entry_is_active(entry_new))
>  			entry_new = NULL;
> @@ -229,9 +229,8 @@ static int tcx_link_update(struct bpf_link *link, struct bpf_prog *nprog,
>  		ret = -ENOENT;
>  		goto out;
>  	}
> -	ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
> -			       BPF_F_REPLACE | BPF_F_ID,
> -			       link->prog->aux->id, 0);
> +	ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
> +			       link->prog->aux->id, 0, BPF_LINK_TYPE_TCX);
>  	if (!ret) {
>  		WARN_ON_ONCE(entry != entry_new);
>  		oprog = xchg(&link->prog, nprog);


  reply	other threads:[~2026-07-22 16:25 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-22  7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
2026-07-22  7:23 ` [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
2026-07-27 23:01   ` Andrii Nakryiko
2026-07-28  1:44     ` Pu Lehui
2026-07-22  7:23 ` [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
2026-07-22 16:25   ` Emil Tsalapatis [this message]
2026-07-23  3:14     ` Pu Lehui
2026-07-28  1:56       ` Pu Lehui
2026-07-28  5:50         ` Emil Tsalapatis
2026-07-22  7:23 ` [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
2026-07-27 23:15   ` Andrii Nakryiko
2026-07-28  1:45     ` Pu Lehui
2026-07-22  7:23 ` [PATCH bpf v6 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
2026-07-27 23:17 ` [PATCH bpf v6 0/4] Fixes for bpf link update Andrii Nakryiko
2026-07-28  1:54   ` Pu Lehui

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=DK58NPCIRLIH.SMOILLVYTZ1Q@etsalapatis.com \
    --to=emil@etsalapatis.com \
    --cc=ameryhung@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=mykyta.yatsenko5@gmail.com \
    --cc=pulehui@huawei.com \
    --cc=pulehui@huaweicloud.com \
    --cc=song@kernel.org \
    --cc=yonghong.song@linux.dev \
    /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

all inboxes | Powered by JetHome®