July 21, 2026·6 min read·engineeringvalkeysecuritytlsstreams

Valkey 9.1.1 fixes TLS iteration and stream ownership flaws

Valkey 9.1.1 fixes two code-execution vulnerabilities in TLS handling and stream loading. It also tightens database ACL checks around command argument parsing.

A callback invoked while walking a list can destroy an element the walker expects to visit next. Valkey's TLS pending-data processing had that problem when a command closed another connection. The list iterator retained a reference that the callback could invalidate.

Valkey 9.1.1, released on July 21, fixes that defect as CVE-2026-56684. Its tagged release notes also identify CVE-2026-63639 in corrupted stream RDB loading. Both carry potential remote code-execution consequences under the documented conditions. The project marks the release SECURITY urgency and recommends applying the fixes as soon as possible.

I default to Valkey where Redis semantics are required. That preference makes the maintenance of its connection and persistence paths relevant to me. Neither defect needs a new public command to become important. Both arise inside existing workflows when object lifetime differs from what later code assumes.

TLS callbacks can mutate the pending list

The TLS patch explains that tlsProcessPendingData walked a list using an iterator that cached the next node. Processing the current connection could invoke a handler. Through CLIENT KILL and connection cleanup, that handler could remove another pending connection from the same list.

If the removed connection was the cached next node, advancing the iterator dereferenced freed memory. The release notes describe an authenticated client using CLIENT KILL as the relevant command path. The flaw therefore concerns connection lifetime during command processing, rather than certificate parsing or the cryptographic properties of TLS.

That distinction matters when identifying the affected component. The presence of TLS is part of the code path, but the failed assumption is about mutation during iteration. Replacing a certificate would not change the iterator's ownership of its cached reference.

The patch description reports a crash and potential code execution. Those consequences justify treating the update as security maintenance. They do not establish that a particular installation has already been exploited, and the source does not provide an installation-wide incident history.

Pop the connection before invoking its handler

The fix removes each connection from the head of the pending list before processing it. It clears the connection's reference to the removed list node. The handler can then mutate the remaining list without invalidating an iterator position held across that call.

This is a change in the traversal's ownership discipline. The routine no longer retains a reference to a future node while executing code that might destroy that node. When it is ready for another connection, it consults the current list again.

Some connections still have buffered data and can add themselves to the tail. The patch captures the list length on entry and limits processing to that initial count. Without a bound, removing and re-adding connections could create repeated processing within the same call.

A connection whose state update is postponed is also re-added to the tail for a later call. That keeps the deferral behavior while using the new traversal. The fix therefore accounts for both destructive callbacks and ordinary requeueing.

I find the bounded traversal particularly useful to understand. Avoiding a stale pointer solves the immediate memory-safety problem, while the entry count preserves a progress boundary for the event loop. A memory fix that accidentally allowed an unbounded loop would introduce a different operating failure.

Stream loading needs a single consumer relationship

The stream RDB patch addresses a different ownership assumption. A stream consumer group's pending-entry state includes NACK structures shared between the group's global pending-entry list and the appropriate consumer's list.

The loader assigned the NACK's consumer pointer while reading consumer-specific data. It did not first establish that another consumer had not already claimed the same structure. Corrupted input could therefore list one message identifier under different consumers.

That produced multiple consumer references to one NACK with an inconsistent consumer relationship. Deleting one consumer could release the structure while another consumer still referred to it. The memory error appeared after loading had already accepted the invalid relationship.

The new check rejects the input when a NACK already belongs to a different consumer. It reports corrupted RDB data and releases the partially loaded stream object through the error path. The existing check for duplicate entries within the same consumer remains responsible for that separate case.

This is semantic validation of a persisted structure. Valid bytes and a recognizable format are insufficient if their internal relationships violate ownership rules. A loader must establish the invariants that later deletion and cleanup code rely on.

A crafted dump tests the invalid relationship

The regression test starts with a valid stream, a consumer group and two consumers. Each consumer initially has a different pending message. The test obtains the serialized data and changes the second consumer's pending identifier to the first consumer's identifier.

That produces the cross-consumer sharing condition directly. The expected result is rejection as a bad data format. The test does not need to wait for a later consumer deletion to demonstrate that the loader has recognized the invalid structure.

The test disables checksum validation because it deliberately modifies serialized bytes. That is a test setup detail, not an operating recommendation. Its purpose is to reach the semantic check being added without having the modified input rejected earlier for a checksum mismatch.

For someone reading the patch, the sequence explains precisely what the new check proves. It rules out one NACK being claimed by different consumers during loading. It does not make a general statement that every possible corrupted stream input has been characterized.

The two security fixes also differ in when they prevent invalid access. TLS processing changes traversal before calling code that can remove connections. Stream loading rejects an invalid ownership relationship before accepting the object into normal use. This distinction helps explain why the patches look different despite both addressing use-after-free behavior. One routine must continue operating while its collection changes. The other can reject the input that would create an unsafe object graph. Their error paths therefore carry different responsibilities, even though later memory access is the consequence in both cases.

Database ACLs must parse the same command grammar

The release also fixes database ACL enforcement for COPY. The COPY authorization patch describes an argument parser that assumed the DB token occupied one fixed position.

COPY permits optional REPLACE and DB arguments in different orders. It also accepts repeated DB clauses. The execution parser could therefore identify a destination database that the authorization helper had not examined.

The fix walks the argument tokens, validates their syntax and collects the database identifiers. Authorization checks each DB clause. The regression cases include REPLACE before DB and repeated DB clauses containing both allowed and denied identifiers.

This is a parser agreement problem. The code deciding what the command means and the code deciding whether it is allowed need to recognize the same accepted grammar. A permission check on a common argument ordering does not cover another ordering that execution still accepts.

The release notes also identify database ACL enforcement for CLUSTER FLUSHSLOT, which removes keys across databases. That concerns the operation's scope. Both fixes show why database authorization has to follow every path by which a command identifies or affects data.

Compatibility includes saved ACL text

Another fix omits the implicit alldbs rule from ACL LIST, ACL SAVE and CONFIG REWRITE output so older versions can parse it. The release distinguishes an implicit default from a newly expressed database restriction.

That is relevant when configuration moves between versions. A server may understand its own generated configuration while an older server cannot read a newly emitted token. Persisted administrative text is part of compatibility even when ordinary GET and SET commands remain unchanged.

The same notes include the ACL LOAD deferred-client fix and several additional input-validation changes. Their mechanisms should be assessed individually. Combining them into a generic claim that ACLs or persistence are now safe would lose the conditions each patch actually addresses.

For an existing 9.1 deployment, the two named vulnerabilities make this a security update, independently of interest in new behavior. The TLS patch establishes safe traversal across destructive callbacks. The stream patch establishes consumer ownership before later code can delete pending state. Those are specific invariants enforced by the released changes, with the accompanying authorization fixes extending policy checks across the command grammar users can already submit.