File netty-CVE-2020-11612.patch of Package netty.18722
--- netty-netty-4.1.13.Final/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java 2020-04-09 09:42:57.546195401 +0200
@@ -16,6 +16,7 @@
package io.netty.handler.codec.compression;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
@@ -63,7 +64,19 @@
* Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
*/
public JdkZlibDecoder() {
- this(ZlibWrapper.ZLIB, null);
+ this(ZlibWrapper.ZLIB, null, false, 0);
+ }
+
+ /**
+ * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
+ * and the specified maximum buffer allocation.
+ *
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ */
+ public JdkZlibDecoder(int maxAllocation) {
+ this(ZlibWrapper.ZLIB, null, false, maxAllocation);
}
/**
@@ -72,7 +85,20 @@
* supports the preset dictionary.
*/
public JdkZlibDecoder(byte[] dictionary) {
- this(ZlibWrapper.ZLIB, dictionary);
+ this(ZlibWrapper.ZLIB, dictionary, false, 0);
+ }
+
+ /**
+ * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
+ * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
+ * supports the preset dictionary.
+ *
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ */
+ public JdkZlibDecoder(byte[] dictionary, int maxAllocation) {
+ this(ZlibWrapper.ZLIB, dictionary, false, maxAllocation);
}
/**
@@ -81,10 +107,41 @@
* supported atm.
*/
public JdkZlibDecoder(ZlibWrapper wrapper) {
- this(wrapper, null);
+ this(wrapper, null, false, 0);
+ }
+
+ /**
+ * Creates a new instance with the specified wrapper and maximum buffer allocation.
+ * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
+ * supported atm.
+ *
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ */
+ public JdkZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
+ this(wrapper, null, false, maxAllocation);
+ }
+
+ public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated) {
+ this(wrapper, null, decompressConcatenated, 0);
+ }
+
+ public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated, int maxAllocation) {
+ this(wrapper, null, decompressConcatenated, maxAllocation);
+ }
+
+ public JdkZlibDecoder(boolean decompressConcatenated) {
+ this(ZlibWrapper.GZIP, null, decompressConcatenated, 0);
}
- private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary) {
+ public JdkZlibDecoder(boolean decompressConcatenated, int maxAllocation) {
+ this(ZlibWrapper.GZIP, null, decompressConcatenated, maxAllocation);
+ }
+
+ private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, boolean decompressConcatenated, int maxAllocation) {
+ super(maxAllocation);
+
if (wrapper == null) {
throw new NullPointerException("wrapper");
}
@@ -167,7 +224,7 @@
inflater.setInput(array);
}
- ByteBuf decompressed = ctx.alloc().heapBuffer(inflater.getRemaining() << 1);
+ ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inflater.getRemaining() << 1);
try {
boolean readFooter = false;
while (!inflater.needsInput()) {
@@ -198,7 +255,7 @@
}
break;
} else {
- decompressed.ensureWritable(inflater.getRemaining() << 1);
+ decompressed = prepareDecompressBuffer(ctx, decompressed, inflater.getRemaining() << 1);
}
}
@@ -223,6 +280,11 @@
}
@Override
+ protected void decompressionBufferExhausted(ByteBuf buffer) {
+ finished = true;
+ }
+
+ @Override
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
super.handlerRemoved0(ctx);
if (inflater != null) {
--- netty-netty-4.1.13.Final/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java 2020-04-09 09:34:03.351374501 +0200
@@ -18,6 +18,7 @@
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
@@ -34,7 +35,21 @@
* @throws DecompressionException if failed to initialize zlib
*/
public JZlibDecoder() {
- this(ZlibWrapper.ZLIB);
+ this(ZlibWrapper.ZLIB, 0);
+ }
+
+ /**
+ * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
+ * and specified maximum buffer allocation.
+ *
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ *
+ * @throws DecompressionException if failed to initialize zlib
+ */
+ public JZlibDecoder(int maxAllocation) {
+ this(ZlibWrapper.ZLIB, maxAllocation);
}
/**
@@ -43,6 +58,21 @@
* @throws DecompressionException if failed to initialize zlib
*/
public JZlibDecoder(ZlibWrapper wrapper) {
+ this(wrapper, 0);
+ }
+
+ /**
+ * Creates a new instance with the specified wrapper and maximum buffer allocation.
+ *
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ *
+ * @throws DecompressionException if failed to initialize zlib
+ */
+ public JZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
+ super(maxAllocation);
+
if (wrapper == null) {
throw new NullPointerException("wrapper");
}
@@ -61,6 +91,22 @@
* @throws DecompressionException if failed to initialize zlib
*/
public JZlibDecoder(byte[] dictionary) {
+ this(dictionary, 0);
+ }
+
+ /**
+ * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
+ * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
+ * supports the preset dictionary.
+ *
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ *
+ * @throws DecompressionException if failed to initialize zlib
+ */
+ public JZlibDecoder(byte[] dictionary, int maxAllocation) {
+ super(maxAllocation);
if (dictionary == null) {
throw new NullPointerException("dictionary");
}
@@ -110,11 +156,11 @@
final int oldNextInIndex = z.next_in_index;
// Configure output.
- ByteBuf decompressed = ctx.alloc().heapBuffer(inputLength << 1);
+ ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inputLength << 1);
try {
loop: for (;;) {
- decompressed.ensureWritable(z.avail_in << 1);
+ decompressed = prepareDecompressBuffer(ctx, decompressed, z.avail_in << 1);
z.avail_out = decompressed.writableBytes();
z.next_out = decompressed.array();
z.next_out_index = decompressed.arrayOffset() + decompressed.writerIndex();
@@ -170,4 +216,9 @@
z.next_out = null;
}
}
+
+ @Override
+ protected void decompressionBufferExhausted(ByteBuf buffer) {
+ finished = true;
+ }
}
--- netty-netty-4.1.13.Final/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java 2020-04-09 09:29:03.593720874 +0200
@@ -16,6 +16,8 @@
package io.netty.handler.codec.compression;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
@@ -24,8 +26,71 @@
public abstract class ZlibDecoder extends ByteToMessageDecoder {
/**
+ * Maximum allowed size of the decompression buffer.
+ */
+ protected final int maxAllocation;
+
+ /**
+ * Same as {@link #ZlibDecoder(int)} with maxAllocation = 0.
+ */
+ public ZlibDecoder() {
+ this(0);
+ }
+
+ /**
+ * Construct a new ZlibDecoder.
+ * @param maxAllocation
+ * Maximum size of the decompression buffer. Must be >= 0.
+ * If zero, maximum size is decided by the {@link ByteBufAllocator}.
+ */
+ public ZlibDecoder(int maxAllocation) {
+ if (maxAllocation < 0) {
+ throw new IllegalArgumentException("maxAllocation must be >= 0");
+ }
+ this.maxAllocation = maxAllocation;
+ }
+
+ /**
* Returns {@code true} if and only if the end of the compressed stream
* has been reached.
*/
public abstract boolean isClosed();
+
+ /**
+ * Allocate or expand the decompression buffer, without exceeding the maximum allocation.
+ * Calls {@link #decompressionBufferExhausted(ByteBuf)} if the buffer is full and cannot be expanded further.
+ */
+ protected ByteBuf prepareDecompressBuffer(ChannelHandlerContext ctx, ByteBuf buffer, int preferredSize) {
+ if (buffer == null) {
+ if (maxAllocation == 0) {
+ return ctx.alloc().heapBuffer(preferredSize);
+ }
+
+ return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation);
+ }
+
+ // this always expands the buffer if possible, even if the expansion is less than preferredSize
+ // we throw the exception only if the buffer could not be expanded at all
+ // this means that one final attempt to deserialize will always be made with the buffer at maxAllocation
+ if (buffer.ensureWritable(preferredSize, true) == 1) {
+ // buffer must be consumed so subclasses don't add it to output
+ // we therefore duplicate it when calling decompressionBufferExhausted() to guarantee non-interference
+ // but wait until after to consume it so the subclass can tell how much output is really in the buffer
+ decompressionBufferExhausted(buffer.duplicate());
+ buffer.skipBytes(buffer.readableBytes());
+ throw new DecompressionException("Decompression buffer has reached maximum size: " + buffer.maxCapacity());
+ }
+
+ return buffer;
+ }
+
+ /**
+ * Called when the decompression buffer cannot be expanded further.
+ * Default implementation is a no-op, but subclasses can override in case they want to
+ * do something before the {@link DecompressionException} is thrown, such as log the
+ * data that was decompressed so far.
+ */
+ protected void decompressionBufferExhausted(ByteBuf buffer) {
+ }
+
}
--- netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java 2020-04-09 09:29:03.593720874 +0200
@@ -26,8 +26,8 @@
}
@Override
- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
- return new JdkZlibDecoder(wrapper);
+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
+ return new JdkZlibDecoder(wrapper, maxAllocation);
}
@Test(expected = DecompressionException.class)
--- netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java 2020-04-09 09:29:03.593720874 +0200
@@ -23,7 +23,7 @@
}
@Override
- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
- return new JZlibDecoder(wrapper);
+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
+ return new JZlibDecoder(wrapper, maxAllocation);
}
}
--- netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java 2020-04-09 09:29:03.593720874 +0200
@@ -23,7 +23,7 @@
}
@Override
- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
- return new JZlibDecoder(wrapper);
+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
+ return new JZlibDecoder(wrapper, maxAllocation);
}
}
--- netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java 2020-04-09 09:29:03.593720874 +0200
@@ -25,8 +25,8 @@
}
@Override
- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
- return new JdkZlibDecoder(wrapper);
+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
+ return new JdkZlibDecoder(wrapper, maxAllocation);
}
@Test(expected = DecompressionException.class)
--- netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java 2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java 2020-04-09 09:29:03.593720874 +0200
@@ -15,7 +15,9 @@
*/
package io.netty.handler.codec.compression;
+import io.netty.buffer.AbstractByteBufAllocator;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
@@ -88,8 +90,12 @@
rand.nextBytes(BYTES_LARGE);
}
+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
+ return createDecoder(wrapper, 0);
+ }
+
protected abstract ZlibEncoder createEncoder(ZlibWrapper wrapper);
- protected abstract ZlibDecoder createDecoder(ZlibWrapper wrapper);
+ protected abstract ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation);
@Test
public void testGZIP2() throws Exception {
@@ -345,6 +351,25 @@
testCompressLarge(ZlibWrapper.GZIP, ZlibWrapper.ZLIB_OR_NONE);
}
+ @Test
+ public void testMaxAllocation() throws Exception {
+ int maxAllocation = 1024;
+ ZlibDecoder decoder = createDecoder(ZlibWrapper.ZLIB, maxAllocation);
+ EmbeddedChannel chDecoder = new EmbeddedChannel(decoder);
+ TestByteBufAllocator alloc = new TestByteBufAllocator(chDecoder.alloc());
+ chDecoder.config().setAllocator(alloc);
+
+ try {
+ chDecoder.writeInbound(Unpooled.wrappedBuffer(deflate(BYTES_LARGE)));
+ fail("decompressed size > maxAllocation, so should have thrown exception");
+ } catch (DecompressionException e) {
+ assertTrue(e.getMessage().startsWith("Decompression buffer has reached maximum size"));
+ assertEquals(maxAllocation, alloc.getMaxAllocation());
+ assertTrue(decoder.isClosed());
+ assertFalse(chDecoder.finish());
+ }
+ }
+
private static byte[] gzip(byte[] bytes) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream stream = new GZIPOutputStream(out);
@@ -360,4 +385,34 @@
stream.close();
return out.toByteArray();
}
+
+ private static final class TestByteBufAllocator extends AbstractByteBufAllocator {
+ private ByteBufAllocator wrapped;
+ private int maxAllocation;
+
+ TestByteBufAllocator(ByteBufAllocator wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ public int getMaxAllocation() {
+ return maxAllocation;
+ }
+
+ @Override
+ public boolean isDirectBufferPooled() {
+ return wrapped.isDirectBufferPooled();
+ }
+
+ @Override
+ protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
+ maxAllocation = Math.max(maxAllocation, maxCapacity);
+ return wrapped.heapBuffer(initialCapacity, maxCapacity);
+ }
+
+ @Override
+ protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
+ maxAllocation = Math.max(maxAllocation, maxCapacity);
+ return wrapped.directBuffer(initialCapacity, maxCapacity);
+ }
+ }
}