GHSA-CQMM-FV72-P7F7
Vulnerability from github – Published: 2026-08-22 18:30 – Updated: 2026-08-22 18:30In the Linux kernel, the following vulnerability has been resolved:
bpf: tcp: Fix use-after-free in bpf_iter_tcp_established_batch()
reqsk_queue_hash_req() publishes a TCP_NEW_SYN_RECV request_sock onto the ehash chain, drops the bucket lock, and only afterwards sets rsk_refcnt to 3.
Lockless readers such as __inet_lookup_established() handle this with refcount_inc_not_zero(), but bpf_iter_tcp_established_batch() uses plain sock_hold() while holding the bucket lock, on the assumption that the lock guarantees sk_refcnt > 0. That assumption does not hold for request_sock:
CPU 0 CPU 1 ----- ----- tcp_conn_request() reqsk_queue_hash_req() inet_ehash_insert(req) spin_lock(bucket) __sk_nulls_add_node_rcu(req) // rsk_refcnt == 0 spin_unlock(bucket) bpf_iter_tcp_established_batch() spin_lock(bucket) sock_hold(req) <-- addition on 0 spin_unlock(bucket) refcount_set(&req->rsk_refcnt, 3) // clobbers saturated value
which surfaces as:
refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x48/0x90, CPU#1 Call Trace: bpf_iter_tcp_established_batch+0x14e/0x170 bpf_iter_tcp_batch+0x53/0x200 bpf_iter_tcp_seq_next+0x27/0x70 bpf_seq_read+0x107/0x410 vfs_read+0xb9/0x380
The iterator's stolen reference is lost when the publishing CPU's refcount_set() overwrites the count, leaving the socket one reference short. When the last legitimate owner drops its reference the reqsk is freed while still reachable, leading to use-after-free.
This reproduces in seconds with tcp_syncookies=0, a handful of threads doing connect()/close() to a local listener while others read an iter/tcp link in a tight loop.
Use refcount_inc_not_zero() and skip the socket on failure. A skipped socket is still part of the bucket, so keep counting it in expected. The reallocations are sized from expected, and a request sock whose refcount gets published while the lock is held across the last realloc must already have room.
A skipped socket is counted in expected but never batched, so end_sk can be short of expected on a batch that is actually complete. Decide completeness by whether the walk left any socket behind instead. The WARN after the locked realloc checks the same, replacing an end_sk == expected check that could not hold on that path since commit cdec67a489d4 ("bpf: tcp: Make sure iter->batch always contains a full bucket snapshot").
If every matching socket in a bucket is mid-init (refcount 0), end_sk stays 0. Advance to the next bucket rather than returning a batch entry that was never filled this round.
{
"affected": [],
"aliases": [
"CVE-2026-74714"
],
"database_specific": {
"cwe_ids": [],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-22T16:16:46Z",
"severity": null
},
"details": "In the Linux kernel, the following vulnerability has been resolved:\n\nbpf: tcp: Fix use-after-free in bpf_iter_tcp_established_batch()\n\nreqsk_queue_hash_req() publishes a TCP_NEW_SYN_RECV request_sock onto\nthe ehash chain, drops the bucket lock, and only afterwards sets\nrsk_refcnt to 3.\n\nLockless readers such as __inet_lookup_established() handle this with\nrefcount_inc_not_zero(), but bpf_iter_tcp_established_batch() uses plain\nsock_hold() while holding the bucket lock, on the assumption that the\nlock guarantees sk_refcnt \u003e 0. That assumption does not hold for\nrequest_sock:\n\n CPU 0 CPU 1\n ----- -----\n tcp_conn_request()\n reqsk_queue_hash_req()\n inet_ehash_insert(req)\n spin_lock(bucket)\n __sk_nulls_add_node_rcu(req) // rsk_refcnt == 0\n spin_unlock(bucket)\n bpf_iter_tcp_established_batch()\n spin_lock(bucket)\n sock_hold(req) \u003c-- addition on 0\n spin_unlock(bucket)\n refcount_set(\u0026req-\u003ersk_refcnt, 3) // clobbers saturated value\n\nwhich surfaces as:\n\n refcount_t: addition on 0; use-after-free.\n WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x48/0x90, CPU#1\n Call Trace:\n bpf_iter_tcp_established_batch+0x14e/0x170\n bpf_iter_tcp_batch+0x53/0x200\n bpf_iter_tcp_seq_next+0x27/0x70\n bpf_seq_read+0x107/0x410\n vfs_read+0xb9/0x380\n\nThe iterator\u0027s stolen reference is lost when the publishing CPU\u0027s\nrefcount_set() overwrites the count, leaving the socket one reference\nshort. When the last legitimate owner drops its reference the reqsk is\nfreed while still reachable, leading to use-after-free.\n\nThis reproduces in seconds with tcp_syncookies=0, a handful of threads\ndoing connect()/close() to a local listener while others read an\niter/tcp link in a tight loop.\n\nUse refcount_inc_not_zero() and skip the socket on failure. A skipped\nsocket is still part of the bucket, so keep counting it in expected.\nThe reallocations are sized from expected, and a request sock whose\nrefcount gets published while the lock is held across the last realloc\nmust already have room.\n\nA skipped socket is counted in expected but never batched, so end_sk\ncan be short of expected on a batch that is actually complete. Decide\ncompleteness by whether the walk left any socket behind instead. The\nWARN after the locked realloc checks the same, replacing an\nend_sk == expected check that could not hold on that path since\ncommit cdec67a489d4 (\"bpf: tcp: Make sure iter-\u003ebatch always\ncontains a full bucket snapshot\").\n\nIf every matching socket in a bucket is mid-init (refcount 0), end_sk\nstays 0. Advance to the next bucket rather than returning a batch entry\nthat was never filled this round.",
"id": "GHSA-cqmm-fv72-p7f7",
"modified": "2026-08-22T18:30:30Z",
"published": "2026-08-22T18:30:30Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-74714"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/7d2b60a4bc0499f62ff8520af6309bbe170882fd"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/97e74d3e45d653c07c2d406fc530a9bbe3df8396"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/cc0295f89296ed351fc4b0b48fee887ba02c5d24"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/cefcbbe20846a45f9a7dae868f7ef1000953e2df"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/ddbe966b5d1fe212ada749bc3d0b410f1a7dea74"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/e5fd3f514e27db1f05fbd72ba615d74941e23c51"
}
],
"schema_version": "1.4.0",
"severity": []
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.