Skip to content

Commit 535da17

Browse files
authored
Merge pull request from GHSA-6mjq-h674-j845
Motivation: In theory the ClientHello can span multiple records and so reach the length of 16MB. This can result in high memory usage, we should allow the user to define a maximum length. Modifications: Add new constructor which allows to limit the maximum length of a ClientHello message. Result: Be able to guard against high memory usage when parsing ClientHello messages
1 parent 1bb825b commit 535da17

File tree

4 files changed

+127
-11
lines changed

4 files changed

+127
-11
lines changed

Diff for: handler/src/main/java/io/netty/handler/ssl/AbstractSniHandler.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,23 @@ private static String extractSniHostname(ByteBuf in) {
126126
private String hostname;
127127

128128
/**
129-
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
129+
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
130130
*/
131131
protected AbstractSniHandler(long handshakeTimeoutMillis) {
132+
this(0, handshakeTimeoutMillis);
133+
}
134+
135+
/**
136+
* @paramm maxClientHelloLength the maximum length of the client hello message.
137+
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
138+
*/
139+
protected AbstractSniHandler(int maxClientHelloLength, long handshakeTimeoutMillis) {
140+
super(maxClientHelloLength);
132141
this.handshakeTimeoutMillis = checkPositiveOrZero(handshakeTimeoutMillis, "handshakeTimeoutMillis");
133142
}
134143

135144
public AbstractSniHandler() {
136-
this(0L);
145+
this(0, 0L);
137146
}
138147

139148
@Override

Diff for: handler/src/main/java/io/netty/handler/ssl/SniHandler.java

+31-5
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ public SniHandler(Mapping<? super String, ? extends SslContext> mapping) {
5656
* maintained by {@link Mapping}
5757
*
5858
* @param mapping the mapping of domain name to {@link SslContext}
59+
* @param maxClientHelloLength the maximum length of the client hello message
5960
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
6061
*/
61-
public SniHandler(Mapping<? super String, ? extends SslContext> mapping, long handshakeTimeoutMillis) {
62-
this(new AsyncMappingAdapter(mapping), handshakeTimeoutMillis);
62+
public SniHandler(Mapping<? super String, ? extends SslContext> mapping,
63+
int maxClientHelloLength, long handshakeTimeoutMillis) {
64+
this(new AsyncMappingAdapter(mapping), maxClientHelloLength, handshakeTimeoutMillis);
6365
}
6466

6567
/**
@@ -80,22 +82,46 @@ public SniHandler(DomainNameMapping<? extends SslContext> mapping) {
8082
*/
8183
@SuppressWarnings("unchecked")
8284
public SniHandler(AsyncMapping<? super String, ? extends SslContext> mapping) {
83-
this(mapping, 0L);
85+
this(mapping, 0, 0L);
8486
}
8587

8688
/**
8789
* Creates a SNI detection handler with configured {@link SslContext}
8890
* maintained by {@link AsyncMapping}
8991
*
9092
* @param mapping the mapping of domain name to {@link SslContext}
93+
* @param maxClientHelloLength the maximum length of the client hello message
9194
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
9295
*/
9396
@SuppressWarnings("unchecked")
94-
public SniHandler(AsyncMapping<? super String, ? extends SslContext> mapping, long handshakeTimeoutMillis) {
95-
super(handshakeTimeoutMillis);
97+
public SniHandler(AsyncMapping<? super String, ? extends SslContext> mapping,
98+
int maxClientHelloLength, long handshakeTimeoutMillis) {
99+
super(maxClientHelloLength, handshakeTimeoutMillis);
96100
this.mapping = (AsyncMapping<String, SslContext>) ObjectUtil.checkNotNull(mapping, "mapping");
97101
}
98102

103+
/**
104+
* Creates a SNI detection handler with configured {@link SslContext}
105+
* maintained by {@link Mapping}
106+
*
107+
* @param mapping the mapping of domain name to {@link SslContext}
108+
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
109+
*/
110+
public SniHandler(Mapping<? super String, ? extends SslContext> mapping, long handshakeTimeoutMillis) {
111+
this(new AsyncMappingAdapter(mapping), handshakeTimeoutMillis);
112+
}
113+
114+
/**
115+
* Creates a SNI detection handler with configured {@link SslContext}
116+
* maintained by {@link AsyncMapping}
117+
*
118+
* @param mapping the mapping of domain name to {@link SslContext}
119+
* @param handshakeTimeoutMillis the handshake timeout in milliseconds
120+
*/
121+
public SniHandler(AsyncMapping<? super String, ? extends SslContext> mapping, long handshakeTimeoutMillis) {
122+
this(mapping, 0, handshakeTimeoutMillis);
123+
}
124+
99125
/**
100126
* @return the selected hostname
101127
*/

Diff for: handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java

+32
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import io.netty.channel.ChannelPromise;
2323
import io.netty.handler.codec.ByteToMessageDecoder;
2424
import io.netty.handler.codec.DecoderException;
25+
import io.netty.handler.codec.TooLongFrameException;
2526
import io.netty.util.concurrent.Future;
2627
import io.netty.util.concurrent.FutureListener;
28+
import io.netty.util.internal.ObjectUtil;
2729
import io.netty.util.internal.PlatformDependent;
2830
import io.netty.util.internal.logging.InternalLogger;
2931
import io.netty.util.internal.logging.InternalLoggerFactory;
@@ -36,14 +38,32 @@
3638
*/
3739
public abstract class SslClientHelloHandler<T> extends ByteToMessageDecoder implements ChannelOutboundHandler {
3840

41+
/**
42+
* The maximum length of client hello message as defined by
43+
* <a href="https://www.rfc-editor.org/rfc/rfc5246#section-6.2.1">RFC5246</a>.
44+
*/
45+
public static final int MAX_CLIENT_HELLO_LENGTH = 0xFFFFFF;
46+
3947
private static final InternalLogger logger =
4048
InternalLoggerFactory.getInstance(SslClientHelloHandler.class);
4149

50+
private final int maxClientHelloLength;
4251
private boolean handshakeFailed;
4352
private boolean suppressRead;
4453
private boolean readPending;
4554
private ByteBuf handshakeBuffer;
4655

56+
public SslClientHelloHandler() {
57+
this(MAX_CLIENT_HELLO_LENGTH);
58+
}
59+
60+
protected SslClientHelloHandler(int maxClientHelloLength) {
61+
// 16MB is the maximum as per RFC:
62+
// See https://www.rfc-editor.org/rfc/rfc5246#section-6.2.1
63+
this.maxClientHelloLength =
64+
ObjectUtil.checkInRange(maxClientHelloLength, 0, MAX_CLIENT_HELLO_LENGTH, "maxClientHelloLength");
65+
}
66+
4767
@Override
4868
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
4969
if (!suppressRead && !handshakeFailed) {
@@ -117,6 +137,15 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) t
117137
handshakeLength = in.getUnsignedMedium(readerIndex +
118138
SslUtils.SSL_RECORD_HEADER_LENGTH + 1);
119139

140+
if (handshakeLength > maxClientHelloLength && maxClientHelloLength != 0) {
141+
TooLongFrameException e = new TooLongFrameException(
142+
"ClientHello length exceeds " + maxClientHelloLength +
143+
": " + handshakeLength);
144+
in.skipBytes(in.readableBytes());
145+
ctx.fireUserEventTriggered(new SniCompletionEvent(e));
146+
SslUtils.handleHandshakeFailure(ctx, e, true);
147+
throw e;
148+
}
120149
// Consume handshakeType and handshakeLength (this sums up as 4 bytes)
121150
readerIndex += 4;
122151
packetLength -= 4;
@@ -161,6 +190,9 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) t
161190
} catch (NotSslRecordException e) {
162191
// Just rethrow as in this case we also closed the channel and this is consistent with SslHandler.
163192
throw e;
193+
} catch (TooLongFrameException e) {
194+
// Just rethrow as in this case we also closed the channel
195+
throw e;
164196
} catch (Exception e) {
165197
// unexpected encoding, ignore sni and use default
166198
if (logger.isDebugEnabled()) {

Diff for: handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java

+53-4
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@
2525
import java.util.concurrent.atomic.AtomicBoolean;
2626
import java.util.concurrent.atomic.AtomicReference;
2727

28-
import javax.net.ssl.HandshakeCompletedEvent;
29-
import javax.net.ssl.SSLContext;
3028
import javax.net.ssl.SSLEngine;
3129
import javax.net.ssl.SSLException;
3230

31+
import io.netty.handler.codec.TooLongFrameException;
3332
import io.netty.util.concurrent.Future;
3433

3534
import io.netty.bootstrap.Bootstrap;
@@ -715,14 +714,64 @@ private static List<ByteBuf> split(ByteBuf clientHello, int maxSize) {
715714
return result;
716715
}
717716

717+
@Test
718+
public void testSniHandlerFailsOnTooBigClientHello() throws Exception {
719+
SniHandler handler = new SniHandler(new Mapping<String, SslContext>() {
720+
@Override
721+
public SslContext map(String input) {
722+
throw new UnsupportedOperationException("Should not be called");
723+
}
724+
}, 10, 0);
725+
726+
final AtomicReference<SniCompletionEvent> completionEventRef =
727+
new AtomicReference<SniCompletionEvent>();
728+
final EmbeddedChannel ch = new EmbeddedChannel(handler, new ChannelInboundHandlerAdapter() {
729+
@Override
730+
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
731+
if (evt instanceof SniCompletionEvent) {
732+
completionEventRef.set((SniCompletionEvent) evt);
733+
}
734+
}
735+
});
736+
final ByteBuf buffer = ch.alloc().buffer();
737+
buffer.writeByte(0x16); // Content Type: Handshake
738+
buffer.writeShort((short) 0x0303); // TLS 1.2
739+
buffer.writeShort((short) 0x0006); // Packet length
740+
741+
// 16_777_215
742+
buffer.writeByte((byte) 0x01); // Client Hello
743+
buffer.writeMedium(0xFFFFFF); // Length
744+
buffer.writeShort((short) 0x0303); // TLS 1.2
745+
746+
assertThrows(TooLongFrameException.class, new Executable() {
747+
@Override
748+
public void execute() throws Throwable {
749+
ch.writeInbound(buffer);
750+
}
751+
});
752+
try {
753+
while (completionEventRef.get() == null) {
754+
Thread.sleep(100);
755+
// We need to run all pending tasks as the handshake timeout is scheduled on the EventLoop.
756+
ch.runPendingTasks();
757+
}
758+
SniCompletionEvent completionEvent = completionEventRef.get();
759+
assertNotNull(completionEvent);
760+
assertNotNull(completionEvent.cause());
761+
assertEquals(TooLongFrameException.class, completionEvent.cause().getClass());
762+
} finally {
763+
ch.finishAndReleaseAll();
764+
}
765+
}
766+
718767
@Test
719768
public void testSniHandlerFiresHandshakeTimeout() throws Exception {
720769
SniHandler handler = new SniHandler(new Mapping<String, SslContext>() {
721770
@Override
722771
public SslContext map(String input) {
723772
throw new UnsupportedOperationException("Should not be called");
724773
}
725-
}, 10);
774+
}, 0, 10);
726775

727776
final AtomicReference<SniCompletionEvent> completionEventRef =
728777
new AtomicReference<SniCompletionEvent>();
@@ -758,7 +807,7 @@ public void testSslHandlerFiresHandshakeTimeout(SslProvider provider) throws Exc
758807
public SslContext map(String input) {
759808
return context;
760809
}
761-
}, 100);
810+
}, 0, 100);
762811

763812
final AtomicReference<SniCompletionEvent> sniCompletionEventRef =
764813
new AtomicReference<SniCompletionEvent>();

0 commit comments

Comments
 (0)