flawopen.com/Teardowns/cve-2025-39682-linux-ktls-zero-length-record-mishandling
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 vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HImagine 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-754and attributes it to CISA-ADP; the Linux CVE record itself assigns no CWE.
Framework mappings
- CWE reference for the weakness described in this guide: CWE-754
- CISA KEV — added 2026-09-18 — CVE-2025-39682
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
kTLS receives records
An application uses a TCP socket with Linux kernel TLS receive processing enabled.
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.
The old guard keys on copied bytes
copied is zero, so the old condition skips checking whether the processed record type was non-DATA.
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.
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
copied = err;
if (len <= copied ||
(copied && control != TLS_RECORD_TYPE_DATA) || rx_more)
goto end;
copied = err;
if (len <= copied || rx_more ||
(control && control != TLS_RECORD_TYPE_DATA))
goto end;
Engineering & System Hardening Checklist
- ✓Inventory workloads that enable Linux kTLS receive processing; do not treat ordinary userspace TLS as automatically affected.
- ✓Update to a vendor kernel package containing the fix and confirm the exact distribution advisory/build.
- ✓Preserve the invariant in reviews: a recvmsg call may consume contiguous DATA records or one non-DATA record, not cross a type boundary.
- ✓Run or retain the upstream kTLS zero-length-record regression test; downstream receive-path implementations should also assert that a zero-length non-DATA record does not let the same call consume the next record.
- ✓Keep “no record type observed” distinct from “zero bytes copied”; check semantic state such as
controlrather than using byte count as a proxy. - ✓If patching is delayed, follow the vendor advisory for kTLS mitigation and verify that it does not disrupt required kernel-TLS workloads.