New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reject DATA Frames for invalid path requests #4405
Conversation
Motivation: If a request with an invalid path such as `/?download=../../secret.txt` and a DATA Frame is received, a `ClassCastException` can be raised. ``` java.lang.ClassCastException: class com.linecorp.armeria.server.EmptyContentDecodedHttpRequest cannot be cast to class com.linecorp.armeria.server.DecodedHttpRequestWriter (com.linecorp.armeria.server.EmptyContentDecodedHttpRequest and com.linecorp.armeria.server.DecodedHttpRequestWriter are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader @301eda63) at com.linecorp.armeria.server.Http2RequestDecoder.onDataRead(Http2RequestDecoder.java:265) at io.netty.handler.codec.http2.Http2FrameListenerDecorator.onDataRead(Http2FrameListenerDecorator.java:36) at io.netty.handler.codec.http2.Http2EmptyDataFrameListener.onDataRead(Http2EmptyDataFrameListener.java:49) at io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder$FrameReadListener.onDataRead(DefaultHttp2ConnectionDecoder.java:307) at io.netty.handler.codec.http2.Http2InboundFrameLogger$1.onDataRead(Http2InboundFrameLogger.java:48) ``` Although a response is immediately sent by `HttpServerHandler` for the invalid path when the headers are received and try to close the stream, `onDataRead()` callback could be triggered by the following DATA frame. Because the stream could not be closed yet. https://github.com/line/armeria/blob/master/core/src/main/java/com/linecorp/armeria/server/Http2RequestDecoder.java#L242-L244 Modification: - Rejects the following DATA frames and HEADERS frames of invalid path requests with `PROTOCOL_ERROR`. Result: You no longer see `ClassCastException` when an invalid path request is recevied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some minor comments/questions 🙇
@@ -182,6 +182,10 @@ public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers | |||
ctx.fireChannelRead(req); | |||
} | |||
} else { | |||
if (!(req instanceof DecodedHttpRequestWriter)) { | |||
throw connectionError(PROTOCOL_ERROR, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Is it correct behavior to close the connection rather than return a 4xx response?
If a malicious user decides to continuously send these type of requests, will other requests on the same connection be penalized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
4xx response might be returned already by HttpServerHandler
. So we don't need to return a response here.
Is it better to silently ignore the incoming DATA frames?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a malicious user decides to continuously send these type of requests, will other requests on the same connection be penalized?
For the protocol violations or unexpected errors, a connection is reset with writeErrorResponse()
.
For example, if a malicious user sends a wrong content-length
, the connection which the request was sent will be closed as well.
However, the HttpServerHandler
who handled the request did not close the connection. So it makes more sense to keep the connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, if a malicious user sends a wrong content-length, the connection which the request was sent will be closed as well.
Maybe I misunderstood the current codebase, but wouldn't only the stream be closed in this case?
writeErrorResponse(streamId, headers, HttpStatus.BAD_REQUEST, |
Since the connection will be closed only if streamId == 0
Anyways, sorry about the late check. I agree with the approach 👍
core/src/main/java/com/linecorp/armeria/server/Http2RequestDecoder.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java
Outdated
Show resolved
Hide resolved
63bed1b
to
82eefb7
Compare
Thanks to @tawAsh1 who reports the bug. 🙇♂️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick fix + name change! 👍 👍 👍
Codecov Report
@@ Coverage Diff @@
## master #4405 +/- ##
============================================
- Coverage 73.80% 73.79% -0.02%
+ Complexity 17713 17709 -4
============================================
Files 1509 1509
Lines 66198 66205 +7
Branches 8313 8315 +2
============================================
- Hits 48860 48853 -7
- Misses 13330 13345 +15
+ Partials 4008 4007 -1
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this! 😄
Motivation: If a request with an invalid path such as `/?download=../../secret.txt` and a DATA Frame is received, a `ClassCastException` can be raised. ``` java.lang.ClassCastException: class com.linecorp.armeria.server.EmptyContentDecodedHttpRequest cannot be cast to class com.linecorp.armeria.server.DecodedHttpRequestWriter (com.linecorp.armeria.server.EmptyContentDecodedHttpRequest and com.linecorp.armeria.server.DecodedHttpRequestWriter are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader @301eda63) at com.linecorp.armeria.server.Http2RequestDecoder.onDataRead(Http2RequestDecoder.java:265) at io.netty.handler.codec.http2.Http2FrameListenerDecorator.onDataRead(Http2FrameListenerDecorator.java:36) at io.netty.handler.codec.http2.Http2EmptyDataFrameListener.onDataRead(Http2EmptyDataFrameListener.java:49) at io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder$FrameReadListener.onDataRead(DefaultHttp2ConnectionDecoder.java:307) at io.netty.handler.codec.http2.Http2InboundFrameLogger$1.onDataRead(Http2InboundFrameLogger.java:48) ``` Although a response is immediately sent by `HttpServerHandler` for the invalid path when the headers are received and try to close the stream, `onDataRead()` callback could be triggered by the following DATA frame. Because the stream could not be closed yet. https://github.com/line/armeria/blob/b9dc1ad1c6f4cfee8aba8e50a61d203c37eb94cc/core/src/main/java/com/linecorp/armeria/server/Http2RequestDecoder.java?rgh-link-date=2022-09-01T06%3A34%3A32Z#L242-L244 Modifications: - Rejects the following DATA frames and HEADERS frames of invalid path requests with `PROTOCOL_ERROR`. Result: You no longer see `ClassCastException` when an invalid path request is received.
Motivation:
If a request with an invalid path such as
/?download=../../secret.txt
and a DATA Frame is received, a
ClassCastException
can be raised.Although a response is immediately sent by
HttpServerHandler
forthe invalid path when the headers are received and try to close the stream,
onDataRead()
callback could be triggered by the following DATA frame.Because the stream could not be closed yet.
armeria/core/src/main/java/com/linecorp/armeria/server/Http2RequestDecoder.java
Lines 242 to 244 in b9dc1ad
Modifications:
requests with
PROTOCOL_ERROR
.Result:
You no longer see
ClassCastException
when an invalid path request isreceived.