Skip to content

CVE-2026-80521: Verified Reproduction

CVE-2026-80521: Linux kernel af unix GC race-condition UAF — unix del edge frees dead SCC without unlinking scc entry

CVE-2026-80521 is verified against the affected target. Vulnerability class: Use-After-Free. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00358.

REPRO-2026-00358 Use-After-Free Sep 24, 2026 CVE entry .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
78m 32s
Tool calls
159
Spend
$4.48
01 · Overview

What Is CVE-2026-80521?

CVE-2026-80521 is a high-severity Use-After-Free vulnerability. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00358).

02 · Severity & CVSS

CVE-2026-80521 Severity

CVE-2026-80521 is rated high severity.

HIGH threat level
Weakness CWE-416 — Use After Free

High — serious impact or readily exploitable. Prioritize remediation.

How to Reproduce CVE-2026-80521

$ pruva-verify REPRO-2026-00358
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00358/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-80521

Memory corruption — reproduced
  • reached the target end-to-end
  • crash observed
  • on the real production code path
  • high confidence
Trigger

unprivileged AF_UNIX datagram sockets: SCM_RIGHTS fd passing (sendmsg of sk-B's fd to sk-B from sk-X) raced with close() of the A<->B socket pair forming a dead SCC

Attack chain
  1. unix_dgram_sendmsg
  2. unix_add_edges (publishes B
  3. B edge) || close(A),close(B)
  4. unix_gc
  5. unix_walk_scc_fast
  6. unix_scc_dead iterates freed unix_vertex via stale scc_entry
How the agent worked 330 events · 159 tool calls · 1h 18m
1h 18mDuration
159Tool calls
56Reasoning steps
330Events
11Dead-ends
Agent activity over 1h 18m
Policy
1
Support
11
Repro
148
Judge
34
Variant
131
Verify
1
0:0078:17

Root Cause and Exploit Chain for CVE-2026-80521

Versions: kernels containing the SCC-based GC rewrite

The Linux kernel AF_UNIX garbage collector (net/unix/garbage.c), rewritten in commit 4090fa373f0e ("af_unix: Replace garbage collection algorithm."), keeps per-SCC vertex linkage in unix_vertex.scc_entry. When unix_del_edge() drops the last outgoing edge of a vertex it frees that vertex (moves it to the scm_fp_list free list, later kfree()d) without unlinking scc_entry. A race between (2-1) sendmsg() passing an AF_UNIX fd with SCM_RIGHTS and (2-2) close() of the sockets forming a dead SCC lets the GC judge SCC {A,B} dead during the tiny window in unix_dgram_sendmsg() where unix_add_edges() has already published the new B->B edge but skb_queue_tail() has not yet queued the skb carrying it. B survives (its not-yet-queued fpl cannot be collected), A's vertex is freed with its scc_entry still linked into {A,B}'s SCC chain, and a later GC pass — forced into unix_walk_scc_fast() by a still-live cyclic SCC {X} — iterates B's scc_entry chain into the freed A vertex: kernel use-after-free.

  • Component: Linux kernel, AF_UNIX socket garbage collector (net/unix/garbage.c).
  • Affected versions: kernels containing the SCC-based GC rewrite (4090fa373f0e, upstream 6.x era and stable backports); fixed upstream by 594d905195024b228c962627ae5ae7c17bd582a4 ("af_unix: Unlink scc_entry in unix_del_edge()"), backported e.g. e3702470ced94fad74d71e2232f022d2eb752a6d.
  • Risk: local unprivileged attacker (plain AF_UNIX sockets + SCM_RIGHTS fd passing, no privileges, no namespaces) triggers kernel memory corruption. Disclosed as potential LPE / container escape or DoS; CVSS 7.8 (NVD).

Impact Parity

  • Disclosed/claimed maximum impact: kernel memory corruption (UAF), potential LPE.
  • Reproduced impact from this run: (updated after runtime; see Evidence) KASAN-detected use-after-free in the unix_walk_scc_fast() / unix_scc_dead() GC path on the vulnerable kernel, with the fixed kernel as negative control.
  • Parity: full for the claimed memory_corruption impact class (no privilege-escalation chain is claimed or demonstrated here).

Root Cause

unix_del_edge() (vulnerable tree, net/unix/garbage.c):

static void unix_del_edge(struct scm_fp_list *fpl, struct unix_edge *edge)
{
        ...
        if (!vertex->out_degree) {
                edge->predecessor->vertex = NULL;
                list_move_tail(&vertex->entry, &fpl->vertices);
                /* missing: list_del(&vertex->scc_entry); */
        }
}

The SCC lists threaded through scc_entry are the persistent grouping produced by __unix_walk_scc() and consumed by unix_walk_scc_fast() / unix_scc_dead() / unix_collect_skb(). Freeing a vertex while its scc_entry remains linked leaves a dangling list node; the next fast SCC walk does list_add(&scc, &vertex->scc_entry) / reverse iteration over the stale chain and dereferences freed memory.

Race window (confirmed by source inspection of the vulnerable tree): in unix_dgram_sendmsg(), scm_stat_add()unix_add_edges() publishes the edge under unix_gc_lock, and only afterwards is the skb queued by skb_queue_tail(). A concurrent unix_gc() that takes unix_gc_lock between those two operations sees the B->B edge but not the skb, so unix_vertex_dead(B) (refcount == out_degree) still judges SCC {A,B} dead; unix_collect_skb() cannot collect the un-queued skb, so only A's vertex is freed — with the stale scc_entry.

Reproduction Steps

  1. bundle/repro/reproduction_steps.sh (self-contained; uses the prepared project cache when available).
  2. The script:
    • fetches torvalds/linux at the fix commit (depth=2) and resolves the vulnerable parent 315f4bd234b3b8a3ed3a71fd4c53b110cf373720;
    • verifies the fix hunk is absent in the vulnerable checkout and present in the fixed checkout;
    • builds two KASAN kernels (CONFIG_KASAN=y, outline mode): vulnerable parent and fixed commit;
    • builds a static initramfs whose /init (root) spawns an unprivileged uid-1000 trigger that:
      • creates persistent socket X holding its own fd (live cyclic SCC keeps the GC graph in UNIX_GRAPH_CYCLIC state, forcing unix_walk_scc_fast());
      • inflates user->unix_inflight past UNIX_INFLIGHT_SANE_USER (2024) so every SCM_RIGHTS send schedules GC; a hammer thread keeps flush_work(unix_gc_work) hot;
      • per iteration creates datagram sockets A,B, forms the A<->B cycle, then races sendmsg(X, fd=B -> B) against close(A); close(B) from two pinned threads;
    • boots QEMU/KVM (4 vCPUs+) twice on the vulnerable kernel and twice on the fixed kernel; the init scans dmesg for BUG: KASAN / use-after-free touching unix_walk_scc* / unix_scc_dead / unix_collect_skb / unix_vertex_dead / unix_del_edge and prints REPRO_UAF_DETECTED or REPRO_CLEAN before poweroff.
  3. Expected evidence: REPRO_UAF_DETECTED with a KASAN splat naming the af_unix GC path on the vulnerable kernel, and REPRO_CLEAN on the fixed kernel.

Evidence

  • bundle/logs/reproduction_steps.log — full driver log.
  • bundle/logs/vm_vuln_attempt{1,2}.log — serial consoles, vulnerable kernel.
  • bundle/logs/vm_fixed_attempt{1,2}.log — serial consoles, fixed kernel (negative control).
  • bundle/repro/kasan_evidence.txt — extracted KASAN/unix GC splat lines.
  • bundle/repro/runtime_manifest.json — runtime evidence manifest with per-artifact SHA-256 and target identity (git:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux@315f4bd234b3b8a3ed3a71fd4c53b110cf373720).
  • Environment: QEMU 8.2.2 (KVM), x86_64, KASAN outline, 6 vCPUs, 4 GiB RAM, unprivileged uid 1000 in-guest.

Key excerpt from bundle/logs/vm_vuln_attempt1.log (identical pattern in attempt 2, kernel 7.2.0-rc6-g315f4bd234b3):

[trigger] running as uid=1000 for 120 seconds
[trigger] inflight inflation done, hammering GC
BUG: KASAN: slab-use-after-free in unix_gc+0x808/0x8f0
Write of size 8 at addr ffff888103eea6a8 by task kworker/u24:3/84
Workqueue: events_unbound unix_gc
...
Allocated by task 102:
  unix_prepare_fpl+0x62/0x1d0
  unix_scm_to_skb+0x16f/0x1e0
  unix_dgram_sendmsg+0x1ff/0xbc0
  ____sys_sendmsg+0x4e0/0x500
Freed by task 84:
  kfree+0x121/0x380
  unix_destroy_fpl+0xb7/0xf0
  unix_wfree+0xb2/0x120
  sk_skb_reason_drop+0x64/0x200
  unix_gc+0x663/0x8f0
The buggy address belongs to the object at ffff888103eea680
 which belongs to the cache kmalloc-96 of size 96   <-- struct unix_vertex
Oops: general protection fault, probably for non-canonical address
 0xdead000000000108: 0000 [#1] SMP KASAN NOPTI      <-- LIST_POISON1+8
RIP: 0010:unix_scc_dead+0x8c/0x250                  <-- stale scc_entry walk
Workqueue: events_unbound unix_gc
REPRO_UAF_DETECTED kasan=1 unix_gc_path=1

The allocation trace shows the victim unix_vertex was allocated by the unprivileged trigger's own sendmsg() (unix_prepare_fpl), freed by the GC itself (unix_destroy_fpl via skb purge in unix_gc), and then written to (offset 40 = scc_entry list splice) and walked (unix_scc_dead dereferences LIST_POISON1-poisoned linkage) by a subsequent GC pass — exactly the CVE-2026-80521 mechanism. Fixed-kernel control runs print REPRO_CLEAN kasan=0 unix_hit=0 oops=0 after the identical stress.

Recommendations / Next Steps

  • Fix: unlink scc_entry before freeing the vertex in unix_del_edge() (upstream 594d9051...); the fix is exactly one line and is already backported to stable series.
  • Upgrade guidance: any kernel carrying the af_unix SCC GC rewrite without this fix is exposed; apply stable updates containing 594d9051 / e3702470 (and the listed stable backports).
  • Testing: stress AF_UNIX SCM_RIGHTS fd-passing with concurrent close under KASAN; syzbot-style fuzzing of unix_gc races.

Additional Notes

  • The script is idempotent: fetched source, kernel builds, and the initramfs are cached under the project cache directory and re-used on re-run (SHA-marker guarded); QEMU evidence is regenerated every run.
  • The race window is a few instructions wide; KASAN instrumentation and the synchronous flush_work() GC path widen it. Attempt counts and stress duration (VULN_SECS/FIX_SECS env overrides) may need tuning on slower hosts (TCG fallback when /dev/kvm is unavailable is supported but slower).

CVE-2026-80521 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:003:02
0:00
session startedaccounts/fireworks/models/kimi-k3 · CVE-2026-80521 · REPRO-20
0:07
0:09
web search
0:21
0:22
0:58
0:58
extract_facts
no facts extracted
1:00
1:00
supportclaim_contract
1:14
1:15
1:15
1:18
1:18
1:22
1:22
1:29
$ls -la /data/pruva/project-cache/.run-views/bc0c95d1852115734884b96e5c8b70790606d70dbafffd3c60ae240107c0930f/repo-mirrors/ 2>/dev/null; cat /data/pruva/project-cache/.run-views/bc0c95d1852115734884b96e5c8b70790606d70dbafffd3c60ae240107c0930f/.pruva/cache_manifest.json 2>/dev/null; echo ===; ls bundle/learning bundle/support bundle/repro 2>/dev/null
total 8
drwx------ 2 pruva pruva 4096 Sep 23 13:26 .
drwx------ 4 pruva pruva 4096 Sep 23 13:26 ..
{
  "schema_version": 1,
  "entries": [
    {
      "path": "repo-mirrors",
1:41
1:42
$ls /data/pruva/project-cache/ 2>/dev/null | head -30; echo ===; which qemu-system-x86_64; qemu-system-x86_64 --version 2>/dev/null | head -1; echo ===; nproc; free -g | head -2; df -h /data /tmp | tail -3; echo ===; gcc --version | head -1; ls /boot 2>/dev/null | head
1.1s
{"cwd":"/data/pruva/runs/81b3bf3f-e693-4d7d-a041-181f1829c771","duration_ms":1067,"exit_code":0,"stderr":"","stdout":"empty-cache-generation-v1\nproject-0cc7526f-59a8-44be-817c-85eda342467d-generation-abf16512e4554428ac98effbfc9ea033\nproject-1b32eb77-2993-4f6d-b197-c6547e683bde-generation-bd818fee90e641f2a228463cb3a3f5ce\nproject-25efce6a-b32f-4f70-b6f7-c782b8f8512e-generation-06e8c3e823d34d5da0e09a625474f5c8\nproject-29067a79-6285-4b08-8e77-3fdf98e4e5d0-generation-3d64103755f54ccc90e07ee0d19c55a0\nproject-3439cc92-7d8a-4f3f-88c0-1a192a0363fc-generation-3ba17638076741b6a33c6c73e766a24e\nproject-362de50f-936d-4d7b-b5c6-38cf6e7ac143-generation-24a738e81f104a708b4e6a7df80bdc9c\nproject-3b4963e4-09b2-44ed-ae35-cf85543e2c6e-generation-0cc484837f3f427c926ad2fe5c1f1e5c\nproject-3bb30334-dfa8-4ffb-b1d2-f4c3350fb9af-generation-e3d0fa5f08ab4e01a7808a8830028ec3\nproject-470a16ed-e666-49df-b897-3d8193522fd3-generation-d752fdbceb6c4d7d9555319d316d6e25\nproject-4f9f8aca-cebb-47cf-b225-e508949c76d0-generation-14788dc7bd374cf3bfc2257b14b0e65b\nproject-55fa7b9c-5351-43fb-a3af-30af408a6666-generation-a7ad3e9c114f48ef9b4bca08efb6b398\nproject-688af86e-dc4c-4d20-bb02-ccdf8da2c7d0-generation-a0afd4a93… [truncated]
1:44
web search
2:16
2:16
$ls -l /dev/kvm 2>&1; echo ===; curl -sL "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux/patch/?id=594d905195024b228c962627ae5ae7c17bd582a4" -o /tmp/fix_upstream.patch && head -100 /tmp/fix_upstream.patch
0.2s
{"cwd":"/data/pruva/runs/81b3bf3f-e693-4d7d-a041-181f1829c771","duration_ms":174,"exit_code":0,"stderr":"","stdout":"crw-rw---- 1 root kvm 10, 232 Sep 18 23:13 /dev/kvm\n===\n<!DOCTYPE html>\n<html lang='en'>\n<head>\n<title></title>\n<meta name='generator' content='cgit 1.3.1-korg'/>\n<meta name='robots' content='noindex, nofollow'/>\n<link rel='stylesheet' type='text/css' href='/cgit-data/cgit.css'/>\n<script type='text/javascript' src='/cgit-data/cgit.js'></script>\n<link rel='shortcut icon' href='/favicon.ico'/>\n</head>\n<body>\n<div id='cgit'><table id='header'>\n<tr>\n<td class='logo' rowspan='2'><a href='/'><img src='/cgit-data/cgit.png' alt='cgit logo'/></a></td>\n<td class='main'>Kernel.org git repositories</td></tr>\n<tr><td class='sub'>Git repositories hosted at kernel.org</td></tr></table>\n<table class='tabs'><tr><td>\n<a class='active' href='/'>index</a></td><td class='form'><form method='get' action='/pub/scm/linux/kernel/git/torvalds/linux/patch/'>\n<input type='search' name='q' size='10' value=''/>\n<input type='submit' value='search'/>\n</form></td></tr></table>\n<div class='content'><div class='error'>No repositories found</div>\n</div> <!-- class=content -->\n<… [truncated]
2:23
2:23
$curl -sL "https://github.com/torvalds/linux/commit/594d905195024b228c962627ae5ae7c17bd582a4.patch" -o /tmp/fix_upstream.patch && wc -l /tmp/fix_upstream.patch && head -120 /tmp/fix_upstream.patch
0.4s
{"cwd":"/data/pruva/runs/81b3bf3f-e693-4d7d-a041-181f1829c771","duration_ms":373,"exit_code":0,"stderr":"","stdout":"64 /tmp/fix_upstream.patch\nFrom 594d905195024b228c962627ae5ae7c17bd582a4 Mon Sep 17 00:00:00 2001\nFrom: Kuniyuki Iwashima <kuniyu@google.com>\nDate: Tue, 4 Aug 2026 00:21:54 +0000\nSubject: [PATCH] af_unix: Unlink scc_entry in unix_del_edge().\n\nKyle Zeng reported that GC could free a dead SCC partially.\n\nThe scenario is as follows:\n\n   1) Create two SCCs:\n\n       X -.   A <-> B\n       ^--'\n\n   2) Run the following concurrently:\n\n      2-1) send() sk-B to sk-B from sk-X\n      2-2) close() both A and B\n\nAt 2-1), there is a small window where unix_add_edges()\npublishes a new edge (B <-> B) to GC but its skb is not queued\nby skb_queue_tail().\n\nIf 2-2) completes before skb_queue_tail() and GC is triggered,\nit judges A <-> B as dead, but B is not freed because GC cannot\ncollect the not-yet-queued skb holding the B <-> B edge.\n\n       X -.   A <-> B -. This edge is visible\n       ^--'         ^..'  but skb is not\n\nThis itself is not a problem since the next GC run will judge\nB as dead as well and free it finally.\n\n       X -.   A <.> B -.\n  … [truncated]
3:01
3:01
3:02
$id; ls -l /dev/kvm; groups; echo ===; apt list --installed 2>/dev/null | grep -Ei 'busybox|flex|bison|libelf|libssl-dev|bc ' | head; which flex bison bc pahole 2>&1
0.8s
uid=1000(pruva) gid=1001(pruva) groups=1001(pruva),27(sudo),993(kvm),1000(docker)
crw-rw---- 1 root kvm 10, 232 Sep 18 23:13 /dev/kvm
pruva sudo kvm docker
===
bison/noble-updates,noble-security,now 2:3.8.2+dfsg-1ubuntu0.24.04.1 amd64 [installed]
busybox-initramfs/noble-updates,noble-security,now 1:1.36.1-6ubuntu3.1 amd64 [installed,automatic]
busybox-static/noble-updates,noble-security,now 1:1.36.1-6ubuntu3.1 amd64 [installed,automatic]
flex/noble,now 2.6.4-8.2build1 amd64 [installed]
08 · How to Fix

How to Fix CVE-2026-80521

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-80521 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-80521

Is CVE-2026-80521 exploitable?

Yes. Pruva independently reproduced CVE-2026-80521 and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00358).

How severe is CVE-2026-80521?

CVE-2026-80521 is rated high severity.

What type of vulnerability is CVE-2026-80521?

CVE-2026-80521 is classified as CWE-416 (Use After Free), a Use-After-Free vulnerability.

How can I reproduce CVE-2026-80521?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-80521 reproduction verified?

Yes. Pruva reproduced CVE-2026-80521 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-80521

Authoritative sources for CVE-2026-80521 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.