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
Data corruption due to interaction between SslHandler
and HttpObjectEncoder
#11792
Comments
Motivation: There's a chance of HTTP content corruption when: - The current connection is TLS; - The current response is using chunked encoding; and - The content length is greater than 16378 (16384 - 6). .. due to a bug in Netty: netty/netty#11792 Modifications: - Fixed the math mistake in `Http1ObjectEncoder.MAX_TLS_DATA_LENGTH`, to prevent `HttpObjectEncoder` from putting its static final `ByteBuf` in the first place of `SslHandler`'s internal queue. Result: - No more HTTP content conrruption.
Motivation: There's a chance of HTTP content corruption when: - The current connection is TLS; - The current response is using chunked encoding; and - The content length is greater than 16378 (16384 - 6). .. due to a bug in Netty: netty/netty#11792 Modifications: - Fixed the math mistake in `Http1ObjectEncoder.MAX_TLS_DATA_LENGTH`, to prevent `HttpObjectEncoder` from putting its static final `ByteBuf` in the first place of `SslHandler`'s internal queue. Result: - No more HTTP content conrruption.
@trustin can you just do a PR so we can easily merge and attribute ? |
Motivation: There's a chance of HTTP content corruption when: - The current connection is HTTP/1 over TLS; - The current response is using chunked encoding; and - The content length is greater than 16378 (16384 - 6). .. due to a bug in Netty: netty/netty#11792 Modifications: - Fixed the math mistake in `Http1ObjectEncoder.MAX_TLS_DATA_LENGTH`, to prevent `HttpObjectEncoder` from putting its static final `ByteBuf` in the first place of `SslHandler`'s internal queue. Result: - No more HTTP content corruption.
@normanmaurer @trustin Do you have a preference/opinion on how to prevent this class of issues going forward? There are a few more instances where static buffers are not marked as read-only, quick search found [1][2], there may be more. We can add unit-tests for the affected classes, but it will not prevent new instances in other places. Ideally, there should be a compile-time check or validation that'll catch issues like that during development, before changes are merged. [1] https://github.com/netty/netty/blob/netty-4.1.68.Final/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java#L34-L39 |
@normanmaurer, @alexc-db will send out a PR. @alexc-db, I'd recommend 1) making sure all |
@trustin sounds good! |
Added validation check in netty/netty-build#12, seems like I don't have permissions to assign reviewers, @normanmaurer - could you please take a look? |
Add validation for static final byte buffers to make sure they are unreleasable and read-only as a follow up for netty/netty#11792. Also enabled support for checkstyle warning suppression using `SuppressWarnings` annotation. Verified by installing it locally and running it against local Netty code. Example: ``` → mvn validate [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Detecting the operating system and CPU architecture [INFO] ------------------------------------------------------------------------ [INFO] os.detected.name: osx [INFO] os.detected.arch: x86_64 [INFO] os.detected.bitness: 64 [INFO] os.detected.version: 10.16 [INFO] os.detected.version.major: 10 [INFO] os.detected.version.minor: 16 [INFO] os.detected.classifier: osx-x86_64 [INFO] [INFO] ---------------------< io.netty:netty-codec-http >---------------------- [INFO] Building Netty/Codec/HTTP 4.1.70.Final-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-enforcer-plugin:1.4.1:enforce (enforce-maven) @ netty-codec-http --- [INFO] [INFO] --- maven-enforcer-plugin:1.4.1:enforce (enforce-tools) @ netty-codec-http --- [INFO] [INFO] --- maven-checkstyle-plugin:3.1.0:check (check-style) @ netty-codec-http --- [INFO] Starting audit... [ERROR] /Users/alex.chekunkov/Dev/github.com/alexc-db/netty/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java:53: static final buffer assignment should match pattern (Unpooled\s*\.)?unreleasableBuffer\(.*?\)\s*\.asReadOnly\(\) [StaticFinalBuffer] [ERROR] /Users/alex.chekunkov/Dev/github.com/alexc-db/netty/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java:54: static final buffer assignment should match pattern (Unpooled\s*\.)?unreleasableBuffer\(.*?\)\s*\.asReadOnly\(\) [StaticFinalBuffer] [ERROR] /Users/alex.chekunkov/Dev/github.com/alexc-db/netty/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java:34: static final buffer assignment should match pattern (Unpooled\s*\.)?unreleasableBuffer\(.*?\)\s*\.asReadOnly\(\) [StaticFinalBuffer] [ERROR] /Users/alex.chekunkov/Dev/github.com/alexc-db/netty/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java:36: static final buffer assignment should match pattern (Unpooled\s*\.)?unreleasableBuffer\(.*?\)\s*\.asReadOnly\(\) [StaticFinalBuffer] [ERROR] /Users/alex.chekunkov/Dev/github.com/alexc-db/netty/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameEncoder.java:38: static final buffer assignment should match pattern (Unpooled\s*\.)?unreleasableBuffer\(.*?\)\s*\.asReadOnly\(\) [StaticFinalBuffer] Audit done. [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.755 s [INFO] Finished at: 2021-10-26T20:00:56-07:00 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:3.1.0:check (check-style) on project netty-codec-http: Failed during checkstyle execution: There are 5 errors reported by Checkstyle 8.29 with io/netty/checkstyle.xml ruleset. -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException ``` Findings from the check are fixed in netty/netty#11802.
Motivation: Fix #11792 and avoid potential corruption issues caused by writable shared buffers. TL;DR: `HttpObjectEncoder` sends a duplicate of its static final `ByteBuf` field to encode HTTP chunks, such as `0\r\n\r\n`. This reused buffer is neither read-only nor unexpandable, and thus anyone in the pipeline can modify or expand it. As a result, `SslHandler` can sometimes expand it and append stuff to it, causing unexpected data corruption. Modification: Most of the static final buffers in the project are made unreleasable and read-only by wrapping them in `Unpooled.unreleasableBuffer(...).asReadOnly()`. Buffers that were excluded: - Unpooled.EMPTY_BUFFER - EmptyByteBuf instances are not readable/writable/releasable. Wrapping this buffer in `Unpooled.unreleasableBuffer(...).asReadOnly()` would change the instance type, since it's a public API it could be a potential breaking change. - ReplayingDecoderByteBuf.EMPTY_BUFFER - since it uses Unpooled.EMPTY_BUFFER and not releasable too. Excluded buffers are marked with `SuppressWarnings` to skip check added in netty/netty-build#12. Result: Fixes #11792.
Motivation: Fix #11792 and avoid potential corruption issues caused by writable shared buffers. TL;DR: `HttpObjectEncoder` sends a duplicate of its static final `ByteBuf` field to encode HTTP chunks, such as `0\r\n\r\n`. This reused buffer is neither read-only nor unexpandable, and thus anyone in the pipeline can modify or expand it. As a result, `SslHandler` can sometimes expand it and append stuff to it, causing unexpected data corruption. Modification: Most of the static final buffers in the project are made unreleasable and read-only by wrapping them in `Unpooled.unreleasableBuffer(...).asReadOnly()`. Buffers that were excluded: - Unpooled.EMPTY_BUFFER - EmptyByteBuf instances are not readable/writable/releasable. Wrapping this buffer in `Unpooled.unreleasableBuffer(...).asReadOnly()` would change the instance type, since it's a public API it could be a potential breaking change. - ReplayingDecoderByteBuf.EMPTY_BUFFER - since it uses Unpooled.EMPTY_BUFFER and not releasable too. Excluded buffers are marked with `SuppressWarnings` to skip check added in netty/netty-build#12. Result: Fixes #11792.
See release notes at https://netty.io/news/2021/10/11/4-1-70-Final.html, and, in particular, the issue at netty/netty#11792.
Motivation: Fix netty#11792 and avoid potential corruption issues caused by writable shared buffers. TL;DR: `HttpObjectEncoder` sends a duplicate of its static final `ByteBuf` field to encode HTTP chunks, such as `0\r\n\r\n`. This reused buffer is neither read-only nor unexpandable, and thus anyone in the pipeline can modify or expand it. As a result, `SslHandler` can sometimes expand it and append stuff to it, causing unexpected data corruption. Modification: Most of the static final buffers in the project are made unreleasable and read-only by wrapping them in `Unpooled.unreleasableBuffer(...).asReadOnly()`. Buffers that were excluded: - Unpooled.EMPTY_BUFFER - EmptyByteBuf instances are not readable/writable/releasable. Wrapping this buffer in `Unpooled.unreleasableBuffer(...).asReadOnly()` would change the instance type, since it's a public API it could be a potential breaking change. - ReplayingDecoderByteBuf.EMPTY_BUFFER - since it uses Unpooled.EMPTY_BUFFER and not releasable too. Excluded buffers are marked with `SuppressWarnings` to skip check added in netty/netty-build#12. Result: Fixes netty#11792.
Motivation: Fix netty#11792 and avoid potential corruption issues caused by writable shared buffers. TL;DR: `HttpObjectEncoder` sends a duplicate of its static final `ByteBuf` field to encode HTTP chunks, such as `0\r\n\r\n`. This reused buffer is neither read-only nor unexpandable, and thus anyone in the pipeline can modify or expand it. As a result, `SslHandler` can sometimes expand it and append stuff to it, causing unexpected data corruption. Modification: Most of the static final buffers in the project are made unreleasable and read-only by wrapping them in `Unpooled.unreleasableBuffer(...).asReadOnly()`. Buffers that were excluded: - Unpooled.EMPTY_BUFFER - EmptyByteBuf instances are not readable/writable/releasable. Wrapping this buffer in `Unpooled.unreleasableBuffer(...).asReadOnly()` would change the instance type, since it's a public API it could be a potential breaking change. - ReplayingDecoderByteBuf.EMPTY_BUFFER - since it uses Unpooled.EMPTY_BUFFER and not releasable too. Excluded buffers are marked with `SuppressWarnings` to skip check added in netty/netty-build#12. Result: Fixes netty#11792.
Motivation: Fix netty#11792 and avoid potential corruption issues caused by writable shared buffers. TL;DR: `HttpObjectEncoder` sends a duplicate of its static final `ByteBuf` field to encode HTTP chunks, such as `0\r\n\r\n`. This reused buffer is neither read-only nor unexpandable, and thus anyone in the pipeline can modify or expand it. As a result, `SslHandler` can sometimes expand it and append stuff to it, causing unexpected data corruption. Modification: Most of the static final buffers in the project are made unreleasable and read-only by wrapping them in `Unpooled.unreleasableBuffer(...).asReadOnly()`. Buffers that were excluded: - Unpooled.EMPTY_BUFFER - EmptyByteBuf instances are not readable/writable/releasable. Wrapping this buffer in `Unpooled.unreleasableBuffer(...).asReadOnly()` would change the instance type, since it's a public API it could be a potential breaking change. - ReplayingDecoderByteBuf.EMPTY_BUFFER - since it uses Unpooled.EMPTY_BUFFER and not releasable too. Excluded buffers are marked with `SuppressWarnings` to skip check added in netty/netty-build#12. Result: Fixes netty#11792.
TL;DR:
HttpObjectEncoder
sends a duplicate of its static finalByteBuf
field to encode HTTP chunks, such as0\r\n\r\n
. This reused buffer is neither read-only nor unexpandable, and thus anyone in the pipeline can modify or expand it. As a result,SslHandler
can sometimes expand it and append stuff to it, causing unexpected data corruption. It'd be nice if this issue is fixed soon and a new release is out given its severity.Here's the full analysis from my colleague @alexc-db:
Channel.flush()
is called,SslHandler
starts getting items from thependingUnencryptedWrites
queue [1] - it gets entries from the internalbufAndListenerPairs
and accumulates them in thetoReturn
buffer [2].toReturn
isnull
and thuscomposeFirst
is called. That method by default just returns the reference to the first entry (it’s overridden inSslHandlerCoalescingBufferQueue
to have special handling forCompositeByteBuf
, but it’s not relevant to the issue here). The following calls are callingcompose
and passtoReturn
there.compose
callsattemptCopyToCumulation
which checks iftoReturn
is writable and its capacity can be expanded to write new bytes from the following queued buffers - if it passes checks in [3],attemptCopyToCumulation
copies bytes from the next entries to the originaltoReturn
buffer (as we saw above it can be the first entry), if it does not pass the checks -SslHandlerCoalescingBufferQueue
callscopyAndCompose
which creates a new biggerbuf
and copies bytes fromtoReturn
and next entry there, making it a newtoReturn
.flush
is called andbufAndListenerPairs
has multiple buffers queued where the first entry is writable, had enough capacity for the bytes in the next entries, and shared across multiple threads - two threads will start copying bytes into the same memory addresses which will cause corruption. In our local reproducer that has more dependencies that just Netty, it wasZERO_CRLF_CRLF_BUF
[4] (duplicate()
call doesn’t make a copy, just wraps the original direct buffer).I was able to make tests pass by marking
ZERO_CRLF_CRLF_BUF read-only
[5].[1] https://github.com/netty/netty/blob/netty-4.1.68.Final/handler/src/main/java/io/netty/handler/ssl/SslHandler.java#L817
[2] https://github.com/netty/netty/blob/netty-4.1.68.Final/transport/src/main/java/io/netty/channel/AbstractCoalescingBufferQueue.java#L175
[3] https://github.com/netty/netty/blob/netty-4.1.68.Final/handler/src/main/java/io/netty/handler/ssl/SslHandler.java#L2326-L2334
[4] https://github.com/netty/netty/blob/netty-4.1.68.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java#L210
[5]
Netty version
4.1.68.Final and 4.1.69.Final and of course possible in older versions.
The text was updated successfully, but these errors were encountered: