MessageAggregator Potential Leak

Motivation:
MessageAggregator has a potential to leak if a new message is received before the existing message has completed, and if a HttpContent is received but maxContentLength has been exceeded, or the content length is too long.

Modifications:

- Make the HttpObjectAggregator more robust to leaks
- Reduce dependance on handlingOversizedMessage but instead rely on the more general check of a null currentMessage

Result:
More robust MessageAggregator with less chance of leaks
This commit is contained in:
Scott Mitchell 2016-08-04 22:14:48 -07:00
parent eb7f751ba5
commit e3462a79c7

View File

@ -18,7 +18,6 @@ package io.netty.handler.codec;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder; import io.netty.buffer.ByteBufHolder;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
@ -28,6 +27,8 @@ import io.netty.util.ReferenceCountUtil;
import java.util.List; import java.util.List;
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
/** /**
* An abstract {@link ChannelHandler} that aggregates a series of message objects into a single aggregated message. * An abstract {@link ChannelHandler} that aggregates a series of message objects into a single aggregated message.
* <p> * <p>
@ -174,6 +175,10 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
} }
} }
/**
* @deprecated This method will be removed in future releases.
*/
@Deprecated
public final boolean isHandlingOversizedMessage() { public final boolean isHandlingOversizedMessage() {
return handlingOversizedMessage; return handlingOversizedMessage;
} }
@ -187,11 +192,11 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
@Override @Override
protected void decode(final ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception { protected void decode(final ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception {
O currentMessage = this.currentMessage;
if (isStartMessage(msg)) { if (isStartMessage(msg)) {
handlingOversizedMessage = false; handlingOversizedMessage = false;
if (currentMessage != null) { if (currentMessage != null) {
currentMessage.release();
currentMessage = null;
throw new MessageAggregationException(); throw new MessageAggregationException();
} }
@ -239,11 +244,10 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
if (m instanceof ByteBufHolder && ((ByteBufHolder) m).content().isReadable()) { if (m instanceof ByteBufHolder && ((ByteBufHolder) m).content().isReadable()) {
aggregated = beginAggregation(m, ((ByteBufHolder) m).content().retain()); aggregated = beginAggregation(m, ((ByteBufHolder) m).content().retain());
} else { } else {
aggregated = beginAggregation(m, Unpooled.EMPTY_BUFFER); aggregated = beginAggregation(m, EMPTY_BUFFER);
} }
finishAggregation(aggregated); finishAggregation(aggregated);
out.add(aggregated); out.add(aggregated);
this.currentMessage = null;
return; return;
} }
@ -252,30 +256,21 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
if (m instanceof ByteBufHolder) { if (m instanceof ByteBufHolder) {
appendPartialContent(content, ((ByteBufHolder) m).content()); appendPartialContent(content, ((ByteBufHolder) m).content());
} }
this.currentMessage = beginAggregation(m, content); currentMessage = beginAggregation(m, content);
} else if (isContentMessage(msg)) { } else if (isContentMessage(msg)) {
@SuppressWarnings("unchecked")
final C m = (C) msg;
final ByteBuf partialContent = ((ByteBufHolder) msg).content();
final boolean isLastContentMessage = isLastContentMessage(m);
if (handlingOversizedMessage) {
if (isLastContentMessage) {
this.currentMessage = null;
}
// already detect the too long frame so just discard the content
return;
}
if (currentMessage == null) { if (currentMessage == null) {
throw new MessageAggregationException(); // it is possible that a TooLongFrameException was already thrown but we can still discard data
// until the begging of the next request/response.
return;
} }
// Merge the received chunk into the content of the current message. // Merge the received chunk into the content of the current message.
CompositeByteBuf content = (CompositeByteBuf) currentMessage.content(); CompositeByteBuf content = (CompositeByteBuf) currentMessage.content();
@SuppressWarnings("unchecked")
final C m = (C) msg;
// Handle oversized message. // Handle oversized message.
if (content.readableBytes() > maxContentLength - partialContent.readableBytes()) { if (content.readableBytes() > maxContentLength - m.content().readableBytes()) {
// By convention, full message type extends first message type. // By convention, full message type extends first message type.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
S s = (S) currentMessage; S s = (S) currentMessage;
@ -284,7 +279,7 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
} }
// Append the content of the chunk. // Append the content of the chunk.
appendPartialContent(content, partialContent); appendPartialContent(content, m.content());
// Give the subtypes a chance to merge additional information such as trailing headers. // Give the subtypes a chance to merge additional information such as trailing headers.
aggregate(currentMessage, m); aggregate(currentMessage, m);
@ -299,10 +294,10 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
} }
last = true; last = true;
} else { } else {
last = isLastContentMessage; last = isLastContentMessage(m);
} }
} else { } else {
last = isLastContentMessage; last = isLastContentMessage(m);
} }
if (last) { if (last) {
@ -310,7 +305,7 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
// All done // All done
out.add(currentMessage); out.add(currentMessage);
this.currentMessage = null; currentMessage = null;
} }
} else { } else {
throw new MessageAggregationException(); throw new MessageAggregationException();
@ -319,8 +314,7 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
private static void appendPartialContent(CompositeByteBuf content, ByteBuf partialContent) { private static void appendPartialContent(CompositeByteBuf content, ByteBuf partialContent) {
if (partialContent.isReadable()) { if (partialContent.isReadable()) {
partialContent.retain(); content.addComponent(true, partialContent.retain());
content.addComponent(true, partialContent);
} }
} }
@ -344,10 +338,11 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
throws Exception; throws Exception;
/** /**
* Determine if the channel should be closed after the result of {@link #newContinueResponse(Object)} is written. * Determine if the channel should be closed after the result of
* @param msg The return value from {@link #newContinueResponse(Object)}. * {@link #newContinueResponse(Object, int, ChannelPipeline)} is written.
* @return {@code true} if the channel should be closed after the result of {@link #newContinueResponse(Object)} * @param msg The return value from {@link #newContinueResponse(Object, int, ChannelPipeline)}.
* is written. {@code false} otherwise. * @return {@code true} if the channel should be closed after the result of
* {@link #newContinueResponse(Object, int, ChannelPipeline)} is written. {@code false} otherwise.
*/ */
protected abstract boolean closeAfterContinueResponse(Object msg) throws Exception; protected abstract boolean closeAfterContinueResponse(Object msg) throws Exception;
@ -355,7 +350,7 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
* Determine if all objects for the current request/response should be ignored or not. * Determine if all objects for the current request/response should be ignored or not.
* Messages will stop being ignored the next time {@link #isContentMessage(Object)} returns {@code true}. * Messages will stop being ignored the next time {@link #isContentMessage(Object)} returns {@code true}.
* *
* @param msg The return value from {@link #newContinueResponse(Object)}. * @param msg The return value from {@link #newContinueResponse(Object, int, ChannelPipeline)}.
* @return {@code true} if all objects for the current request/response should be ignored or not. * @return {@code true} if all objects for the current request/response should be ignored or not.
* {@code false} otherwise. * {@code false} otherwise.
*/ */
@ -406,13 +401,12 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
try {
// release current message if it is not null as it may be a left-over // release current message if it is not null as it may be a left-over
if (currentMessage != null) {
currentMessage.release();
currentMessage = null;
}
super.channelInactive(ctx); super.channelInactive(ctx);
} finally {
releaseCurrentMessage();
}
} }
@Override @Override
@ -422,13 +416,20 @@ public abstract class MessageAggregator<I, S, C extends ByteBufHolder, O extends
@Override @Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
try {
super.handlerRemoved(ctx); super.handlerRemoved(ctx);
} finally {
// release current message if it is not null as it may be a left-over as there is not much more we can do in // release current message if it is not null as it may be a left-over as there is not much more we can do in
// this case // this case
releaseCurrentMessage();
}
}
private void releaseCurrentMessage() {
if (currentMessage != null) { if (currentMessage != null) {
currentMessage.release(); currentMessage.release();
currentMessage = null; currentMessage = null;
handlingOversizedMessage = false;
} }
} }
} }