FlawOpen

flawopen.com/Teardowns/cve-2025-39682-linux-ktls-zero-length-record-mishandling

● CVE-2025-39682 · CVSS 9.8 · Critical
FlawOpen Security Research

Linux CVE-2025-39682: kTLS Zero-Length Record Bug

A zero-length non-data TLS record in Linux’s kTLS receive queue could bypass the per-recvmsg record-type check. The fix checks the processed record type even when recvmsg copied zero bytes. The Linux CVSS assessment describes possible kernel-heap disclosure to userspace, memory corruption, and remote kernel crashes; these are assessed consequences, not claims that every affected system experiences each outcome.

CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
💡 Plain English Explainer (ELI5)

Imagine a mail sorter that must put only letters from one category into each tray. An empty envelope still has a category, but the old sorter decided whether to switch trays by counting letters. With zero letters, it ignored the envelope’s category and mixed the next type into the same tray. The kernel fix checks the category marker itself, even when the envelope contains no data.

Core Concepts & Subsystem Terms

kTLS
Kernel TLS: Linux can process TLS records in the kernel for an attached TCP socket.
recvmsg()
Socket receive system call used by the application and kernel TLS receive path.
TLS content type
A field classifying the record, such as application data or a control record.
rx_list
Queue of decrypted or pending receive records handled by later recvmsg calls.
CWE-754
Improper Check for Unusual or Exceptional Conditions. NVD lists CWE-754 and attributes it to CISA-ADP; the Linux CVE record itself assigns no CWE.

Framework mappings

Root Cause Analysis

Linux kTLS receive processing must return either contiguous DATA records or one non-DATA record per recvmsg() call. When process_rx_list() handled an initial zero-length non-DATA record, it could set the TLS content-type control value while returning err == 0. tls_sw_recvmsg() copied that zero to copied, then used (copied && control != TLS_RECORD_TYPE_DATA) as part of its stop condition. Because copied was zero, the condition ignored the already-processed record type and continued into following data. The upstream patch changes the guard to test control directly: when a record type has been reported and it is not DATA, stop; zero remains the sentinel for “no type reported yet.” This is reachable only where the Linux kernel TLS receive path (kTLS) is enabled and used; it is not a defect in TLS record parsing by every userspace TLS library. Linux’s published CVSS 3.1 vector is 9.8 Critical (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). The source description establishes the receive-state invariant and fix; assess exposure based on actual kTLS use and vendor kernel status.

Step-by-Step Attack Flow

Step 1

kTLS receives records

An application uses a TCP socket with Linux kernel TLS receive processing enabled.

Step 2

A pending zero-length non-DATA record is processed

The corner case occurs when the first record taken from rx_list has no payload. Processing can report a content type while returning zero copied bytes.

Step 3

The old guard keys on copied bytes

copied is zero, so the old condition skips checking whether the processed record type was non-DATA.

Step 4

The receive loop continues across the type boundary

Following data may be handled in the same recvmsg call, contrary to the invariant that one call processes contiguous DATA records or a single non-DATA record. The Linux CVSS assessment describes possible kernel-heap disclosure to userspace, memory corruption, and remote kernel crashes; these are assessed consequences, not claims that every affected system experiences each outcome.

Step 5

The fix checks the semantic marker

The patched guard stops when control is set to a non-DATA type, independently of the byte count.

Source Code: Flaw vs. Secure Implementation

Focused excerpt from net/tls/tls_sw.c, not a standalone function. Upstream changed the stop predicate to use the reported content-type marker rather than whether bytes were copied. control == 0 means no type has been reported; nonzero non-DATA means stop this recvmsg call. This is the exact one-line logic change from the upstream patch.

Source excerpt: net/tls/tls_sw.c — tls_sw_recvmsg

UNPATCHED FLAW
copied = err;
if (len <= copied ||
    (copied && control != TLS_RECORD_TYPE_DATA) || rx_more)
    goto end;
HARDENED SECURE PATCH
copied = err;
if (len <= copied || rx_more ||
    (control && control != TLS_RECORD_TYPE_DATA))
    goto end;

Engineering & System Hardening Checklist

Sources