
found a remotely triggerable out-of-bounds read in the Linux kernel's H.323 connection tracking parser (CVE-2026-23455, CVSS 9.1 Critical). no authentication. no privileges. send a packet and the kernel reads memory until it hits an unmapped page. this code has been reachable from the network since 2007. H.323 is a VoIP signaling protocol from the 1990s that refuses to die. it predates SIP. it's the protocol your office phone system was probably running before someone migrated to Teams, and in a lot of places nobody migrated. telecom carriers still use it for trunk signaling. hospitals run it for nurse call systems and overhead paging. hotels use it for room phones. building intercoms, elevator emergency phones, courtroom recording systems, prison phone networks, air traffic control voice switches. I keep finding it in places where I'm like, who is maintaining this, and the answer is always nobody, it just works. scariest sentence in infra. every Linux firewall or NAT gateway that handles this traffic loads nf_conntrack_h323. full ASN.1 PER decoder. inline in the kernel. parsing untrusted packets from the network in kernel context. the module exists because H.323 uses dynamic port allocation for media streams so the firewall needs to understand the signaling to open the right ports. it auto-loads when H.323 traffic hits a conntrack rule. on many distributions it's loaded by default. you don't configure this or opt into this. someone behind your gateway plugs in a phone and now you have an ASN.1 parser in your kernel processing packets from the internet. the attack surface is a UDP packet to port 1719 or a TCP connection to port 1720 on any Linux machine doing NAT for a network that might have a phone on it somewhere. the parser runs before the packet reaches userspace, and there is no firewall rule that helps because the firewall itself is the thing running the vulnerable code. DecodeQ931() processes Q.931 signaling messages. the UserUserIE code path reads a 16-bit length from the packet and decrements it by 1 to skip the protocol discriminator byte: len = (buf[p] << 8) | buf[p+1] p++; len-- return DecodeH323_UserInformation(buf, p, len, ...) if the encoded length is 0, len-- wraps to -1. len is a signed int. -1 gets passed to the decoder, which interprets it as a very large positive value and walks through memory far past the end of the packet buffer. the decoder iterates through ASN.1 fields, following structure, dereferencing pointers, until it hits an unmapped page or a parse error. one malformed packet, unbounded kernel memory disclosure. depending on slab layout the adjacent memory could contain kernel pointers that defeat KASLR, crypto keys, credentials from other processes, fragments of network packets belonging to other users. so the programmer who wrote this in 2007 was implementing RFC 2225. the RFC says the UserUserIE field contains a protocol discriminator byte followed by a variable-length body. the discriminator is always present. so len-- is always safe because len is always at least 1. the RFC says so. the protocol guarantees it. the wire does not care about your protocol guarantees. the wire is untrusted input. but once you've internalized "there's always a discriminator byte" the decrement looks like bookkeeping, and every reviewer after you internalizes the same thing because the code reads naturally and the RFC agrees and nobody is going to stop and ask "but what if len is zero" because the protocol says it can't be. the code is wrong because the programmer understood the protocol. someone who understood H.323 less might have added a defensive check, but someone who understood it perfectly trusted the wire to be well-formed. a reviewer who also understands the protocol reads len-- and sees bookkeeping. the bug is invisible to expertise. you can only see it by distrusting the wire unconditionally, which means ignoring what you know about the protocol. I don't have a clean answer for how you systematize that. static analyzers can't flag it because every individual operation is valid. the length read is bounds-checked. the decrement is legal C. the function call is type-correct. fuzzers might hit it if they generate a zero-length UserUserIE but protocol-aware fuzzers tend to generate valid protocol structures, which means len >= 1, which means they systematically avoid the one input that triggers the bug. the tool that finds this is a human reading code and asking "what if this value is zero" at every arithmetic operation on untrusted input. boring question. answer is almost always nothing. the 1% where it isn't nothing is a 9.1 Critical and you have no way to know which 1% until you've asked everywhere. protocol-specific conntrack helpers get written once for a deployment need. they work. they ship. then they sit in the kernel for two decades because the protocol still exists somewhere and removing the module would break someone's phone system in a hospital basement. nobody is reading the code for bugs. they're keeping it compiling. eighteen years. 9.1. if (len <= 0) break;. originally reported by @VidocSecurity (Klaudia Kloc and Dawid Moczadło). I confirmed, reproduced, and patched it. patched in stable 5.10–6.19.
Post summary
A new remote‑triggered out‑of‑bounds read (CVE‑2026‑23455) in the Linux kernel’s H.323 conntrack parser is disclosed with detailed technical explanation and a patch released for kernels 5.10–6.19.


