From 727f03755c98612f5b1fbe03d9e35948011f6025 Mon Sep 17 00:00:00 2001 From: Nick Hill Date: Fri, 10 Jan 2020 21:04:32 -0800 Subject: [PATCH] Fix BufferOverflowException during non-Unsafe PooledDirectByteBuf resize (#9912) Motivation Recent optimization #9765 introduced a bug where the native indices of the internal reused duplicate nio buffer are not properly reset prior to using it to copy data during a reallocation operation. This can result in BufferOverflowExceptions thrown during ByteBuf capacity changes. The code path in question applies only to pooled direct buffers when Unsafe is disabled or not available. Modification Ensure ByteBuffer#clear() is always called on the reused internal nio buffer prior to returning it from PooledByteBuf#internalNioBuffer() (protected method); add unit test that exposes the bug. Result Fixes #9911 --- .../java/io/netty/buffer/PooledByteBuf.java | 2 ++ .../io/netty/buffer/PooledDirectByteBuf.java | 4 +-- .../java/io/netty/buffer/PoolArenaTest.java | 27 ++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java b/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java index 65bfe489f0..b46d20f767 100644 --- a/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java @@ -153,6 +153,8 @@ abstract class PooledByteBuf extends AbstractReferenceCountedByteBuf { ByteBuffer tmpNioBuf = this.tmpNioBuf; if (tmpNioBuf == null) { this.tmpNioBuf = tmpNioBuf = newInternalNioBuffer(memory); + } else { + tmpNioBuf.clear(); } return tmpNioBuf; } diff --git a/buffer/src/main/java/io/netty/buffer/PooledDirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/PooledDirectByteBuf.java index dee7fe6774..3d77ecf195 100644 --- a/buffer/src/main/java/io/netty/buffer/PooledDirectByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/PooledDirectByteBuf.java @@ -260,7 +260,7 @@ final class PooledDirectByteBuf extends PooledByteBuf { } index = idx(index); - tmpBuf.clear().position(index).limit(index + length); + tmpBuf.limit(index + length).position(index); tmpBuf.put(src); return this; } @@ -274,7 +274,7 @@ final class PooledDirectByteBuf extends PooledByteBuf { return readBytes; } ByteBuffer tmpBuf = internalNioBuffer(); - tmpBuf.clear().position(idx(index)); + tmpBuf.position(idx(index)); tmpBuf.put(tmp, 0, readBytes); return readBytes; } diff --git a/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java b/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java index 2c1bd12db3..3ad331ddb3 100644 --- a/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java +++ b/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java @@ -20,6 +20,8 @@ import io.netty.util.internal.PlatformDependent; import org.junit.Assert; import org.junit.Test; +import static org.junit.Assume.assumeTrue; + import java.nio.ByteBuffer; public class PoolArenaTest { @@ -46,6 +48,7 @@ public class PoolArenaTest { @Test public void testDirectArenaOffsetCacheLine() throws Exception { + assumeTrue(PlatformDependent.hasUnsafe()); int capacity = 5; int alignment = 128; @@ -64,7 +67,7 @@ public class PoolArenaTest { } @Test - public final void testAllocationCounter() { + public void testAllocationCounter() { final PooledByteBufAllocator allocator = new PooledByteBufAllocator( true, // preferDirect 0, // nHeapArena @@ -107,4 +110,26 @@ public class PoolArenaTest { Assert.assertEquals(1, metric.numNormalDeallocations()); Assert.assertEquals(1, metric.numNormalAllocations()); } + + @Test + public void testDirectArenaMemoryCopy() { + ByteBuf src = PooledByteBufAllocator.DEFAULT.directBuffer(512); + ByteBuf dst = PooledByteBufAllocator.DEFAULT.directBuffer(512); + + PooledByteBuf pooledSrc = unwrapIfNeeded(src); + PooledByteBuf pooledDst = unwrapIfNeeded(dst); + + // This causes the internal reused ByteBuffer duplicate limit to be set to 128 + pooledDst.writeBytes(ByteBuffer.allocate(128)); + // Ensure internal ByteBuffer duplicate limit is properly reset (used in memoryCopy non-Unsafe case) + pooledDst.chunk.arena.memoryCopy(pooledSrc.memory, 0, pooledDst, 512); + + src.release(); + dst.release(); + } + + @SuppressWarnings("unchecked") + private PooledByteBuf unwrapIfNeeded(ByteBuf buf) { + return (PooledByteBuf) (buf instanceof PooledByteBuf ? buf : buf.unwrap()); + } }