Issue #3978 - HTTP/2 vulnerabilities.

Code cleanups and reformatting.

Fixed logic for SETTINGS frame replies: they are not subject to rate control.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2019-09-05 23:11:53 +02:00
parent 53fc01793c
commit 508ad4aff9
12 changed files with 83 additions and 59 deletions

View File

@ -27,7 +27,6 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -153,7 +152,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet() start(new HttpServlet()
{ {
@Override @Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
{ {
resp.getOutputStream().write(content); resp.getOutputStream().write(content);
} }
@ -201,7 +200,7 @@ public class HTTP2Test extends AbstractTest
start(new EmptyHttpServlet() start(new EmptyHttpServlet()
{ {
@Override @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{ {
IO.copy(request.getInputStream(), response.getOutputStream()); IO.copy(request.getInputStream(), response.getOutputStream());
} }
@ -245,7 +244,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet() start(new HttpServlet()
{ {
@Override @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{ {
int download = request.getIntHeader(downloadBytes); int download = request.getIntHeader(downloadBytes);
byte[] content = new byte[download]; byte[] content = new byte[download];
@ -288,7 +287,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet() start(new HttpServlet()
{ {
@Override @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException protected void service(HttpServletRequest request, HttpServletResponse response)
{ {
response.setStatus(status); response.setStatus(status);
} }
@ -323,7 +322,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet() start(new HttpServlet()
{ {
@Override @Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException protected void service(HttpServletRequest request, HttpServletResponse response)
{ {
assertEquals(host, request.getServerName()); assertEquals(host, request.getServerName());
assertEquals(port, request.getServerPort()); assertEquals(port, request.getServerPort());

View File

@ -57,6 +57,7 @@ import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.Atomics; import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CountingCallback; import org.eclipse.jetty.util.CountingCallback;
import org.eclipse.jetty.util.MathUtils;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.Retainable; import org.eclipse.jetty.util.Retainable;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -464,7 +465,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
if (stream != null) if (stream != null)
{ {
int streamSendWindow = stream.updateSendWindow(0); int streamSendWindow = stream.updateSendWindow(0);
if (sumOverflows(streamSendWindow, windowDelta)) if (MathUtils.sumOverflows(streamSendWindow, windowDelta))
{ {
reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP); reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP);
} }
@ -483,7 +484,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
else else
{ {
int sessionSendWindow = updateSendWindow(0); int sessionSendWindow = updateSendWindow(0);
if (sumOverflows(sessionSendWindow, windowDelta)) if (MathUtils.sumOverflows(sessionSendWindow, windowDelta))
onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window"); onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
else else
onWindowUpdate(null, frame); onWindowUpdate(null, frame);
@ -501,19 +502,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
callback.succeeded(); callback.succeeded();
} }
private boolean sumOverflows(int a, int b)
{
try
{
Math.addExact(a, b);
return false;
}
catch (ArithmeticException x)
{
return true;
}
}
@Override @Override
public void onConnectionFailure(int error, String reason) public void onConnectionFailure(int error, String reason)
{ {

View File

@ -33,13 +33,12 @@ public class HeaderParser
private final RateControl rateControl; private final RateControl rateControl;
private State state = State.LENGTH; private State state = State.LENGTH;
private int cursor; private int cursor;
private int length; private int length;
private int type; private int type;
private int flags; private int flags;
private int streamId; private int streamId;
HeaderParser(RateControl rateControl) public HeaderParser(RateControl rateControl)
{ {
this.rateControl = rateControl; this.rateControl = rateControl;
} }

View File

@ -61,7 +61,7 @@ public class Parser
public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize) public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize)
{ {
this (byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, RateControl.NO_RATE_CONTROL); this(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, RateControl.NO_RATE_CONTROL);
} }
public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl) public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl)
@ -76,7 +76,6 @@ public class Parser
public void init(UnaryOperator<Listener> wrapper) public void init(UnaryOperator<Listener> wrapper)
{ {
Listener listener = wrapper.apply(this.listener); Listener listener = wrapper.apply(this.listener);
RateControl rateControl = getRateControl();
unknownBodyParser = new UnknownBodyParser(headerParser, listener); unknownBodyParser = new UnknownBodyParser(headerParser, listener);
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser); HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(); HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments();
@ -240,11 +239,6 @@ public class Parser
this.maxSettingsKeys = maxSettingsKeys; this.maxSettingsKeys = maxSettingsKeys;
} }
public RateControl getRateControl()
{
return headerParser.getRateControl();
}
protected void notifyConnectionFailure(int error, String reason) protected void notifyConnectionFailure(int error, String reason)
{ {
try try

View File

@ -23,7 +23,7 @@ package org.eclipse.jetty.http2.parser;
*/ */
public interface RateControl public interface RateControl
{ {
RateControl NO_RATE_CONTROL = event -> true; public static final RateControl NO_RATE_CONTROL = event -> true;
/** /**
* <p>Applications should call this method when they want to signal an * <p>Applications should call this method when they want to signal an

View File

@ -73,8 +73,9 @@ public class SettingsBodyParser extends BodyParser
@Override @Override
protected void emptyBody(ByteBuffer buffer) protected void emptyBody(ByteBuffer buffer)
{ {
SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK)); boolean isReply = hasFlag(Flags.ACK);
if (!rateControlOnEvent(frame)) SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), isReply);
if (!isReply && !rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate"); connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
else else
onSettings(frame); onSettings(frame);

View File

@ -176,16 +176,17 @@ public class HpackDecoder
name = Huffman.decode(buffer, length); name = Huffman.decode(buffer, length);
else else
name = toASCIIString(buffer, length); name = toASCIIString(buffer, length);
check: for (int i = name.length(); i-- > 0;) check:
for (int i = name.length(); i-- > 0;)
{ {
char c = name.charAt(i); char c = name.charAt(i);
if (c>0xff) if (c > 0xff)
{ {
_builder.streamException("Illegal header name %s", name); _builder.streamException("Illegal header name %s", name);
break check; break;
} }
HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c]; HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c];
switch(token.getType()) switch (token.getType())
{ {
case ALPHA: case ALPHA:
if (c >= 'A' && c <= 'Z') if (c >= 'A' && c <= 'Z')

View File

@ -115,7 +115,7 @@ public class MetaDataBuilder
{ {
case C_STATUS: case C_STATUS:
if (checkPseudoHeader(header, _status)) if (checkPseudoHeader(header, _status))
_status = Integer.valueOf(field.getIntValue()); _status = field.getIntValue();
_response = true; _response = true;
break; break;

View File

@ -384,7 +384,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "")); mdb.emit(new HttpField(HttpHeader.C_PATH, ""));
StreamException ex = assertThrows(StreamException.class, () -> mdb.build()); StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Path")); assertThat(ex.getMessage(), Matchers.containsString("No Path"));
} }
@ -394,7 +394,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
StreamException ex = assertThrows(StreamException.class, () -> mdb.build()); StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Method")); assertThat(ex.getMessage(), Matchers.containsString("No Method"));
} }
@ -404,7 +404,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
StreamException ex = assertThrows(StreamException.class, () -> mdb.build()); StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Scheme")); assertThat(ex.getMessage(), Matchers.containsString("No Scheme"));
} }
@ -414,7 +414,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET"));
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
StreamException ex = assertThrows(StreamException.class, () -> mdb.build()); StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("No Path")); assertThat(ex.getMessage(), Matchers.containsString("No Path"));
} }
@ -426,7 +426,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
StreamException ex = assertThrows(StreamException.class, () -> mdb.build()); StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("Duplicate")); assertThat(ex.getMessage(), Matchers.containsString("Duplicate"));
} }
@ -439,7 +439,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); mdb.emit(new HttpField(HttpHeader.C_PATH, "/"));
StreamException ex = assertThrows(StreamException.class, () -> mdb.build()); StreamException ex = assertThrows(StreamException.class, mdb::build);
assertThat(ex.getMessage(), Matchers.containsString("Duplicate")); assertThat(ex.getMessage(), Matchers.containsString("Duplicate"));
} }
} }
@ -463,7 +463,7 @@ public class HpackDecoderTest
/* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */ /* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */
@Test @Test
public void testHuffmanEncodedExtraPadding() throws Exception public void testHuffmanEncodedExtraPadding()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -475,7 +475,7 @@ public class HpackDecoderTest
/* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */ /* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */
@Test @Test
public void testHuffmanEncodedZeroPadding() throws Exception public void testHuffmanEncodedZeroPadding()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -488,7 +488,7 @@ public class HpackDecoderTest
/* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */ /* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */
@Test @Test
public void testHuffmanEncodedWithEOS() throws Exception public void testHuffmanEncodedWithEOS()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -500,7 +500,7 @@ public class HpackDecoderTest
} }
@Test @Test
public void testHuffmanEncodedOneIncompleteOctet() throws Exception public void testHuffmanEncodedOneIncompleteOctet()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -512,7 +512,7 @@ public class HpackDecoderTest
} }
@Test @Test
public void testHuffmanEncodedTwoIncompleteOctet() throws Exception public void testHuffmanEncodedTwoIncompleteOctet()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -524,7 +524,7 @@ public class HpackDecoderTest
} }
@Test @Test
public void testZeroLengthName() throws Exception public void testZeroLengthName()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -547,7 +547,7 @@ public class HpackDecoderTest
} }
@Test @Test
public void testUpperCaseName() throws Exception public void testUpperCaseName()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);
@ -558,7 +558,7 @@ public class HpackDecoderTest
} }
@Test @Test
public void testWhiteSpaceName() throws Exception public void testWhiteSpaceName()
{ {
HpackDecoder decoder = new HpackDecoder(4096, 8192); HpackDecoder decoder = new HpackDecoder(4096, 8192);

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.http2.client.http; package org.eclipse.jetty.http2.client.http;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.ServerSocket; import java.net.ServerSocket;
@ -38,7 +37,6 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -188,7 +186,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler() start(new AbstractHandler()
{ {
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{ {
baseRequest.setHandled(true); baseRequest.setHandled(true);
HttpVersion version = HttpVersion.fromString(request.getProtocol()); HttpVersion version = HttpVersion.fromString(request.getProtocol());
@ -314,7 +312,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler() start(new AbstractHandler()
{ {
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{ {
baseRequest.setHandled(true); baseRequest.setHandled(true);
assertEquals(path, request.getRequestURI()); assertEquals(path, request.getRequestURI());
@ -338,7 +336,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler() start(new AbstractHandler()
{ {
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{ {
baseRequest.setHandled(true); baseRequest.setHandled(true);
assertEquals(path, request.getRequestURI()); assertEquals(path, request.getRequestURI());

View File

@ -49,7 +49,6 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PingFrame; import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
@ -75,7 +74,6 @@ public class MaxConcurrentStreamsTest extends AbstractTest
{ {
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration()); HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration());
http2.setMaxConcurrentStreams(maxConcurrentStreams); http2.setMaxConcurrentStreams(maxConcurrentStreams);
http2.setRateControl(RateControl.NO_RATE_CONTROL);
prepareServer(http2); prepareServer(http2);
server.setHandler(handler); server.setHandler(handler);
server.start(); server.start();

View File

@ -0,0 +1,46 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
public class MathUtils
{
private MathUtils()
{
}
/**
* Returns whether the sum of the arguments overflows an {@code int}.
*
* @param a the first value
* @param b the second value
* @return whether the sum of the arguments overflows an {@code int}
*/
public static boolean sumOverflows(int a, int b)
{
try
{
Math.addExact(a, b);
return false;
}
catch (ArithmeticException x)
{
return true;
}
}
}