Valkey 8.1.8 fixes ownership during deferred client cleanup
Valkey 8.1.8 fixes an ACL reload use-after-free and an I/O client cleanup race. A separate stream-loading fix shows how malformed input can expose duplicate memory ownership.
Closing a connection does not necessarily destroy its client object immediately. That difference caused an ACL reload defect fixed in Valkey 8.1.8, released on June 2. A client awaiting deferred cleanup could retain a pointer to a user that the reload had already freed.
The release's tagged notes mark it HIGH urgency for affected installations. They also include an I/O-thread cleanup race and a double free while loading corrupted stream data. These fixes concern different paths, but each depends on describing the lifetime of an object precisely.
I default to Valkey for applications that need Redis semantics. That preference includes paying attention to maintenance on the branch an installation actually uses. An internal ownership fix can matter more to an existing deployment than a feature available in a newer line.
ACL reload can outlive the user being removed
The ACL LOAD fix explains the sequence. Reloading ACL configuration can remove a user. Clients authenticated as that user then need to close, but client protection or pending I/O can defer their destruction.
Those clients remain in the server's client collection. Before the fix, their user pointers still referred to the old user objects. Once the reload released the old user collection, the pointers became invalid.
The defect could then appear during code that merely inspected a client. Its description names CLIENT LIST and periodic client maintenance as examples of code that could dereference the stale user. The operation discovering the error need not be the operation that created it.
That makes this a useful example of why a successful administrative response is incomplete evidence about memory safety. The ACL reload could perform its intended policy update while leaving a surviving object with an invalid reference. Later activity would encounter the broken relationship.
Clearing the pointer needed an authorization decision
The first patch version set the client's user pointer to null before requesting closure. That removed the dangling reference. Review then identified a different consequence, because a null user could temporarily pass ACL checks as unrestricted.
The final fix uses clientSetUser with DefaultUser and an unauthenticated state before requesting closure. The tagged ACL implementation contains that sequence in the reload path. It follows an existing defensive pattern used when removing a user and closing its clients.
The ordering matters. The client must stop referring to the soon-to-be-freed user before the old user object disappears. Its replacement state also needs an explicit authorization meaning during the remaining lifetime.
I find the review correction instructive because the first solution addressed the memory error but changed a security assumption. An object can remain valid enough to inspect while no longer being authorized to issue ordinary work. Those conditions need to be represented together.
The patch does not turn closure into an immediate operation. It makes the deferred interval safe by changing the client state before that interval. That is a more precise repair than assuming all callers can force immediate destruction.
The regression test preserves the deferred interval
The patch adds a debug facility that protects a selected client from immediate freeing. Its test authenticates a client as a temporary user, reloads an ACL file without that user, and then requests CLIENT LIST.
The protected client must still appear in the result. That assertion is important because a test that accidentally destroys the client first would not exercise the dangling-pointer condition. The test deliberately preserves the object whose user reference was previously invalid.
Review also strengthens the client-identifier assertion so that one identifier cannot accidentally match the prefix of another. That is a small test detail with a clear purpose. The observed surviving client must be the one the test protected.
This is a deterministic reproduction of a deferred-cleanup condition. It does not require repeated connection churn to make a race occur by chance. The test establishes the lifecycle ordering directly and then exercises the inspection path that previously dereferenced freed state.
I/O cleanup must reset the bytes-read state
The separate I/O-thread fix concerns a client marked for closure as soon as possible. The read routine returned without resetting the number of bytes read.
The patch description explains that I/O threads could then interpret the stale count as new data and crash. The fix sets the count to zero before returning from that early-exit branch.
This is an example of state that must describe the current operation even when no read occurs. Returning false was not enough because another part of the implementation also consumed the byte count. An early exit has to preserve both parts of that contract.
The change is short, but its consequence crosses the thread boundary. A worker's interpretation of the client state depends on fields left by the read path. The closed-client case needs a consistent result in those fields.
Corrupted stream loading exposed duplicate cleanup
The stream consumer loading fix addresses a double free in an error path. A pending-entry object obtained from the consumer group's pending-entry list still belonged to that group's structure.
On the failing path, decrementing the stream object's reference count already released the nested pending-entry allocation through stream cleanup. An additional explicit free then attempted to release the same allocation again. The patch removes that redundant action.
Its regression test supplies corrupted data with a duplicate consumer pending-entry identifier. The loader should report a bad data format and remain able to answer PING. The test targets the error branch where normal ownership assumptions previously failed.
These defects illustrate why maintenance work includes more than handling valid commands. Administrative replacement and malformed persisted input can both reach objects during partial transitions. In 8.1.8, the fixes make those transitions explicit, with a client reassigned before deferred closure and a nested stream object released through its existing owner.