From cfe1baa048296e90f95773db680ca4d5ef24e0a2 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Sat, 17 Aug 2019 22:51:39 +0200
Subject: [PATCH 1/8] Issue #3978 - HTTP/2 vulnerabilities.
Implemented rate control for HTTP/2 frames using a single RateControl
object to avoid that each individual vulnerability is within limits,
but combined they still overload the server.
Signed-off-by: Simone Bordet
---
.../org/eclipse/jetty/http2/HTTP2Session.java | 46 ++---
.../jetty/http2/frames/ContinuationFrame.java | 48 ++++++
.../jetty/http2/frames/UnknownFrame.java | 36 ++++
.../jetty/http2/parser/BodyParser.java | 8 +-
.../http2/parser/ContinuationBodyParser.java | 13 +-
.../jetty/http2/parser/DataBodyParser.java | 20 ++-
.../jetty/http2/parser/HeadersBodyParser.java | 31 +++-
.../eclipse/jetty/http2/parser/Parser.java | 29 +++-
.../jetty/http2/parser/PingBodyParser.java | 12 +-
.../http2/parser/PriorityBodyParser.java | 10 +-
.../jetty/http2/parser/RateControl.java | 37 ++++
.../http2/parser/SettingsBodyParser.java | 51 +++---
.../jetty/http2/parser/UnknownBodyParser.java | 17 +-
.../jetty/http2/parser/WindowRateControl.java | 61 +++++++
.../http2/parser/WindowUpdateBodyParser.java | 16 +-
.../jetty/http2/frames/FrameFloodTest.java | 162 ++++++++++++++++++
.../AbstractHTTP2ServerConnectionFactory.java | 15 ++
17 files changed, 523 insertions(+), 89 deletions(-)
create mode 100644 jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java
create mode 100644 jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java
create mode 100644 jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
create mode 100644 jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
create mode 100644 jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
index 770f1d07f28..1908884b48c 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
@@ -460,47 +460,33 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
int windowDelta = frame.getWindowDelta();
if (streamId > 0)
{
- if (windowDelta == 0)
+ IStream stream = getStream(streamId);
+ if (stream != null)
{
- reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
- }
- else
- {
- IStream stream = getStream(streamId);
- if (stream != null)
+ int streamSendWindow = stream.updateSendWindow(0);
+ if (sumOverflows(streamSendWindow, windowDelta))
{
- int streamSendWindow = stream.updateSendWindow(0);
- if (sumOverflows(streamSendWindow, windowDelta))
- {
- reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP);
- }
- else
- {
- stream.process(frame, Callback.NOOP);
- onWindowUpdate(stream, frame);
- }
+ reset(new ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), Callback.NOOP);
}
else
{
- if (!isRemoteStreamClosed(streamId))
- onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame");
+ stream.process(frame, Callback.NOOP);
+ onWindowUpdate(stream, frame);
}
}
+ else
+ {
+ if (!isRemoteStreamClosed(streamId))
+ onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame");
+ }
}
else
{
- if (windowDelta == 0)
- {
- onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
- }
+ int sessionSendWindow = updateSendWindow(0);
+ if (sumOverflows(sessionSendWindow, windowDelta))
+ onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
else
- {
- int sessionSendWindow = updateSendWindow(0);
- if (sumOverflows(sessionSendWindow, windowDelta))
- onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
- else
- onWindowUpdate(null, frame);
- }
+ onWindowUpdate(null, frame);
}
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java
new file mode 100644
index 00000000000..39d69310ce8
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ContinuationFrame.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// 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.http2.frames;
+
+public class ContinuationFrame extends Frame
+{
+ private final int streamId;
+ private final boolean endHeaders;
+
+ public ContinuationFrame(int streamId, boolean endHeaders)
+ {
+ super(FrameType.CONTINUATION);
+ this.streamId = streamId;
+ this.endHeaders = endHeaders;
+ }
+
+ public int getStreamId()
+ {
+ return streamId;
+ }
+
+ public boolean isEndHeaders()
+ {
+ return endHeaders;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s#%d{end=%b}", super.toString(), getStreamId(), isEndHeaders());
+ }
+}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java
new file mode 100644
index 00000000000..ffd88682b2a
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/UnknownFrame.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// 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.http2.frames;
+
+public class UnknownFrame extends Frame
+{
+ private final int frameType;
+
+ public UnknownFrame(int frameType)
+ {
+ super(null);
+ this.frameType = frameType;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s,t=%d", super.toString(), frameType);
+ }
+}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java
index 82d18f96a84..c85aa9ba8bf 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java
@@ -96,6 +96,11 @@ public abstract class BodyParser
return headerParser.getLength();
}
+ protected int getFrameType()
+ {
+ return headerParser.getFrameType();
+ }
+
protected void notifyData(DataFrame frame)
{
try
@@ -223,9 +228,10 @@ public abstract class BodyParser
}
}
- protected void streamFailure(int streamId, int error, String reason)
+ protected boolean streamFailure(int streamId, int error, String reason)
{
notifyStreamFailure(streamId, error, reason);
+ return false;
}
private void notifyStreamFailure(int streamId, int error, String reason)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
index 65e47d1c827..b14cabf4e7d 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
@@ -23,27 +23,38 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.Flags;
+import org.eclipse.jetty.http2.frames.ContinuationFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
public class ContinuationBodyParser extends BodyParser
{
private final HeaderBlockParser headerBlockParser;
private final HeaderBlockFragments headerBlockFragments;
+ private final RateControl rateControl;
private State state = State.PREPARE;
private int length;
- public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments)
+ public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments, RateControl rateControl)
{
super(headerParser, listener);
this.headerBlockParser = headerBlockParser;
this.headerBlockFragments = headerBlockFragments;
+ this.rateControl = rateControl;
}
@Override
protected void emptyBody(ByteBuffer buffer)
{
if (hasFlag(Flags.END_HEADERS))
+ {
onHeaders();
+ }
+ else
+ {
+ ContinuationFrame frame = new ContinuationFrame(getStreamId(), hasFlag(Flags.END_HEADERS));
+ if (rateControl != null && !rateControl.onEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
+ }
}
@Override
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
index ac9e7bab991..5ea1a5bca81 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
@@ -26,14 +26,16 @@ import org.eclipse.jetty.util.BufferUtil;
public class DataBodyParser extends BodyParser
{
+ private final RateControl rateControl;
private State state = State.PREPARE;
private int padding;
private int paddingLength;
private int length;
- public DataBodyParser(HeaderParser headerParser, Parser.Listener listener)
+ public DataBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
{
super(headerParser, listener);
+ this.rateControl = rateControl;
}
private void reset()
@@ -48,9 +50,17 @@ public class DataBodyParser extends BodyParser
protected void emptyBody(ByteBuffer buffer)
{
if (isPadding())
+ {
connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame");
+ }
else
- onData(BufferUtil.EMPTY_BUFFER, false, 0);
+ {
+ DataFrame frame = new DataFrame(getStreamId(), BufferUtil.EMPTY_BUFFER, isEndStream());
+ if (!isEndStream() && rateControl != null && !rateControl.onEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_data_frame_rate");
+ else
+ onData(frame);
+ }
}
@Override
@@ -134,7 +144,11 @@ public class DataBodyParser extends BodyParser
private void onData(ByteBuffer buffer, boolean fragment, int padding)
{
- DataFrame frame = new DataFrame(getStreamId(), buffer, !fragment && isEndStream(), padding);
+ onData(new DataFrame(getStreamId(), buffer, !fragment && isEndStream(), padding));
+ }
+
+ private void onData(DataFrame frame)
+ {
notifyData(frame);
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java
index febdefb6c25..9c53eed6a9e 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java
@@ -32,6 +32,7 @@ public class HeadersBodyParser extends BodyParser
{
private final HeaderBlockParser headerBlockParser;
private final HeaderBlockFragments headerBlockFragments;
+ private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private int length;
@@ -40,11 +41,12 @@ public class HeadersBodyParser extends BodyParser
private int parentStreamId;
private int weight;
- public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments)
+ public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments, RateControl rateControl)
{
super(headerParser, listener);
this.headerBlockParser = headerBlockParser;
this.headerBlockFragments = headerBlockFragments;
+ this.rateControl = rateControl;
}
private void reset()
@@ -61,17 +63,23 @@ public class HeadersBodyParser extends BodyParser
@Override
protected void emptyBody(ByteBuffer buffer)
{
- if (hasFlag(Flags.END_HEADERS))
+ if (hasFlag(Flags.PRIORITY))
+ {
+ connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_priority_frame");
+ }
+ else if (hasFlag(Flags.END_HEADERS))
{
MetaData metaData = headerBlockParser.parse(BufferUtil.EMPTY_BUFFER, 0);
- onHeaders(0, 0, false, metaData);
+ HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
+ if (rateControl != null && !rateControl.onEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
+ else
+ onHeaders(frame);
}
else
{
headerBlockFragments.setStreamId(getStreamId());
headerBlockFragments.setEndStream(isEndStream());
- if (hasFlag(Flags.PRIORITY))
- connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_priority_frame");
}
}
@@ -179,7 +187,15 @@ public class HeadersBodyParser extends BodyParser
state = State.PADDING;
loop = paddingLength == 0;
if (metaData != HeaderBlockParser.STREAM_FAILURE)
+ {
onHeaders(parentStreamId, weight, exclusive, metaData);
+ }
+ else
+ {
+ HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
+ if (rateControl != null && !rateControl.onEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
+ }
}
}
else
@@ -230,6 +246,11 @@ public class HeadersBodyParser extends BodyParser
if (hasFlag(Flags.PRIORITY))
priorityFrame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, priorityFrame, isEndStream());
+ onHeaders(frame);
+ }
+
+ private void onHeaders(HeadersFrame frame)
+ {
notifyHeaders(frame);
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
index 15920bebcdd..3b0efca7c20 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
@@ -54,8 +54,9 @@ public class Parser
private final HpackDecoder hpackDecoder;
private final BodyParser[] bodyParsers;
private UnknownBodyParser unknownBodyParser;
- private int maxFrameLength;
+ private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
+ private RateControl rateControl;
private boolean continuation;
private State state = State.HEADER;
@@ -65,26 +66,26 @@ public class Parser
this.listener = listener;
this.headerParser = new HeaderParser();
this.hpackDecoder = new HpackDecoder(maxDynamicTableSize, maxHeaderSize);
- this.maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
this.bodyParsers = new BodyParser[FrameType.values().length];
}
public void init(UnaryOperator wrapper)
{
Listener listener = wrapper.apply(this.listener);
- unknownBodyParser = new UnknownBodyParser(headerParser, listener);
+ RateControl rateControl = getRateControl();
+ unknownBodyParser = new UnknownBodyParser(headerParser, listener, rateControl);
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments();
- bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener);
- bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
- bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);
+ bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener, rateControl);
+ bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments, rateControl);
+ bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener, rateControl);
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
- bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys());
+ bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys(), rateControl);
bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser);
- bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener);
+ bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener, rateControl);
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener);
- bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
+ bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments, rateControl);
}
private void reset()
@@ -235,6 +236,16 @@ public class Parser
this.maxSettingsKeys = maxSettingsKeys;
}
+ public RateControl getRateControl()
+ {
+ return rateControl;
+ }
+
+ public void setRateControl(RateControl rateControl)
+ {
+ this.rateControl = rateControl;
+ }
+
protected void notifyConnectionFailure(int error, String reason)
{
try
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
index 8cee350e91e..675ba6d14ac 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
@@ -26,13 +26,15 @@ import org.eclipse.jetty.http2.frames.PingFrame;
public class PingBodyParser extends BodyParser
{
+ private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private byte[] payload;
- public PingBodyParser(HeaderParser headerParser, Parser.Listener listener)
+ public PingBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
{
super(headerParser, listener);
+ this.rateControl = rateControl;
}
private void reset()
@@ -66,7 +68,7 @@ public class PingBodyParser extends BodyParser
if (buffer.remaining() >= 8)
{
buffer.get(payload);
- return onPing(payload);
+ return onPing(buffer, payload);
}
else
{
@@ -80,7 +82,7 @@ public class PingBodyParser extends BodyParser
payload[8 - cursor] = buffer.get();
--cursor;
if (cursor == 0)
- return onPing(payload);
+ return onPing(buffer, payload);
break;
}
default:
@@ -92,9 +94,11 @@ public class PingBodyParser extends BodyParser
return false;
}
- private boolean onPing(byte[] payload)
+ private boolean onPing(ByteBuffer buffer, byte[] payload)
{
PingFrame frame = new PingFrame(payload, hasFlag(Flags.ACK));
+ if (rateControl != null && !rateControl.onEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_ping_frame_rate");
reset();
notifyPing(frame);
return true;
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java
index a9d11398987..67629f4fa09 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java
@@ -25,14 +25,16 @@ import org.eclipse.jetty.http2.frames.PriorityFrame;
public class PriorityBodyParser extends BodyParser
{
+ private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private boolean exclusive;
private int parentStreamId;
- public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener)
+ public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
{
super(headerParser, listener);
+ this.rateControl = rateControl;
}
private void reset()
@@ -103,7 +105,7 @@ public class PriorityBodyParser extends BodyParser
if (getStreamId() == parentStreamId)
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame");
int weight = (buffer.get() & 0xFF) + 1;
- return onPriority(parentStreamId, weight, exclusive);
+ return onPriority(buffer, parentStreamId, weight, exclusive);
}
default:
{
@@ -114,9 +116,11 @@ public class PriorityBodyParser extends BodyParser
return false;
}
- private boolean onPriority(int parentStreamId, int weight, boolean exclusive)
+ private boolean onPriority(ByteBuffer buffer, int parentStreamId, int weight, boolean exclusive)
{
PriorityFrame frame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
+ if (rateControl != null && !rateControl.onEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_priority_frame_rate");
reset();
notifyPriority(frame);
return true;
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
new file mode 100644
index 00000000000..16d82fa4e77
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// 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.http2.parser;
+
+/**
+ * Controls rate of events via {@link #onEvent(Object)}.
+ */
+public interface RateControl
+{
+ /**
+ * Applications should call this method when they want to signal an
+ * event that is subject to rate control.
+ * Implementations should return true if the event does not exceed
+ * the desired rate, or false to signal that the event exceeded the
+ * desired rate.
+ *
+ * @param event the event subject to rate control
+ * @return whether the rate is within limits
+ */
+ public boolean onEvent(Object event);
+}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
index 741dd95981d..3bf278f1089 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@@ -35,6 +36,7 @@ public class SettingsBodyParser extends BodyParser
private static final Logger LOG = Log.getLogger(SettingsBodyParser.class);
private final int maxKeys;
+ private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private int length;
@@ -45,13 +47,14 @@ public class SettingsBodyParser extends BodyParser
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
- this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS);
+ this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS, null);
}
- public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys)
+ public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys, RateControl rateControl)
{
super(headerParser, listener);
this.maxKeys = maxKeys;
+ this.rateControl = rateControl;
}
protected void reset()
@@ -72,7 +75,11 @@ public class SettingsBodyParser extends BodyParser
@Override
protected void emptyBody(ByteBuffer buffer)
{
- onSettings(buffer, new HashMap<>());
+ SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK));
+ if (rateControl != null && !rateControl.onEvent(frame))
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame");
+ else
+ onSettings(frame);
}
@Override
@@ -200,6 +207,11 @@ public class SettingsBodyParser extends BodyParser
return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size");
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
+ return onSettings(frame);
+ }
+
+ private boolean onSettings(SettingsFrame frame)
+ {
reset();
notifySettings(frame);
return true;
@@ -207,40 +219,25 @@ public class SettingsBodyParser extends BodyParser
public static SettingsFrame parseBody(final ByteBuffer buffer)
{
- final int bodyLength = buffer.remaining();
- final AtomicReference frameRef = new AtomicReference<>();
- SettingsBodyParser parser = new SettingsBodyParser(null, null)
+ AtomicReference frameRef = new AtomicReference<>();
+ SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(), new Parser.Listener.Adapter()
{
@Override
- protected int getStreamId()
+ public void onSettings(SettingsFrame frame)
{
- return 0;
+ frameRef.set(frame);
}
@Override
- protected int getBodyLength()
- {
- return bodyLength;
- }
-
- @Override
- protected boolean onSettings(ByteBuffer buffer, Map settings)
- {
- frameRef.set(new SettingsFrame(settings, false));
- return true;
- }
-
- @Override
- protected boolean connectionFailure(ByteBuffer buffer, int error, String reason)
+ public void onConnectionFailure(int error, String reason)
{
frameRef.set(null);
- return false;
}
- };
- if (bodyLength == 0)
- parser.emptyBody(buffer);
- else
+ });
+ if (buffer.hasRemaining())
parser.parse(buffer);
+ else
+ parser.emptyBody(buffer);
return frameRef.get();
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
index dbb29bfede2..0d3f3840490 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
@@ -20,13 +20,19 @@ package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http2.ErrorCode;
+import org.eclipse.jetty.http2.frames.Frame;
+import org.eclipse.jetty.http2.frames.UnknownFrame;
+
public class UnknownBodyParser extends BodyParser
{
+ private final RateControl rateControl;
private int cursor;
- public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener)
+ public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
{
super(headerParser, listener);
+ this.rateControl = rateControl;
}
@Override
@@ -34,7 +40,14 @@ public class UnknownBodyParser extends BodyParser
{
int length = cursor == 0 ? getBodyLength() : cursor;
cursor = consume(buffer, length);
- return cursor == 0;
+ boolean parsed = cursor == 0;
+ if (parsed && rateControl != null)
+ {
+ Frame frame = new UnknownFrame(getFrameType());
+ if (!rateControl.onEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame");
+ }
+ return parsed;
}
private int consume(ByteBuffer buffer, int length)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
new file mode 100644
index 00000000000..f2cef157e47
--- /dev/null
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
@@ -0,0 +1,61 @@
+//
+// ========================================================================
+// 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.http2.parser;
+
+import java.time.Duration;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * An implementation of {@link RateControl} that limits the number of
+ * events within a time period.
+ * Events are kept in a queue and for each event the queue is first
+ * drained of the old events outside the time window, and then the new
+ * event is added to the queue. If the size of the queue exceeds the max
+ * number of events then {@link #onEvent(Object)} returns {@code false}.
+ */
+public class WindowRateControl implements RateControl
+{
+ private final Queue events = new ConcurrentLinkedQueue<>();
+ private final int maxEvents;
+ private final long window;
+
+ public WindowRateControl(int maxEvents, Duration window)
+ {
+ this.maxEvents = maxEvents;
+ this.window = window.toNanos();
+ }
+
+ @Override
+ public boolean onEvent(Object event)
+ {
+ long now = System.nanoTime();
+ while (true)
+ {
+ Long time = events.peek();
+ if (time == null)
+ break;
+ if (now - time < window)
+ break;
+ events.poll();
+ }
+ events.add(now);
+ return events.size() <= maxEvents;
+ }
+}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java
index 78505540939..2c3ec6b736c 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowUpdateBodyParser.java
@@ -61,7 +61,7 @@ public class WindowUpdateBodyParser extends BodyParser
if (buffer.remaining() >= 4)
{
windowDelta = buffer.getInt() & 0x7F_FF_FF_FF;
- return onWindowUpdate(windowDelta);
+ return onWindowUpdate(buffer, windowDelta);
}
else
{
@@ -78,7 +78,7 @@ public class WindowUpdateBodyParser extends BodyParser
if (cursor == 0)
{
windowDelta &= 0x7F_FF_FF_FF;
- return onWindowUpdate(windowDelta);
+ return onWindowUpdate(buffer, windowDelta);
}
break;
}
@@ -91,9 +91,17 @@ public class WindowUpdateBodyParser extends BodyParser
return false;
}
- private boolean onWindowUpdate(int windowDelta)
+ private boolean onWindowUpdate(ByteBuffer buffer, int windowDelta)
{
- WindowUpdateFrame frame = new WindowUpdateFrame(getStreamId(), windowDelta);
+ int streamId = getStreamId();
+ if (windowDelta == 0)
+ {
+ if (streamId == 0)
+ return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
+ else
+ return streamFailure(streamId, ErrorCode.PROTOCOL_ERROR.code, "invalid_window_update_frame");
+ }
+ WindowUpdateFrame frame = new WindowUpdateFrame(streamId, windowDelta);
reset();
notifyWindowUpdate(frame);
return true;
diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
new file mode 100644
index 00000000000..aa9f8073068
--- /dev/null
+++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
@@ -0,0 +1,162 @@
+//
+// ========================================================================
+// 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.http2.frames;
+
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.UnaryOperator;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.Flags;
+import org.eclipse.jetty.http2.hpack.HpackEncoder;
+import org.eclipse.jetty.http2.parser.Parser;
+import org.eclipse.jetty.http2.parser.WindowRateControl;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
+
+public class FrameFloodTest
+{
+ private final ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+
+ // Frame structure:
+ // | Len0 | Len1 | Len2 | Type | Flags | StreamID0 |StreamID1 |StreamID2 |StreamID3 | Payload... |
+
+ private byte[] frameFrom(int length, int frameType, int flags, int streamId, byte[] payload)
+ {
+ byte[] result = new byte[3 + 1 + 1 + 4 + payload.length];
+ result[0] = (byte)((length >>> 16) & 0xFF);
+ result[1] = (byte)((length >>> 8) & 0xFF);
+ result[2] = (byte)(length & 0xFF);
+ result[3] = (byte)frameType;
+ result[4] = (byte)flags;
+ result[5] = (byte)((streamId >>> 24) & 0xFF);
+ result[6] = (byte)((streamId >>> 16) & 0xFF);
+ result[7] = (byte)((streamId >>> 8) & 0xFF);
+ result[8] = (byte)(streamId & 0xFF);
+ System.arraycopy(payload, 0, result, 9, payload.length);
+ return result;
+ }
+
+ @Test
+ public void testDataFrameFlood()
+ {
+ byte[] payload = new byte[0];
+ testFrameFlood(null, frameFrom(payload.length, FrameType.DATA.getType(), 0, 13, payload));
+ }
+
+ @Test
+ public void testHeadersFrameFlood()
+ {
+ byte[] payload = new byte[0];
+ testFrameFlood(null, frameFrom(payload.length, FrameType.HEADERS.getType(), Flags.END_HEADERS, 13, payload));
+ }
+
+ @Test
+ public void testInvalidHeadersFrameFlood()
+ {
+ // Invalid MetaData (no method, no scheme, etc).
+ MetaData.Request metadata = new MetaData.Request(null, (String)null, null, null, HttpVersion.HTTP_2, null, -1);
+ HpackEncoder encoder = new HpackEncoder();
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ encoder.encode(buffer, metadata);
+ buffer.flip();
+ byte[] payload = new byte[buffer.remaining()];
+ buffer.get(payload);
+ testFrameFlood(null, frameFrom(payload.length, FrameType.HEADERS.getType(), Flags.END_HEADERS, 13, payload));
+ }
+
+ @Test
+ public void testPriorityFrameFlood()
+ {
+ byte[] payload = new byte[]{0, 0, 0, 7, 0};
+ testFrameFlood(null, frameFrom(payload.length, FrameType.PRIORITY.getType(), 0, 13, payload));
+ }
+
+ @Test
+ public void testSettingsFrameFlood()
+ {
+ byte[] payload = new byte[0];
+ testFrameFlood(null, frameFrom(payload.length, FrameType.SETTINGS.getType(), 0, 0, payload));
+ }
+
+ @Test
+ public void testPingFrameFlood()
+ {
+ byte[] payload = {0, 0, 0, 0, 0, 0, 0, 0};
+ testFrameFlood(null, frameFrom(payload.length, FrameType.PING.getType(), 0, 0, payload));
+ }
+
+ @Test
+ public void testContinuationFrameFlood()
+ {
+ int streamId = 13;
+ byte[] headersPayload = new byte[0];
+ byte[] headersBytes = frameFrom(headersPayload.length, FrameType.HEADERS.getType(), 0, streamId, headersPayload);
+ byte[] continuationPayload = new byte[0];
+ testFrameFlood(headersBytes, frameFrom(continuationPayload.length, FrameType.CONTINUATION.getType(), 0, streamId, continuationPayload));
+ }
+
+ @Test
+ public void testUnknownFrameFlood()
+ {
+ byte[] payload = {0, 0, 0, 0};
+ testFrameFlood(null, frameFrom(payload.length, 64, 0, 0, payload));
+ }
+
+ private void testFrameFlood(byte[] preamble, byte[] bytes)
+ {
+ AtomicBoolean failed = new AtomicBoolean();
+ Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
+ {
+ @Override
+ public void onConnectionFailure(int error, String reason)
+ {
+ failed.set(true);
+ }
+ }, 4096, 8192);
+ parser.setRateControl(new WindowRateControl(8, Duration.ofSeconds(1)));
+ parser.init(UnaryOperator.identity());
+
+ if (preamble != null)
+ {
+ ByteBuffer buffer = ByteBuffer.wrap(preamble);
+ while (buffer.hasRemaining())
+ {
+ parser.parse(buffer);
+ }
+ }
+
+ int count = 0;
+ while (!failed.get())
+ {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ while (buffer.hasRemaining())
+ {
+ parser.parse(buffer);
+ }
+ assertThat("too many frames allowed", ++count, lessThan(1024));
+ }
+ }
+}
diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
index 82312d11e17..5824d276277 100644
--- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
+++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.http2.server;
import java.io.IOException;
+import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -34,7 +35,9 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.generator.Generator;
+import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.parser.ServerParser;
+import org.eclipse.jetty.http2.parser.WindowRateControl;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory;
@@ -58,6 +61,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
private int maxHeaderBlockFragment = 0;
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
+ private RateControl rateControl = new WindowRateControl(20, Duration.ofSeconds(1));
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
private long streamIdleTimeout;
@@ -178,6 +182,16 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
this.maxSettingsKeys = maxSettingsKeys;
}
+ public RateControl getRateControl()
+ {
+ return rateControl;
+ }
+
+ public void setRateControl(RateControl rateControl)
+ {
+ this.rateControl = rateControl;
+ }
+
/**
* @return -1
* @deprecated feature removed, no replacement
@@ -240,6 +254,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
ServerParser parser = newServerParser(connector, session);
parser.setMaxFrameLength(getMaxFrameLength());
parser.setMaxSettingsKeys(getMaxSettingsKeys());
+ parser.setRateControl(getRateControl());
HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(),
endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener);
From 47fb8f4dea4a6c7873875ffdd69cf65f664c88b7 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Mon, 19 Aug 2019 10:11:28 +1000
Subject: [PATCH 2/8] Issue #3978 HTTP2 Vulnerabilities
Fixed race in WindowRateControl by only removing the event that we
just inspected.
Added an AtomicInteger to track the size to avoid iterating over the
linked list.
Signed-off-by: Greg Wilkins
---
.../eclipse/jetty/http2/parser/RateControl.java | 6 ++++--
.../jetty/http2/parser/WindowRateControl.java | 14 +++++++++-----
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
index 16d82fa4e77..c811603d44f 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
@@ -23,6 +23,8 @@ package org.eclipse.jetty.http2.parser;
*/
public interface RateControl
{
+ RateControl NO_RATE_CONTROL = event -> true;
+
/**
* Applications should call this method when they want to signal an
* event that is subject to rate control.
@@ -30,8 +32,8 @@ public interface RateControl
* the desired rate, or false to signal that the event exceeded the
* desired rate.
*
- * @param event the event subject to rate control
- * @return whether the rate is within limits
+ * @param event the event subject to rate control.
+ * @return true IFF the rate is within limits
*/
public boolean onEvent(Object event);
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
index f2cef157e47..00a2c769737 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/WindowRateControl.java
@@ -21,18 +21,21 @@ package org.eclipse.jetty.http2.parser;
import java.time.Duration;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* An implementation of {@link RateControl} that limits the number of
* events within a time period.
* Events are kept in a queue and for each event the queue is first
* drained of the old events outside the time window, and then the new
- * event is added to the queue. If the size of the queue exceeds the max
+ * event is added to the queue. The size of the queue is maintained
+ * separately in an AtomicInteger and if it exceeds the max
* number of events then {@link #onEvent(Object)} returns {@code false}.
*/
public class WindowRateControl implements RateControl
{
private final Queue events = new ConcurrentLinkedQueue<>();
+ private final AtomicInteger size = new AtomicInteger();
private final int maxEvents;
private final long window;
@@ -51,11 +54,12 @@ public class WindowRateControl implements RateControl
Long time = events.peek();
if (time == null)
break;
- if (now - time < window)
+ if (now < time)
break;
- events.poll();
+ if (events.remove(time))
+ size.decrementAndGet();
}
- events.add(now);
- return events.size() <= maxEvents;
+ events.add(now + window);
+ return size.incrementAndGet() <= maxEvents;
}
}
From 5fc83c3d0c63ea61a99734eab62c2dd888415f51 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Mon, 19 Aug 2019 10:16:40 +1000
Subject: [PATCH 3/8] Issue #3978 HTTP2 Vulnerabilities
Reduce the number of RateControl fields, instead using common field in
HeaderParser.
Avoid null checking rateControl by having a NO_RATE_CONTROL static
HPack does not emit field with empty header name.
Apply rate control to any header parsing issue resulting in
session/stream failure
Signed-off-by: Greg Wilkins
---
.../eclipse/jetty/http2/client/HTTP2Test.java | 5 +-
.../jetty/http2/parser/BodyParser.java | 5 ++
.../http2/parser/ContinuationBodyParser.java | 6 +--
.../jetty/http2/parser/DataBodyParser.java | 6 +--
.../jetty/http2/parser/HeaderBlockParser.java | 5 ++
.../jetty/http2/parser/HeaderParser.java | 11 ++++
.../jetty/http2/parser/HeadersBodyParser.java | 8 ++-
.../eclipse/jetty/http2/parser/Parser.java | 29 +++++------
.../jetty/http2/parser/PingBodyParser.java | 6 +--
.../http2/parser/PriorityBodyParser.java | 6 +--
.../jetty/http2/parser/ServerParser.java | 4 +-
.../http2/parser/SettingsBodyParser.java | 11 ++--
.../jetty/http2/parser/UnknownBodyParser.java | 14 ++---
.../jetty/http2/frames/FrameFloodTest.java | 3 +-
.../jetty/http2/hpack/HpackDecoder.java | 2 +-
.../jetty/http2/hpack/MetaDataBuilder.java | 4 +-
.../jetty/http2/hpack/HpackDecoderTest.java | 52 ++++++++++++++++++-
.../HttpClientTransportOverHTTP2Test.java | 2 +-
.../AbstractHTTP2ServerConnectionFactory.java | 7 ++-
19 files changed, 120 insertions(+), 66 deletions(-)
diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java
index 132733ee8c2..98d168f6cdb 100644
--- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java
+++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java
@@ -46,6 +46,7 @@ import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
+import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.Connector;
@@ -747,7 +748,7 @@ public class HTTP2Test extends AbstractTest
RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener)
{
@Override
- protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener)
+ protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl)
{
return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener)
{
@@ -757,7 +758,7 @@ public class HTTP2Test extends AbstractTest
super.onGoAway(frame);
goAwayLatch.countDown();
}
- });
+ }, rateControl);
}
};
prepareServer(connectionFactory);
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java
index c85aa9ba8bf..01c6d29e570 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java
@@ -245,4 +245,9 @@ public abstract class BodyParser
LOG.info("Failure while notifying listener " + listener, x);
}
}
+
+ protected boolean rateControlOnEvent(Object o)
+ {
+ return headerParser.getRateControl().onEvent(o);
+ }
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
index b14cabf4e7d..09965e59527 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
@@ -30,16 +30,14 @@ public class ContinuationBodyParser extends BodyParser
{
private final HeaderBlockParser headerBlockParser;
private final HeaderBlockFragments headerBlockFragments;
- private final RateControl rateControl;
private State state = State.PREPARE;
private int length;
- public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments, RateControl rateControl)
+ public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments)
{
super(headerParser, listener);
this.headerBlockParser = headerBlockParser;
this.headerBlockFragments = headerBlockFragments;
- this.rateControl = rateControl;
}
@Override
@@ -52,7 +50,7 @@ public class ContinuationBodyParser extends BodyParser
else
{
ContinuationFrame frame = new ContinuationFrame(getStreamId(), hasFlag(Flags.END_HEADERS));
- if (rateControl != null && !rateControl.onEvent(frame))
+ if (!rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
}
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
index 5ea1a5bca81..be0ede0a5cb 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/DataBodyParser.java
@@ -26,16 +26,14 @@ import org.eclipse.jetty.util.BufferUtil;
public class DataBodyParser extends BodyParser
{
- private final RateControl rateControl;
private State state = State.PREPARE;
private int padding;
private int paddingLength;
private int length;
- public DataBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
+ public DataBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
super(headerParser, listener);
- this.rateControl = rateControl;
}
private void reset()
@@ -56,7 +54,7 @@ public class DataBodyParser extends BodyParser
else
{
DataFrame frame = new DataFrame(getStreamId(), BufferUtil.EMPTY_BUFFER, isEndStream());
- if (!isEndStream() && rateControl != null && !rateControl.onEvent(frame))
+ if (!isEndStream() && !rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_data_frame_rate");
else
onData(frame);
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java
index 54e4661da67..45f0a301c69 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java
@@ -103,6 +103,11 @@ public class HeaderBlockParser
{
if (LOG.isDebugEnabled())
LOG.debug(x);
+ if (!headerParser.getRateControl().onEvent(x))
+ {
+ notifier.connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_header_frame_rate");
+ return SESSION_FAILURE;
+ }
notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block");
return STREAM_FAILURE;
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java
index 0e4d2dc5e9e..c973eae1cbb 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java
@@ -30,6 +30,7 @@ import org.eclipse.jetty.http2.frames.FrameType;
*/
public class HeaderParser
{
+ private final RateControl rateControl;
private State state = State.LENGTH;
private int cursor;
@@ -38,6 +39,16 @@ public class HeaderParser
private int flags;
private int streamId;
+ HeaderParser(RateControl rateControl)
+ {
+ this.rateControl = rateControl;
+ }
+
+ public RateControl getRateControl()
+ {
+ return rateControl;
+ }
+
protected void reset()
{
state = State.LENGTH;
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java
index 9c53eed6a9e..3054d5ee439 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java
@@ -32,7 +32,6 @@ public class HeadersBodyParser extends BodyParser
{
private final HeaderBlockParser headerBlockParser;
private final HeaderBlockFragments headerBlockFragments;
- private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private int length;
@@ -41,12 +40,11 @@ public class HeadersBodyParser extends BodyParser
private int parentStreamId;
private int weight;
- public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments, RateControl rateControl)
+ public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments)
{
super(headerParser, listener);
this.headerBlockParser = headerBlockParser;
this.headerBlockFragments = headerBlockFragments;
- this.rateControl = rateControl;
}
private void reset()
@@ -71,7 +69,7 @@ public class HeadersBodyParser extends BodyParser
{
MetaData metaData = headerBlockParser.parse(BufferUtil.EMPTY_BUFFER, 0);
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
- if (rateControl != null && !rateControl.onEvent(frame))
+ if (!rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
else
onHeaders(frame);
@@ -193,7 +191,7 @@ public class HeadersBodyParser extends BodyParser
else
{
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, null, isEndStream());
- if (rateControl != null && !rateControl.onEvent(frame))
+ if (!rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_headers_frame_rate");
}
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
index 3b0efca7c20..f0363ae6287 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
@@ -56,15 +56,19 @@ public class Parser
private UnknownBodyParser unknownBodyParser;
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
- private RateControl rateControl;
private boolean continuation;
private State state = State.HEADER;
public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize)
+ {
+ this (byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, RateControl.NO_RATE_CONTROL);
+ }
+
+ public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl)
{
this.byteBufferPool = byteBufferPool;
this.listener = listener;
- this.headerParser = new HeaderParser();
+ this.headerParser = new HeaderParser(rateControl == null ? RateControl.NO_RATE_CONTROL : rateControl);
this.hpackDecoder = new HpackDecoder(maxDynamicTableSize, maxHeaderSize);
this.bodyParsers = new BodyParser[FrameType.values().length];
}
@@ -73,19 +77,19 @@ public class Parser
{
Listener listener = wrapper.apply(this.listener);
RateControl rateControl = getRateControl();
- unknownBodyParser = new UnknownBodyParser(headerParser, listener, rateControl);
+ unknownBodyParser = new UnknownBodyParser(headerParser, listener);
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments();
- bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener, rateControl);
- bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments, rateControl);
- bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener, rateControl);
+ bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener);
+ bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
+ bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
- bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys(), rateControl);
+ bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys());
bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser);
- bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener, rateControl);
+ bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener);
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener);
- bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments, rateControl);
+ bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
}
private void reset()
@@ -238,12 +242,7 @@ public class Parser
public RateControl getRateControl()
{
- return rateControl;
- }
-
- public void setRateControl(RateControl rateControl)
- {
- this.rateControl = rateControl;
+ return headerParser.getRateControl();
}
protected void notifyConnectionFailure(int error, String reason)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
index 675ba6d14ac..e56e573236e 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java
@@ -26,15 +26,13 @@ import org.eclipse.jetty.http2.frames.PingFrame;
public class PingBodyParser extends BodyParser
{
- private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private byte[] payload;
- public PingBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
+ public PingBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
super(headerParser, listener);
- this.rateControl = rateControl;
}
private void reset()
@@ -97,7 +95,7 @@ public class PingBodyParser extends BodyParser
private boolean onPing(ByteBuffer buffer, byte[] payload)
{
PingFrame frame = new PingFrame(payload, hasFlag(Flags.ACK));
- if (rateControl != null && !rateControl.onEvent(frame))
+ if (!rateControlOnEvent(frame))
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_ping_frame_rate");
reset();
notifyPing(frame);
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java
index 67629f4fa09..914e9387b30 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PriorityBodyParser.java
@@ -25,16 +25,14 @@ import org.eclipse.jetty.http2.frames.PriorityFrame;
public class PriorityBodyParser extends BodyParser
{
- private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private boolean exclusive;
private int parentStreamId;
- public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
+ public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
super(headerParser, listener);
- this.rateControl = rateControl;
}
private void reset()
@@ -119,7 +117,7 @@ public class PriorityBodyParser extends BodyParser
private boolean onPriority(ByteBuffer buffer, int parentStreamId, int weight, boolean exclusive)
{
PriorityFrame frame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
- if (rateControl != null && !rateControl.onEvent(frame))
+ if (!rateControlOnEvent(frame))
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_priority_frame_rate");
reset();
notifyPriority(frame);
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java
index 49d40f8fd58..067e263cd33 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java
@@ -37,9 +37,9 @@ public class ServerParser extends Parser
private State state = State.PREFACE;
private boolean notifyPreface = true;
- public ServerParser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize)
+ public ServerParser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize, RateControl rateControl)
{
- super(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize);
+ super(byteBufferPool, listener, maxDynamicTableSize, maxHeaderSize, rateControl);
this.listener = listener;
this.prefaceParser = new PrefaceParser(listener);
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
index 3bf278f1089..abd97658402 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
@@ -36,7 +36,6 @@ public class SettingsBodyParser extends BodyParser
private static final Logger LOG = Log.getLogger(SettingsBodyParser.class);
private final int maxKeys;
- private final RateControl rateControl;
private State state = State.PREPARE;
private int cursor;
private int length;
@@ -47,14 +46,13 @@ public class SettingsBodyParser extends BodyParser
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
- this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS, null);
+ this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS);
}
- public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys, RateControl rateControl)
+ public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys)
{
super(headerParser, listener);
this.maxKeys = maxKeys;
- this.rateControl = rateControl;
}
protected void reset()
@@ -76,7 +74,7 @@ public class SettingsBodyParser extends BodyParser
protected void emptyBody(ByteBuffer buffer)
{
SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK));
- if (rateControl != null && !rateControl.onEvent(frame))
+ if (!rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame");
else
onSettings(frame);
@@ -220,7 +218,8 @@ public class SettingsBodyParser extends BodyParser
public static SettingsFrame parseBody(final ByteBuffer buffer)
{
AtomicReference frameRef = new AtomicReference<>();
- SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(), new Parser.Listener.Adapter()
+ // TODO should we do rate control here?
+ SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(RateControl.NO_RATE_CONTROL), new Parser.Listener.Adapter()
{
@Override
public void onSettings(SettingsFrame frame)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
index 0d3f3840490..a0ef9e3ce0a 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
@@ -21,18 +21,15 @@ package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.ErrorCode;
-import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.UnknownFrame;
public class UnknownBodyParser extends BodyParser
{
- private final RateControl rateControl;
private int cursor;
- public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener, RateControl rateControl)
+ public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener)
{
super(headerParser, listener);
- this.rateControl = rateControl;
}
@Override
@@ -41,12 +38,9 @@ public class UnknownBodyParser extends BodyParser
int length = cursor == 0 ? getBodyLength() : cursor;
cursor = consume(buffer, length);
boolean parsed = cursor == 0;
- if (parsed && rateControl != null)
- {
- Frame frame = new UnknownFrame(getFrameType());
- if (!rateControl.onEvent(frame))
- return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame");
- }
+ if (parsed && !rateControlOnEvent(new UnknownFrame(getFrameType())))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame");
+
return parsed;
}
diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
index aa9f8073068..04685dcb743 100644
--- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
+++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java
@@ -135,8 +135,7 @@ public class FrameFloodTest
{
failed.set(true);
}
- }, 4096, 8192);
- parser.setRateControl(new WindowRateControl(8, Duration.ofSeconds(1)));
+ }, 4096, 8192, new WindowRateControl(8, Duration.ofSeconds(1)));
parser.init(UnaryOperator.identity());
if (preamble != null)
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
index 34c47201932..106281445f3 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
@@ -175,7 +175,7 @@ public class HpackDecoder
name = Huffman.decode(buffer, length);
else
name = toASCIIString(buffer, length);
- for (int i = 0; i < name.length(); i++)
+ for (int i = name.length(); i-- > 0;)
{
char c = name.charAt(i);
if (c >= 'A' && c <= 'Z')
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
index f10f792fb44..41f02c3df3f 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
@@ -74,11 +74,13 @@ public class MetaDataBuilder
{
HttpHeader header = field.getHeader();
String name = field.getName();
+ if (name == null || name.length() == 0)
+ throw new HpackException.SessionException("Header size 0");
String value = field.getValue();
int fieldSize = name.length() + (value == null ? 0 : value.length());
_size += fieldSize + 32;
if (_size > _maxSize)
- throw new HpackException.SessionException("Header Size %d > %d", _size, _maxSize);
+ throw new HpackException.SessionException("Header size %d > %d", _size, _maxSize);
if (field instanceof StaticTableHttpField)
{
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
index 3aa3da34e69..c93882561ca 100644
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
+++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
@@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackException.CompressionException;
+import org.eclipse.jetty.http2.hpack.HpackException.SessionException;
import org.eclipse.jetty.http2.hpack.HpackException.StreamException;
import org.eclipse.jetty.util.TypeUtil;
import org.hamcrest.Matchers;
@@ -43,6 +44,21 @@ import static org.junit.jupiter.api.Assertions.fail;
public class HpackDecoderTest
{
+ /*
+ 0 1 2 3 4 5 6 7
+ +---+---+---+---+---+---+---+---+
+ | 0 | 0 | 0 | 0 | 0 |
+ +---+---+-----------------------+
+ | H | Name Length (7+) |
+ +---+---------------------------+
+ | Name String (Length octets) |
+ +---+---------------------------+
+ | H | Value Length (7+) |
+ +---+---------------------------+
+ | Value String (Length octets) |
+ +-------------------------------+
+ */
+
@Test
public void testDecodeD_3() throws Exception
{
@@ -253,7 +269,7 @@ public class HpackDecoderTest
decoder.decode(buffer);
fail();
}
- catch (HpackException.SessionException e)
+ catch (SessionException e)
{
assertThat(e.getMessage(), Matchers.startsWith("Unknown index"));
}
@@ -506,4 +522,38 @@ public class HpackDecoderTest
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
}
+
+ @Test()
+ public void testZeroLengthName() throws Exception
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "00000130";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ SessionException ex = assertThrows(SessionException.class, () -> decoder.decode(buffer));
+ assertThat(ex.getMessage(), Matchers.containsString("Header size 0"));
+ }
+
+ @Test()
+ public void testZeroLengthValue() throws Exception
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "00016800";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ MetaData metaData = decoder.decode(buffer);
+ assertThat(metaData.getFields().size(), is(1));
+ assertThat(metaData.getFields().get("h"), is(""));
+ }
+
+ @Test()
+ public void testUpperCaseName() throws Exception
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "0001480130";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer));
+ assertThat(ex.getMessage(), Matchers.containsString("Uppercase header"));
+ }
}
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
index 2e9e2cf725a..4f9f99c2275 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
@@ -501,7 +501,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
x.printStackTrace();
}
}
- }, 4096, 8192);
+ }, 4096, 8192, null);
parser.init(UnaryOperator.identity());
byte[] bytes = new byte[1024];
diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
index 5824d276277..7b5319efbc5 100644
--- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
+++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
@@ -251,10 +251,9 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
session.setInitialSessionRecvWindow(getInitialSessionRecvWindow());
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
- ServerParser parser = newServerParser(connector, session);
+ ServerParser parser = newServerParser(connector, session, getRateControl());
parser.setMaxFrameLength(getMaxFrameLength());
parser.setMaxSettingsKeys(getMaxSettingsKeys());
- parser.setRateControl(getRateControl());
HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(),
endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener);
@@ -264,9 +263,9 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
protected abstract ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint);
- protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener)
+ protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl)
{
- return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize());
+ return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize(), rateControl);
}
@ManagedObject("The container of HTTP/2 sessions")
From 5184c4ec3d895e960918bdd3add100c2a9d86c01 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Mon, 19 Aug 2019 11:38:01 +0200
Subject: [PATCH 4/8] Issue #3978 - HTTP/2 vulnerabilities.
Small fixed after review.
Signed-off-by: Simone Bordet
---
.../http2/parser/ContinuationBodyParser.java | 15 ++++++++++-----
.../jetty/http2/parser/HeaderBlockParser.java | 5 -----
.../jetty/http2/parser/SettingsBodyParser.java | 2 +-
.../jetty/http2/parser/UnknownBodyParser.java | 2 +-
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
index 09965e59527..fc2e03e97a0 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ContinuationBodyParser.java
@@ -45,7 +45,7 @@ public class ContinuationBodyParser extends BodyParser
{
if (hasFlag(Flags.END_HEADERS))
{
- onHeaders();
+ onHeaders(buffer);
}
else
{
@@ -90,7 +90,7 @@ public class ContinuationBodyParser extends BodyParser
headerBlockFragments.storeFragment(buffer, length, last);
reset();
if (last)
- return onHeaders();
+ return onHeaders(buffer);
return true;
}
}
@@ -103,15 +103,20 @@ public class ContinuationBodyParser extends BodyParser
return false;
}
- private boolean onHeaders()
+ private boolean onHeaders(ByteBuffer buffer)
{
ByteBuffer headerBlock = headerBlockFragments.complete();
MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining());
+ if (metaData == null)
+ return true;
if (metaData == HeaderBlockParser.SESSION_FAILURE)
return false;
- if (metaData == null || metaData == HeaderBlockParser.STREAM_FAILURE)
- return true;
HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream());
+ if (metaData == HeaderBlockParser.STREAM_FAILURE)
+ {
+ if (!rateControlOnEvent(frame))
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_continuation_frame_rate");
+ }
notifyHeaders(frame);
return true;
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java
index 45f0a301c69..54e4661da67 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java
@@ -103,11 +103,6 @@ public class HeaderBlockParser
{
if (LOG.isDebugEnabled())
LOG.debug(x);
- if (!headerParser.getRateControl().onEvent(x))
- {
- notifier.connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_header_frame_rate");
- return SESSION_FAILURE;
- }
notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block");
return STREAM_FAILURE;
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
index abd97658402..ab90f909c28 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
@@ -75,7 +75,7 @@ public class SettingsBodyParser extends BodyParser
{
SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK));
if (!rateControlOnEvent(frame))
- connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame");
+ connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
else
onSettings(frame);
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
index a0ef9e3ce0a..90293fee9db 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/UnknownBodyParser.java
@@ -39,7 +39,7 @@ public class UnknownBodyParser extends BodyParser
cursor = consume(buffer, length);
boolean parsed = cursor == 0;
if (parsed && !rateControlOnEvent(new UnknownFrame(getFrameType())))
- return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame");
+ return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_unknown_frame_rate");
return parsed;
}
From 32fe4e5ca8d83c1f532a70ae5f2be0608504ad1d Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Wed, 21 Aug 2019 11:36:06 +0200
Subject: [PATCH 5/8] Issue #3978 - HTTP/2 vulnerabilities.
Fixed load test that required `RateControl.NO_RATE_CONTROL`.
Signed-off-by: Simone Bordet
---
.../java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java | 1 -
.../jetty/http2/client/http/MaxConcurrentStreamsTest.java | 2 ++
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
index ab90f909c28..97e3e28c65b 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
@@ -218,7 +218,6 @@ public class SettingsBodyParser extends BodyParser
public static SettingsFrame parseBody(final ByteBuffer buffer)
{
AtomicReference frameRef = new AtomicReference<>();
- // TODO should we do rate control here?
SettingsBodyParser parser = new SettingsBodyParser(new HeaderParser(RateControl.NO_RATE_CONTROL), new Parser.Listener.Adapter()
{
@Override
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
index f05b0da7ae2..1fc482a1a97 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
@@ -49,6 +49,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
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.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
@@ -74,6 +75,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
{
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration());
http2.setMaxConcurrentStreams(maxConcurrentStreams);
+ http2.setRateControl(RateControl.NO_RATE_CONTROL);
prepareServer(http2);
server.setHandler(handler);
server.start();
From b2aa083778642396c77d15953ad8218c0be8aeb6 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Mon, 26 Aug 2019 13:18:12 +1000
Subject: [PATCH 6/8] Updates from review
Signed-off-by: Greg Wilkins
---
.../jetty/http2/hpack/HpackDecoder.java | 29 ++++++++++++++++---
.../jetty/http2/hpack/HpackDecoderTest.java | 13 ++++++++-
.../HttpClientTransportOverHTTP2Test.java | 3 +-
3 files changed, 39 insertions(+), 6 deletions(-)
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
index 106281445f3..1021bc98273 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
@@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
@@ -175,13 +176,33 @@ public class HpackDecoder
name = Huffman.decode(buffer, length);
else
name = toASCIIString(buffer, length);
- for (int i = name.length(); i-- > 0;)
+ check: for (int i = name.length(); i-- > 0;)
{
char c = name.charAt(i);
- if (c >= 'A' && c <= 'Z')
+ if (c>0xff)
{
- _builder.streamException("Uppercase header name %s", name);
- break;
+ _builder.streamException("Illegal header name %s", name);
+ break check;
+ }
+ HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c];
+ switch(token.getType())
+ {
+ case ALPHA:
+ if (c >= 'A' && c <= 'Z')
+ {
+ _builder.streamException("Uppercase header name %s", name);
+ break check;
+ }
+ break;
+
+ case COLON:
+ case TCHAR:
+ case DIGIT:
+ break;
+
+ default:
+ _builder.streamException("Illegal header name %s", name);
+ break check;
}
}
header = HttpHeader.CACHE.get(name);
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
index c93882561ca..ae7cd8c3bef 100644
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
+++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
@@ -276,7 +276,7 @@ public class HpackDecoderTest
}
/* 8.1.2.1. Pseudo-Header Fields */
- @Test()
+ @Test
public void test8_1_2_1_PsuedoHeaderFields() throws Exception
{
// 1:Sends a HEADERS frame that contains a unknown pseudo-header field
@@ -556,4 +556,15 @@ public class HpackDecoderTest
StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer));
assertThat(ex.getMessage(), Matchers.containsString("Uppercase header"));
}
+
+ @Test()
+ public void testWhiteSpaceName() throws Exception
+ {
+ HpackDecoder decoder = new HpackDecoder(4096, 8192);
+
+ String encoded = "0001200130";
+ ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
+ StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer));
+ assertThat(ex.getMessage(), Matchers.containsString("Illegal header"));
+ }
}
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
index 4f9f99c2275..3a7739f42d3 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
@@ -63,6 +63,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.generator.Generator;
+import org.eclipse.jetty.http2.parser.RateControl;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -501,7 +502,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
x.printStackTrace();
}
}
- }, 4096, 8192, null);
+ }, 4096, 8192, RateControl.NO_RATE_CONTROL);
parser.init(UnaryOperator.identity());
byte[] bytes = new byte[1024];
From 53fc01793c24961bec4f8930cf153df9b3b0e426 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Mon, 26 Aug 2019 13:20:07 +1000
Subject: [PATCH 7/8] Updates from review
Signed-off-by: Greg Wilkins
---
.../jetty/http2/hpack/HpackDecoderTest.java | 24 +++++++++----------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
index ae7cd8c3bef..f4488c72459 100644
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
+++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
@@ -328,7 +328,7 @@ public class HpackDecoderTest
}
}
- @Test()
+ @Test
public void test8_1_2_2_ConnectionSpecificHeaderFields() throws Exception
{
MetaDataBuilder mdb;
@@ -365,7 +365,7 @@ public class HpackDecoderTest
assertNotNull(mdb.build());
}
- @Test()
+ @Test
public void test8_1_2_3_RequestPseudoHeaderFields() throws Exception
{
{
@@ -444,7 +444,7 @@ public class HpackDecoderTest
}
}
- @Test()
+ @Test
public void testHuffmanEncodedStandard() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -462,7 +462,7 @@ public class HpackDecoderTest
}
/* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */
- @Test()
+ @Test
public void testHuffmanEncodedExtraPadding() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -474,7 +474,7 @@ public class HpackDecoderTest
}
/* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */
- @Test()
+ @Test
public void testHuffmanEncodedZeroPadding() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -487,7 +487,7 @@ public class HpackDecoderTest
}
/* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */
- @Test()
+ @Test
public void testHuffmanEncodedWithEOS() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -499,7 +499,7 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("EOS in content"));
}
- @Test()
+ @Test
public void testHuffmanEncodedOneIncompleteOctet() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -511,7 +511,7 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
}
- @Test()
+ @Test
public void testHuffmanEncodedTwoIncompleteOctet() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -523,7 +523,7 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
}
- @Test()
+ @Test
public void testZeroLengthName() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -534,7 +534,7 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("Header size 0"));
}
- @Test()
+ @Test
public void testZeroLengthValue() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -546,7 +546,7 @@ public class HpackDecoderTest
assertThat(metaData.getFields().get("h"), is(""));
}
- @Test()
+ @Test
public void testUpperCaseName() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -557,7 +557,7 @@ public class HpackDecoderTest
assertThat(ex.getMessage(), Matchers.containsString("Uppercase header"));
}
- @Test()
+ @Test
public void testWhiteSpaceName() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
From 508ad4aff91dc23653ae2cbbf6b7c6549fd982f2 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Thu, 5 Sep 2019 23:11:53 +0200
Subject: [PATCH 8/8] 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
---
.../eclipse/jetty/http2/client/HTTP2Test.java | 11 ++---
.../org/eclipse/jetty/http2/HTTP2Session.java | 18 ++------
.../jetty/http2/parser/HeaderParser.java | 3 +-
.../eclipse/jetty/http2/parser/Parser.java | 8 +---
.../jetty/http2/parser/RateControl.java | 2 +-
.../http2/parser/SettingsBodyParser.java | 5 +-
.../jetty/http2/hpack/HpackDecoder.java | 9 ++--
.../jetty/http2/hpack/MetaDataBuilder.java | 2 +-
.../jetty/http2/hpack/HpackDecoderTest.java | 28 +++++------
.../HttpClientTransportOverHTTP2Test.java | 8 ++--
.../client/http/MaxConcurrentStreamsTest.java | 2 -
.../org/eclipse/jetty/util/MathUtils.java | 46 +++++++++++++++++++
12 files changed, 83 insertions(+), 59 deletions(-)
create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java
diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java
index 98d168f6cdb..b5be288a82b 100644
--- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java
+++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/HTTP2Test.java
@@ -27,7 +27,6 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -153,7 +152,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
resp.getOutputStream().write(content);
}
@@ -201,7 +200,7 @@ public class HTTP2Test extends AbstractTest
start(new EmptyHttpServlet()
{
@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());
}
@@ -245,7 +244,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@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);
byte[] content = new byte[download];
@@ -288,7 +287,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response)
{
response.setStatus(status);
}
@@ -323,7 +322,7 @@ public class HTTP2Test extends AbstractTest
start(new HttpServlet()
{
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response)
{
assertEquals(host, request.getServerName());
assertEquals(port, request.getServerPort());
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
index 1908884b48c..a8fb651121a 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
@@ -57,6 +57,7 @@ import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CountingCallback;
+import org.eclipse.jetty.util.MathUtils;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.Retainable;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@@ -464,7 +465,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
if (stream != null)
{
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);
}
@@ -483,7 +484,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
else
{
int sessionSendWindow = updateSendWindow(0);
- if (sumOverflows(sessionSendWindow, windowDelta))
+ if (MathUtils.sumOverflows(sessionSendWindow, windowDelta))
onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window");
else
onWindowUpdate(null, frame);
@@ -501,19 +502,6 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
callback.succeeded();
}
- private boolean sumOverflows(int a, int b)
- {
- try
- {
- Math.addExact(a, b);
- return false;
- }
- catch (ArithmeticException x)
- {
- return true;
- }
- }
-
@Override
public void onConnectionFailure(int error, String reason)
{
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java
index c973eae1cbb..e735d9ada3e 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java
@@ -33,13 +33,12 @@ public class HeaderParser
private final RateControl rateControl;
private State state = State.LENGTH;
private int cursor;
-
private int length;
private int type;
private int flags;
private int streamId;
- HeaderParser(RateControl rateControl)
+ public HeaderParser(RateControl rateControl)
{
this.rateControl = rateControl;
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
index f0363ae6287..7545b6fe159 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/Parser.java
@@ -61,7 +61,7 @@ public class Parser
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)
@@ -76,7 +76,6 @@ public class Parser
public void init(UnaryOperator wrapper)
{
Listener listener = wrapper.apply(this.listener);
- RateControl rateControl = getRateControl();
unknownBodyParser = new UnknownBodyParser(headerParser, listener);
HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, byteBufferPool, hpackDecoder, unknownBodyParser);
HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments();
@@ -240,11 +239,6 @@ public class Parser
this.maxSettingsKeys = maxSettingsKeys;
}
- public RateControl getRateControl()
- {
- return headerParser.getRateControl();
- }
-
protected void notifyConnectionFailure(int error, String reason)
{
try
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
index c811603d44f..be8f82a7cf2 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/RateControl.java
@@ -23,7 +23,7 @@ package org.eclipse.jetty.http2.parser;
*/
public interface RateControl
{
- RateControl NO_RATE_CONTROL = event -> true;
+ public static final RateControl NO_RATE_CONTROL = event -> true;
/**
* Applications should call this method when they want to signal an
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
index 97e3e28c65b..b58fa8a8b51 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java
@@ -73,8 +73,9 @@ public class SettingsBodyParser extends BodyParser
@Override
protected void emptyBody(ByteBuffer buffer)
{
- SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), hasFlag(Flags.ACK));
- if (!rateControlOnEvent(frame))
+ boolean isReply = hasFlag(Flags.ACK);
+ SettingsFrame frame = new SettingsFrame(Collections.emptyMap(), isReply);
+ if (!isReply && !rateControlOnEvent(frame))
connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame_rate");
else
onSettings(frame);
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
index 1021bc98273..bb1d3ede5fb 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java
@@ -176,16 +176,17 @@ public class HpackDecoder
name = Huffman.decode(buffer, length);
else
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);
- if (c>0xff)
+ if (c > 0xff)
{
_builder.streamException("Illegal header name %s", name);
- break check;
+ break;
}
HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c];
- switch(token.getType())
+ switch (token.getType())
{
case ALPHA:
if (c >= 'A' && c <= 'Z')
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
index 41f02c3df3f..ce1f8b36cbe 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java
@@ -115,7 +115,7 @@ public class MetaDataBuilder
{
case C_STATUS:
if (checkPseudoHeader(header, _status))
- _status = Integer.valueOf(field.getIntValue());
+ _status = field.getIntValue();
_response = true;
break;
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
index f4488c72459..791ecd3d060 100644
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
+++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java
@@ -384,7 +384,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
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"));
}
@@ -394,7 +394,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
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"));
}
@@ -404,7 +404,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
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"));
}
@@ -414,7 +414,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET"));
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
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"));
}
@@ -426,7 +426,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
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"));
}
@@ -439,7 +439,7 @@ public class HpackDecoderTest
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080"));
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"));
}
}
@@ -463,7 +463,7 @@ public class HpackDecoderTest
/* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */
@Test
- public void testHuffmanEncodedExtraPadding() throws Exception
+ public void testHuffmanEncodedExtraPadding()
{
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 */
@Test
- public void testHuffmanEncodedZeroPadding() throws Exception
+ public void testHuffmanEncodedZeroPadding()
{
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 */
@Test
- public void testHuffmanEncodedWithEOS() throws Exception
+ public void testHuffmanEncodedWithEOS()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -500,7 +500,7 @@ public class HpackDecoderTest
}
@Test
- public void testHuffmanEncodedOneIncompleteOctet() throws Exception
+ public void testHuffmanEncodedOneIncompleteOctet()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -512,7 +512,7 @@ public class HpackDecoderTest
}
@Test
- public void testHuffmanEncodedTwoIncompleteOctet() throws Exception
+ public void testHuffmanEncodedTwoIncompleteOctet()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -524,7 +524,7 @@ public class HpackDecoderTest
}
@Test
- public void testZeroLengthName() throws Exception
+ public void testZeroLengthName()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -547,7 +547,7 @@ public class HpackDecoderTest
}
@Test
- public void testUpperCaseName() throws Exception
+ public void testUpperCaseName()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
@@ -558,7 +558,7 @@ public class HpackDecoderTest
}
@Test
- public void testWhiteSpaceName() throws Exception
+ public void testWhiteSpaceName()
{
HpackDecoder decoder = new HpackDecoder(4096, 8192);
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
index 3a7739f42d3..883078555dc 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.http2.client.http;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
@@ -38,7 +37,6 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -188,7 +186,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler()
{
@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);
HttpVersion version = HttpVersion.fromString(request.getProtocol());
@@ -314,7 +312,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler()
{
@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);
assertEquals(path, request.getRequestURI());
@@ -338,7 +336,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
start(new AbstractHandler()
{
@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);
assertEquals(path, request.getRequestURI());
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
index 1fc482a1a97..f05b0da7ae2 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java
@@ -49,7 +49,6 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
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.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
@@ -75,7 +74,6 @@ public class MaxConcurrentStreamsTest extends AbstractTest
{
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration());
http2.setMaxConcurrentStreams(maxConcurrentStreams);
- http2.setRateControl(RateControl.NO_RATE_CONTROL);
prepareServer(http2);
server.setHandler(handler);
server.start();
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java
new file mode 100644
index 00000000000..6c522299b21
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MathUtils.java
@@ -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;
+ }
+ }
+}