Fix possible unsafe sharing of internal NIO buffer in CompositeByteBuf (#9169)

Motivation

A small thread-safety bug was introduced during the internal
optimizations of ComponentByteBuf made a while back in #8437. When there
is a single component which was added as a slice,
internalNioBuffer(int,int) will currently return the unwrapped slice's
un-duplicated internal NIO buffer. This is not safe since it could be
modified concurrently with other usage of that parent buffer.

Modifications

Delegate internalNioBuffer to nioBuffer in this case, which returns a
duplicate. This matches what's done in derived buffers in general
(for the same reason). Add unit test.

Result

Fixed possible thread-safety bug
This commit is contained in:
Nick Hill 2019-05-22 02:07:06 -07:00 committed by Norman Maurer
parent 52c5389190
commit 507e0a05b5
2 changed files with 32 additions and 3 deletions

View File

@ -1606,8 +1606,7 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
case 0: case 0:
return EMPTY_NIO_BUFFER; return EMPTY_NIO_BUFFER;
case 1: case 1:
Component c = components[0]; return components[0].internalNioBuffer(index, length);
return c.buf.internalNioBuffer(c.idx(index), length);
default: default:
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -1889,6 +1888,16 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
return buf.duplicate().setIndex(idx(offset), idx(endOffset)); return buf.duplicate().setIndex(idx(offset), idx(endOffset));
} }
ByteBuffer internalNioBuffer(int index, int length) {
// We must not return the unwrapped buffer's internal buffer
// if it was originally added as a slice - this check of the
// slice field is threadsafe since we only care whether it
// was set upon Component construction, and we aren't
// attempting to access the referenced slice itself
return slice != null ? buf.nioBuffer(idx(index), length)
: buf.internalNioBuffer(idx(index), length);
}
void free() { void free() {
// Release the slice if present since it may have a different // Release the slice if present since it may have a different
// refcount to the unwrapped buf if it is a PooledSlicedByteBuf // refcount to the unwrapped buf if it is a PooledSlicedByteBuf

View File

@ -977,7 +977,27 @@ public abstract class AbstractCompositeByteBufTest extends AbstractByteBufTest {
@Override @Override
@Test @Test
public void testInternalNioBuffer() { public void testInternalNioBuffer() {
// ignore CompositeByteBuf buf = compositeBuffer();
assertEquals(0, buf.internalNioBuffer(0, 0).remaining());
// If non-derived buffer is added, its internal buffer should be returned
ByteBuf concreteBuffer = directBuffer().writeByte(1);
buf.addComponent(concreteBuffer);
assertSame(concreteBuffer.internalNioBuffer(0, 1), buf.internalNioBuffer(0, 1));
buf.release();
// In derived cases, the original internal buffer must not be used
buf = compositeBuffer();
concreteBuffer = directBuffer().writeByte(1);
buf.addComponent(concreteBuffer.slice());
assertNotSame(concreteBuffer.internalNioBuffer(0, 1), buf.internalNioBuffer(0, 1));
buf.release();
buf = compositeBuffer();
concreteBuffer = directBuffer().writeByte(1);
buf.addComponent(concreteBuffer.duplicate());
assertNotSame(concreteBuffer.internalNioBuffer(0, 1), buf.internalNioBuffer(0, 1));
buf.release();
} }
@Test @Test