CVE-2025-39946: Linux Kernel TLS SKB Overflow via Bogus Record Headers
A heap overflow in the Linux kernel TLS subsystem allows overflow of allocated SKB space when bogus record headers arrive in small OOB chunks. CVSS 9.8 critical.
A newly discovered flaw in Linux affects how the operating system handles encrypted internet connections. When your device receives scrambled messages (encrypted data), the system is supposed to check that the message is legitimate before processing it. This vulnerability creates a situation where malformed or fake messages can slip past that check.
Think of it like a bouncer at a club who's supposed to verify your ID. If the ID looks fake, the bouncer should turn you away immediately. Instead, this bouncer keeps asking for the same ID over and over, letting the person stand in the doorway causing a bottleneck.
Here's what actually happens: An attacker sends deliberately broken encrypted messages to a Linux system. Instead of rejecting these messages right away, the kernel gets confused and tries to process them repeatedly. Each failed attempt consumes memory and processing power, like repeatedly trying to fit a square peg through a round hole without ever stopping to acknowledge it won't work.
The impact matters most to people running servers or networked devices that handle encrypted traffic. Web servers, email systems, and IoT devices running Linux could be vulnerable. An attacker could bombard these systems with bogus messages, eventually exhausting their memory or making them so busy they stop responding to legitimate users.
This is a denial of service attack. Your service stops being available, but nothing gets stolen or compromised in the traditional sense.
Here's what you should do: First, if you run a Linux server, check whether your system provider has released security updates and install them. Second, keep your router and connected devices updated through their standard update mechanisms. Third, if you manage infrastructure, talk to your hosting provider about their patching timeline. Most users with standard consumer devices will be protected through automatic updates.
Want the full technical analysis? Click "Technical" above.
CVE-2025-39946 is a critical (CVSS 9.8) heap overflow in the Linux kernel's in-kernel TLS implementation (net/tls/). The vulnerability exists in the record parsing path of the TLS receive side. When the kernel is under socket-buffer pressure, it reads record data out of the socket earlier than usual — before a full record has arrived — to prevent connection stalls. If the record header that eventually arrives is invalid (bogus length or type), the kernel fails to abort the stream promptly. Because more data is copied into the pre-allocated SKB on each retry pass, the kernel can write past the end of the allocated SKB data space, producing a classic heap overflow with attacker-influenced content.
Exploitation was demonstrated against the lts-6.12.48 kCTF kernel instance by farazsth98. The attack vector is network-accessible: a malicious TLS peer serves record headers in small out-of-band (OOB) sends to force the early-read path, then floods the receive buffer with a crafted payload designed to overflow the SKB allocation.
Root cause: When TLS record parsing is retried under socket-buffer pressure, each retry copies additional data into the SKB before re-validating the header, and a missing stream-abort on header validation failure allows the accumulated copies to overflow the original SKB allocation.
Affected Component
Subsystem:net/tls/tls_sw.c — software TLS record layer
Function:tls_sw_recvmsg() and the underlying decrypt_skb_update() / tls_strparser_msg_parser() path
Affected versions: Linux kernel prior to the fixing commit in the 6.x stable series (see NVD for exact range)
Trigger condition: Socket receive buffer small enough that the strparser calls back before a full TLS record is buffered; header delivered via TCP OOB/urgent data
Root Cause Analysis
The TLS software path uses the kernel's strparser (net/strparser.c) to frame TLS records out of the TCP stream. The strparser calls tls_strp_read_copyin() to accumulate record bytes. Under normal conditions the socket buffers the entire record before the strparser fires. Under buffer pressure the strparser fires early, before the full record is present, and tls_strp_msg_load() is called to read what is available so far into an SKB that was sized based on the advertised (but not yet validated) record length.
The critical path is inside tls_strp_msg_load() / tls_strp_read_sock():
/* net/tls/tls_strp.c — simplified, pre-patch */
static int tls_strp_read_sock(struct tls_strparser *strp)
{
struct strp_msg *rxm = strp_msg(strp->anchor);
int sz;
/*
* BUG: SKB was allocated using the length field from the TLS header
* (strp->anchor->len = TLS_HEADER_SIZE + record_len).
* If record_len is attacker-controlled and header is bogus, we
* allocated exactly that many bytes — but we keep copying here
* on every retry without re-checking whether the header is valid,
* and without aborting when it is found invalid.
*/
sz = tcp_read_sock(strp->sk, &strp->desc, tls_strp_copyin); // BUG: copies more data each retry
return sz;
}
static int tls_strp_copyin(read_descriptor_t *desc, struct sk_buff *in_skb,
unsigned int offset, size_t in_len)
{
struct tls_strparser *strp = desc->arg.data;
struct sk_buff *skb = strp->anchor;
struct strp_msg *rxm = strp_msg(skb);
size_t chunk;
int err;
if (skb->len >= rxm->full_len) // already have enough?
return 0;
chunk = min_t(size_t, in_len, rxm->full_len - skb->len);
/* Copies directly into skb tail — no re-validation of rxm->full_len
* against actual allocated skb data space on retried calls. */
err = skb_add_data_nocache(strp->sk, skb, in_skb->data + offset, chunk);
// BUG: if header validation later fails, we already wrote past alloc
return err ?: chunk;
}
static int tls_strp_msg_parser(struct tls_strparser *strp)
{
struct tls_record_info *rec;
int header_err;
header_err = tls_strp_check_rcv_hdr(strp); // validates record type+len
if (header_err) {
/* MISSING: strp_done() / tls_strp_abort_strp() call here.
* Without abort, the strparser will retry, copy more data,
* and eventually overflow the skb allocation. */
return header_err; // BUG: no abort
}
/* ... */
}
The key invariant that breaks: rxm->full_len is written from the (attacker-supplied) TLS header before the header is validated. The SKB's data area is sized to full_len. On the first retry the copy stays within bounds. On subsequent retries, each call to tls_strp_copyin appends another chunk. Because rxm->full_len was never corrected (the abort never happened), the guard skb->len >= rxm->full_len never trips, and the write advances beyond the end of the allocated tailroom.
Exploitation Mechanics
EXPLOIT CHAIN (farazsth98, lts-6.12.48 kCTF):
1. Attacker opens a TLS connection to the victim kernel socket.
2. Attacker sends the 5-byte TLS record header (type + version + length)
split across multiple small TCP OOB/urgent sends. This forces the
strparser to fire before the full record body has arrived, triggering
the early-read (buffer-pressure) path.
3. The bogus header encodes a record length L such that the kernel
allocates an SKB with exactly L bytes of tailroom.
4. Attacker fills the TCP receive buffer with a crafted payload longer
than L bytes. The strparser retries tls_strp_read_sock() on each
select/poll wake-up.
5. Each retry appends another chunk (up to recv_window size) into the
SKB past the original L-byte allocation. The overflow overwrites
adjacent heap objects (typically other skb_shared_info or
neighbouring slab objects on the same page).
6. With a heap grooming primitive (e.g., spray of fixed-size kmalloc
objects) the attacker places a target object (e.g., a file struct,
a cred struct, or a pipe_buffer) immediately after the TLS SKB.
7. Overwritten object fields are used to build a read/write primitive.
farazsth98's PoC targets core_pattern for an lpe-to-code-execution
path: overwrite core_pattern with a reverse-shell path, then trigger
a crash in an unprivileged process.
8. Shell executes as root via the kernel's core dump handler.
The exploit author notes the only per-kernel tunable is CORE_PATTERN_OFFSET — the distance from _text to core_pattern — which can be extracted from a bzImage loaded with root privileges by comparing addresses in /proc/kallsyms.
Memory Layout
SLAB STATE BEFORE OVERFLOW (kmalloc-2k example, record length L = 0x400):
[ skb->head ]
[ skb_shared_info @ head + L <-- allocation boundary ]
[ adjacent kmalloc obj @ head + L + sizeof(skb_shared_info) ]
e.g.: pipe_buffer / file * / cred *
skb tailroom:
offset 0x000: [ TLS record data — legitimate copies ]
offset 0x3f8: [ last valid byte of L-byte allocation ]
offset 0x400: [ skb_shared_info.frags[0] — first overflow word ]
HEAP STATE AFTER N RETRIES (overflow of ~0x80 bytes past L):
offset 0x400: skb_shared_info.frags[0].page.p <- attacker data
offset 0x408: skb_shared_info.frags[0].page_offset <- attacker data
offset 0x410: skb_shared_info.frags[0].size <- attacker data
...
offset 0x440: adjacent object header CORRUPTED
e.g. pipe_buffer->ops ptr = attacker-controlled fn ptr
The fix adds an explicit stream abort in the header-validation failure path, preventing any further copies into the already-allocated SKB:
// BEFORE (vulnerable — net/tls/tls_strp.c):
static int tls_strp_msg_parser(struct tls_strparser *strp)
{
int header_err;
header_err = tls_strp_check_rcv_hdr(strp);
if (header_err) {
return header_err; // returns but does NOT abort stream
// strparser retries → more copies → overflow
}
tls_strp_msg_load(strp);
return 0;
}
// AFTER (patched):
static int tls_strp_msg_parser(struct tls_strparser *strp)
{
int header_err;
header_err = tls_strp_check_rcv_hdr(strp);
if (header_err) {
tls_strp_abort_strp(strp, header_err); // ADDED: aborts stream
return header_err; // no further copies possible
}
tls_strp_msg_load(strp);
return 0;
}
// tls_strp_abort_strp() sets strp->stopped = 1, which causes
// subsequent calls to tls_strp_read_sock() to return immediately:
static void tls_strp_abort_strp(struct tls_strparser *strp, int err)
{
if (strp->stopped)
return;
strp->stopped = 1;
/* Wake up any waiting recvmsg — returns error to userspace */
strp->sk->sk_err = -err;
sk_error_report(strp->sk);
}
The fix is minimal and surgical: a single tls_strp_abort_strp() call gates the retry path. Once strp->stopped is set, tcp_read_sock() is never called again for this stream, so no further data can accumulate in the over-allocated SKB.
Detection and Indicators
Runtime indicators of active exploitation:
Kernel SLUB/KASAN reports:BUG: KASAN: slab-out-of-bounds in tls_strp_copyin on kernels with sanitizers enabled.
Anomalous TLS OOB traffic: TCP urgent pointer set on a TLS connection; multiple 1–2 byte sends preceding a large payload burst on the same connection.
core_pattern mutation:/proc/sys/kernel/core_pattern changed to a pipe command or unexpected path — a post-exploitation artifact of the PoC's privilege escalation path.
Kernel crash / skb_over_panic: panic string skb_over_panic: text:ffffffff... len:N put:M head:... data:... tail:... end:... dev:<NULL> in dmesg indicates the overflow was detected before exploitation succeeded.
// Indicative dmesg on unpatched kernel hitting the panic path:
[ 142.887431] skb_over_panic: text:ffffffff81a3c290 len:1152 put:128
head:ffff888107b40000 data:ffff888107b40054
tail:0x4c0 end:0x440 dev:
[ 142.887441] ------------[ cut here ]------------
[ 142.887442] kernel BUG at net/core/skbuff.c:192!
Remediation
Patch immediately: Apply the upstream fix to net/tls/tls_strp.c adding tls_strp_abort_strp() on header validation failure. Verify with git log --oneline net/tls/ that the fixing commit is present.
Kernel config mitigations: Build with CONFIG_KASAN=y and CONFIG_KASAN_INLINE=y on staging/CI systems to catch out-of-bounds writes early.
Network-level filtering: Drop or rate-limit TCP urgent/OOB segments at the border (iptables -m u32 or equivalent) if not required by any application.
Disable in-kernel TLS if unused:CONFIG_TLS=n or unload the tls module (rmmod tls) if userspace TLS termination (e.g., OpenSSL) is used instead.
Monitor core_pattern: Alert on unexpected writes to /proc/sys/kernel/core_pattern via auditd rule: -w /proc/sys/kernel/core_pattern -p w -k tls_exploit_ioc.