---
id: "CVE-2026-98642"
kind: "vulnerability"
aliases: ["CVE-2026-98642"]
title: "Linux kernel: BPF memory allocator race lets a use-after-free occur"
severity: "unknown"
cvss: null
epss: null
exploited: false
status: "open"
category: "infrastructure"
products: []
ecosystems: []
tags: ["bpf", "infrastructure", "linux-kernel", "self-hosted"]
not_affected_if: ["unknown"]
published_at: "2026-10-18T16:17:17+00:00"
first_seen_at: "2026-10-23T21:57:16+00:00"
updated_at: "2026-10-25T05:20:53+00:00"
sources: [{"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-98642", "first_seen_at": "2026-10-23T21:57:16+00:00"}]
---

# Linux kernel: BPF memory allocator race lets a use-after-free occur

Concurrent consumption of BPF memalloc lists (waiting_for_gp_ttrace and free_by_rcu_ttrace) can read freed nodes, a use-after-free detected by KASAN. The fix adds a raw spinlock to serialize access to those lists.

_Summary, check and fix written by a model from the sources; labelled as such._

## How to check

- `uname -r` to see the running kernel version, then compare it against the fixed kernel listed in the NVD record
- unknown

## Fix

- Update the Linux kernel to a release that includes the upstream commit fixing the BPF memalloc race (exact version unknown from the sources)

## Timeline

- 2026-10-23T21:57:16+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-98642

## From the sources

In the Linux kernel, the following vulnerability has been resolved:

bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk

Syzkaller repeatedly triggered UAF splats related to nodes in
waiting_for_gp_ttrace within the bpf memalloc:

BUG: KASAN: slab-use-after-free in llist_del_first+0x85/0x110 lib/llist.c:61
Read of size 8 at addr ffff8881572cd080 by task syz.4.470/5112
 ...
 llist_del_first+0x85/0x110 lib/llist.c:61
 alloc_bulk+0x193/0x460 kernel/bpf/memalloc.c:229
 bpf_mem_refill+0x386/0x560 kernel/bpf/memalloc.c:436

Freed by task 14:
 ...
 __free_rcu kernel/bpf/memalloc.c:281 [inline]
 __free_rcu_tasks_trace+0x48/0xd0 kernel/bpf/memalloc.c:291
 rcu_tasks_invoke_cbs+0x1ec/0x3e0 kernel/rcu/tasks.h:571
 rcu_tasks_one_gp+0x13d/0x220 kernel/rcu/tasks.h:621
 rcu_tasks_kthread+0xf3/0x120 kernel/rcu/tasks.h:651

The reason is that the UAF occurs after the RCU Tasks Trace GP expires:
when the __free_rcu() callback runs, there is no synchronization
protecting llist_del_all() against concurrent alloc_bulk() operating on
waiting_for_gp_ttrace, leading to the race condition below:

CPU0                                           CPU1
                                               __free_rcu (RCU Tasks Trace callback)
alloc_bulk
  llist_del_first(&c->waiting_for_gp_ttrace)
    entry = smp_load_acquire(&head->first);
    do {
      if (entry == NULL)
        return NULL;
                                               free_all(llist_del_all(&c->waiting_for_gp_ttrace))
                                                 llist_for_each_safe(pos, t, llnode)
                                                   free_one(pos);
      next = READ_ONCE(entry->next); <-- trigger UAF
    } while (!try_cmpxchg(&head->first, &entry, next));

In addition, there is also a theoretical race condition on the
free_by_rcu_ttrace list. This race requires two preconditions: an
in-flight Tasks Trace GP keeping c->call_rcu_ttrace_in_progress == 1,
and concurrent cross-CPU frees repopulating c->free_by_rcu_ttrace with
new nodes. Under these conditions, the following scenario triggers UAF:

// CPU0
// irq work is still busy (on PREEMPT_RT)
alloc_bulk()
  llist_del_first(&c->free_by_rcu_ttrace)
    entry = smp_load_acquire(&head->first);
    do {
      if (entry == NULL)
        return NULL;

        // CPU1
        bpf_mem_alloc_destroy()
          WRITE_ONCE(c->draining, true)
          // wait for CPU0
          irq_work_sync()

                // CPU2
                do_call_rcu_ttrace(tgt(CPU0))
                  if (c->draining) {
                    llist_del_all(&c->free_by_rcu_ttrace)
                    free_all()
                  }

// CPU0 continue
      next = READ_ONCE(entry->next); <-- trigger UAF
    while (!try_cmpxchg(&head->first, &entry, next));

Fix this by introducing a raw spinlock to synchronize the concurrent
consumption on waiting_for_gp_ttrace and free_by_rcu_ttrace.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
