Re: [PATCH bpf v4] bpf: Fix UAF when reading prog type in bpf_mprog_link
From: Kumar Kartikeya Dwivedi
Date: Mon Aug 03 2026 - 15:24:28 EST
On Mon Aug 3, 2026 at 9:00 PM CEST, Daniel Borkmann wrote:
> On 7/28/26 9:42 AM, Pu Lehui wrote:
>> From: Pu Lehui <pulehui@xxxxxxxxxx>
>>
>> In bpf_mprog_link, the code currently allows a user to pass an abnormal
>> non-netkit or non-tcx link via relative_fd. If a concurrent
>> BPF_LINK_UPDATE is performed on this abnormal link before dereferencing
>> link->prog->type in bpf_mprog_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.
>>
>> To resolve this, we access link->prog under RCU protection to safely
>> fetch the pointer and guarantee its lifetime during the type check.
>> 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@xxxxxxxxxx>
>> Reviewed-by: Emil Tsalapatis <emil@xxxxxxxxxxxxxxx>
>> Signed-off-by: Pu Lehui <pulehui@xxxxxxxxxx>
>> ---
>> v4:
>> - Access prog->type under rcu protection to simplify the repair logic,
>> and let unconditional detachment make sense when the bare prog or
>> link->prog being detached is NULL.
>> - Add Reviewed-by tag by Emil.
>> - Separate from patchset [0]. (Andrii)
>>
>> Link: https://lore.kernel.org/bpf/f87b53c0-8f00-45a6-82db-8242fa9b143f@xxxxxxxxxxxxxxx [0]
>>
>> v3: https://lore.kernel.org/bpf/20260722072326.1545677-3-pulehui@xxxxxxxxxxxxxxx
>> - BPF_F_LINK flag set means the relative id_or_fd is a link, not the object being
>> attached. We can not get the link while attach a bare prog with relative link.
>> So passing expected link type from callers of bpf_mprog_attach/detach. (Sashiko)
>> - Add comment to explain that why ptype == UNSPEC. (Emil)
>>
>> v2: https://lore.kernel.org/bpf/20260721041048.1394085-3-pulehui@xxxxxxxxxxxxxxx
>> - Improve commit msg for patch 2. (Amery)
>>
>> v1: https://lore.kernel.org/bpf/20260720134547.1289964-3-pulehui@xxxxxxxxxxxxxxx
>>
>> kernel/bpf/mprog.c | 17 +++++++++++++----
>> 1 file changed, 13 insertions(+), 4 deletions(-)
>>
>> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
>> index 1394168062e8..af3e6c1c6a1f 100644
>> --- a/kernel/bpf/mprog.c
>> +++ b/kernel/bpf/mprog.c
>> @@ -10,6 +10,8 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
>> {
>> struct bpf_link *link = ERR_PTR(-EINVAL);
>> bool id = flags & BPF_F_ID;
>> + bool type_mismatch = false;
>> + struct bpf_prog *prog;
>>
>> if (id)
>> link = bpf_link_by_id(id_or_fd);
>> @@ -17,13 +19,20 @@ 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) {
>> +
>> + rcu_read_lock();
>> + prog = READ_ONCE(link->prog);
>> + if (!prog || (type && prog->type != type))
>> + type_mismatch = true;
>> + rcu_read_unlock();
>
> Hm, what about progs under rcu_read_lock_trace cases, wouldn't the UAF
> still be there?
>
> Even though the diff is smaller, I'd kind of lean towards link->type
> testing since this addresses the underlying issue and avoids touching
> the prog completely.. do you want me to look into it and also add a
> BPF selftest to it as patch 2/2?
>
I think Pu's implication was that an RCU tasks trace GP would imply RCU gp, thus
using RCU read lock for protection in both cases would be sufficient for the
link's free path to wait for this reader. More context is available in [0]. I do
think this merits a comment for clarity, even in [0] I explicitly commented
about it everywhere even when it felt unnecessary.
I didn't closely follow this set of fixes (those in addition to this) yet but in
general, we also need to be careful about tracepoint BPF links. Those will only
wait for SRCU gp, which is not implied for rcu_read_lock() etc., so will need
their own distinct critical section. The fix pertaining to that was in [1].
[0]: https://lore.kernel.org/bpf/20260407162234.785270-1-memxor@xxxxxxxxx
[1]: https://lore.kernel.org/bpf/20260331211021.1632902-2-memxor@xxxxxxxxx
>> + if (type_mismatch) {
>> bpf_link_put(link);
>> return -EINVAL;
>> }
>>
>> tuple->link = link;
>> - tuple->prog = link->prog;
>> + tuple->prog = prog;
>> return 0;
>> }
>>
>> @@ -343,8 +352,8 @@ 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);
>> + /* Use UNSPEC as wildcard when prog is NULL */
>> + prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
>> if (ret)
>> return ret;
>> if (dtuple.prog) {