September 25, 2019·4 min read·engineeringvalkeyredissecurityreliability

Redis 5.0.6 rejects a malformed HyperLogLog update path

Redis 5.0.6 adds an end-of-buffer check to sparse HyperLogLog updates. The maintainers identify untrusted client access as the condition that makes this release critical.

A parser can inspect valid bytes and still finish without finding the object it was looking for. Code after the loop must distinguish that outcome from a successful search. Redis 5.0.6 corrects such a boundary in sparse HyperLogLog updates. Reaching the end of the encoded representation now causes an invalid-format result before the function continues interpreting the current opcode.

The 25 September release notes describe malformed HyperLogLog strings causing invalid memory access. They classify the release as critical for instances exposed to untrusted users. The maintainer's initial assessment is denial of service, with exploitability beyond that not established by the announcement. That wording is narrower than a claim of confirmed remote code execution.

I have used Redis since its release and appreciate its compact specialized types. This correction concerns the implementation's handling of an invalid object, separately from the useful approximation that HyperLogLog provides. A statistical estimate may be approximate, but the memory boundaries used to compute and update it still need explicit enforcement.

Searching for a logical register

The 5.0.6 HyperLogLog implementation stores sparse register runs as encoded opcodes. An update receives the logical index of a register and looks for the opcode whose span covers it. The parser tracks both its current byte pointer and the first logical register represented by the current run.

That arrangement saves space because many registers can share a short encoding. It also means a byte position and a logical register index are different coordinates. Advancing over one opcode can advance one or two encoded bytes while accounting for many registers. The loop must preserve enough state to establish that it found the desired register inside an existing opcode.

The function uses while (p < end) while searching. It can leave that loop by finding the requested index, but it can also leave because the byte pointer reaches the end. Both outcomes arrive at the same code after the loop. Only the first establishes that the current pointer denotes a usable opcode for the update.

The corrected condition rejects either a zero span or a pointer at or beyond the end. Previously, it rejected only a zero span. A nonzero span from a previously examined opcode did not prove that the pointer still referred to encoded data. The added pointer check supplies the missing postcondition. It states what the following decoder needs to know at that point, even when earlier loop iterations processed an opcode and left a nonzero span behind.

The correction precedes further decoding

After the guard, the function determines the next opcode and caches the current opcode's type and length. It then handles simple updates or splits a run into replacement opcodes. Those later stages assume the search produced a valid location inside the representation.

The guard therefore belongs before those operations. A check after an opcode has already been inspected cannot justify the earlier access. The release's source places the rejection immediately after the search loop, before calculating the next opcode from the current pointer.

The same implementation reserves space before taking pointers because an update can enlarge a sparse representation. That allocation step solves a different problem. Spare allocation capacity does not make bytes beyond the logical end part of a valid encoded object. Memory capacity and representation length must not be treated as interchangeable bounds.

This distinction is useful beyond the particular data structure. A container can have room for more bytes without containing another valid element. The sparse parser's logical end remains the point after its actual representation. Its later update logic needs a real opcode, not merely an address inside a possibly larger allocation.

Exposure determines the stated urgency

The release notes qualify their critical rating by untrusted user access. That is an assessment of which deployments allow a client outside the operator's trust assumptions to exercise the affected behavior. It does not require the installation to be a public unauthenticated endpoint. An authenticated user can still be untrusted for the purpose of constructing unusual data.

At the same time, the announcement does not state that ordinary valid HyperLogLog use inevitably triggers the bug. It describes a malformed representation and says the trigger is complex. The difference matters when deciding how to communicate the defect. The relevant risk is a client-induced failure in invalid-object handling, not normal statistical error in a cardinality estimate.

I prefer the maintainer's qualified account to either minimizing the issue as an obscure edge case or claiming an unverified exploit outcome. Invalid memory access in a service warrants the update in the exposure context the release identifies. The announcement's initial denial-of-service assessment should retain its stated uncertainty.

The source correction is sufficient to explain the mechanism without providing a malformed object or a sequence for constructing one. Its essential fact is the missing end check after searching sparse opcodes. That is the condition repaired in the versioned implementation.

Shutdown replication receives a separate fix

The release also changes flushing of pending replica output. The networking code at the same tag checks that a replica is online, can receive writes and has pending replies before sending buffered output. It also respects the state in which a replica must wait for an acknowledgement before being put online.

This correction addresses delivery of pending buffers during shutdown. It does not share the HyperLogLog parser's mechanism, and it does not establish a new synchronous replication guarantee. The release notes describe it as another significant change alongside the invalid-object fix.

Keeping those changes separate makes the upgrade rationale clearer. An operator exposed to untrusted clients has the critical HyperLogLog reason to update. The pending-output correction concerns an orderly lifecycle operation with its own state checks. Both belong to 5.0.6, but the security significance comes from rejecting malformed encoded data before the sparse update proceeds past the end of its representation.