GHSA-p9jm-q85p-7mcp
Netty: RedisArrayAggregator max-elements failure leaves retained partial aggregate state
Quick fix
GHSA-p9jm-q85p-7mcp — io.netty:netty-codec-redis: upgrade to the fixed version with the command below.
# pom.xml: bump <version>4.1.136.Final</version> for io.netty:netty-codec-redis Details
## Summary
`RedisArrayAggregator` clears retained partial aggregate state when the `maxNestedArrayDepth` limit is exceeded, but it does not clear the same state when the sibling `maxElements` limit is exceeded. A peer can start a valid RESP array, send a bulk-string child, then send a nested array header longer than the configured `maxElements`. Netty throws a decoder exception, but the existing partial aggregate remains retained in the handler.
If the application leaves the channel alive after the exception, later messages are still consumed into the pre-error aggregate. The supplied PoV proves both the retained `ByteBuf` reference and the stale parser state continuation.
## Technical Details
`RedisArrayAggregator.decode(...)` retains non-array messages before adding them to `depths.peek().children`. In `decodeRedisArrayHeader(...)`, the `header.length() > maxElements` branch throws immediately:
```java if (header.length() > maxElements) { throw new CodecException("this codec doesn't support longer length than " + maxElements); } ```
The immediately following nested-depth branch clears retained aggregate state before throwing:
```java if (depths.size() >= maxNestedArrayDepth) { releaseAndClearDepths(); throw new CodecException("max nested array depth exceeded: " + maxNestedArrayDepth); } ```
The missing cleanup in the first branch leaves retained children and aggregate state reachable after the exception.
## PoC
Place the supplied `RedisArrayAggregatorIncompleteCleanupPovTest.java` under:
`codec-redis/src/test/java/io/netty/handler/codec/redis/`
Run:
```fish ./mvnw -pl codec-redis -am -Dtest=RedisArrayAggregatorIncompleteCleanupPovTest -Dsurefire.failIfNoSpecifiedTests=false -DskipNativeTests -DskipAutobahnTests test ```
The test suite includes:
- serialized RESP trigger through `RedisDecoder`, `RedisBulkStringAggregator`, and `RedisArrayAggregator`; - direct refcount proof that max-elements overflow does not release the retained child immediately; - post-exception continuation proof that the stale aggregate consumes a later message; - nested-depth controls that clear the same partial aggregate state.
All five tests pass on current `4.2`, `4.2.15.Final`, and `4.1.135.Final`.
## Impact
For Redis codec pipelines that continue after codec exceptions, an unauthenticated peer can keep attacker-controlled aggregate state alive across a security-limit exception. This can pin retained pooled buffers until channel close/removal or until a later message completes the stale aggregate.
`RedisBulkStringAggregator` permits bulk strings up to `RedisConstants.REDIS_MESSAGE_MAX_LENGTH` (`512MB`), so the retained child can be large in deployments that aggregate untrusted Redis streams.
Applications that always close the channel or remove the handler on decoder exceptions will trigger existing cleanup; the issue is the missing immediate cleanup on the max-elements failure path while the handler remains installed.
## Suggested Fix
Call `releaseAndClearDepths()` before throwing from the max-elements branch. Consider applying the same cleanup to all unrecoverable `decodeRedisArrayHeader(...)` error exits that can occur while `depths` is non-empty.
## Affected Package/Versions
`io.netty:netty-codec-redis`
Confirmed on:
- current `4.2` branch head `7bae566a93e69409697fe57fa807910ba5c9720e` - `4.2.15.Final` at `a41f7b289ce1` - `4.1.135.Final` at `f05f765d8146`
## Advisory History
This differs from the public Redis codec advisories because it reproduces on their patched tags:
- `GHSA-5w86-c3rq-vjj7` - `GHSA-3244-j874-rhc2` / `CVE-2026-44250` - `GHSA-6jv9-x5w9-2ccm` / `CVE-2026-48006` - `GHSA-6ghj-frrj-jjj3` / `CVE-2026-44890`
## Why This Is Not Intended Behavior
The public API docs document `RedisArrayAggregator` as aggregating `RedisMessage` parts into `ArrayRedisMessage` and document a `CodecException` when an array header exceeds `maxElements`. They do not document preserving pre-exception partial aggregate state after that limit fires.
The adjacent nested-depth branch already calls `releaseAndClearDepths()` before throwing. The max-elements branch is the sibling aggregation-limit branch but throws without cleanup. Netty's later Redis lifecycle cleanup patch explicitly added release behavior for nested-array failure and handler removal, leaving the max-elements failure branch as a missed cleanup path.
Are you affected?
Enter the version of the package you're using.
Affected packages
0 Fixed in: 4.1.136.Final # pom.xml: bump <version>4.1.136.Final</version> for io.netty:netty-codec-redis 4.2.0-Final Fixed in: 4.2.16.Final # pom.xml: bump <version>4.2.16.Final</version> for io.netty:netty-codec-redis References
- https://github.com/netty/netty/security/advisories/GHSA-p9jm-q85p-7mcp [WEB]
- https://github.com/netty/netty/pull/17065 [WEB]
- https://github.com/netty/netty/commit/5b68c61f37aa4a3045cba624cbea239655c9003b [WEB]
- https://github.com/netty/netty/commit/bb2ff68a1fb71cb4b0eb9a9e17b66c52aff680c6 [WEB]
- https://github.com/netty/netty [PACKAGE]