GHSA-p9jm-q85p-7mcp Netty: RedisArrayAggregator max-elements failure leaves retained partial aggregate state
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:The immediately following nested-depth branch clears retained aggregate state before throwing:The missing cleanup in the first branch leaves retained children and aggregate state reachable after the exception.PoCPlace the supplied `RedisArrayAggregatorIncompleteCleanupPovTest.java` under:`codec-redis/src/test/java/io/netty/handler/codec/redis/`Run: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`.ImpactFor 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 FixCall `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 HistoryThis 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 BehaviorThe 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.
CVSS v3.1 score: 6.5