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; + } + } +}