Redis 4.0.10 repairs Lua bounds checks and shrinking-table scans
Redis 4.0.10 fixes security defects in Lua helper libraries and a SCAN cursor error during hash-table shrinkage. The patches show why bounds checks and incremental iteration each require precise invariants.
A script can stay within its advertised API and still reach unsafe native code beneath it. Redis 4.0.10 fixes security defects in the C libraries exposed through its Lua scripting environment. The same release repairs a different correctness failure in incremental scanning, where resizing a hash table could cause the scan to omit entries.
The June 13 release notes assign critical urgency to the security fixes and high urgency to the SCAN-family fix. The version-pinned notes distinguish those problems from moderate-urgency replication and Sentinel repairs. That separation helps identify which operational assumptions each patch restores.
I have used Redis since it first appeared, and its small commands remain a reason to like it. Those commands depend on internal invariants that application code cannot repair. This release matters because a valid server interface needs both safe execution and the completeness that its documented operations promise.
Lua calls still depend on native stack capacity
The initial cmsgpack correction explains a defect found by the Apple Vulnerability Research team. A variadic packing function pushed additional values onto the Lua C API stack without checking available capacity. The number of values depended on the arguments supplied by the caller.
The commit identifies Redis versions at or above 2.8.18 as affected by this particular problem. It reports that the defect can crash the server and describes exploitation as a possibility, without claiming a deterministic exploit against every build. That distinction should survive any summary of the release. A demonstrated crash and a proven general-purpose exploit are different findings.
The patch checks stack capacity before proceeding with the packing operation and returns an argument error when it cannot accommodate the request. The relevant stack is the one managed through the Lua C API. Calling this merely a recursion limit would miss the mechanism described in the patch, since a large argument list can cause the problematic pushes.
The follow-up cmsgpack patch adds checks at other points that push values. These include table encoding and array decoding. It also adds a check when unpacking returns an offset. The additional checks show why securing the first reported entry point is not the whole review. Each native helper has to account for the stack slots its own operations require.
An application cannot make this library safe by treating serialization as an ordinary pure computation. The implementation manipulates runtime state in native code, even when the caller sees a Lua function accepting values and returning bytes. The patch corrects that implementation boundary. Restricting who can submit scripts and correcting the native code address different parts of the problem.
An arithmetic comparison can approve an invalid read
The struct library has another class of bounds error. The library update credits an Apple audit with finding an integer-overflow problem in a bounds-checking statement. Its commit message says the affected library has been present since Redis introduced scripting.
The subsequent offset repair makes the check more explicit. Lua positions are one-based, while the C pointer calculation uses a zero-based offset. The patch checks the supplied position before decrementing it, and it changes the condition that verifies a requested object fits inside the input string.
Conceptually, the old comparison added the position and object size, then compared the sum with the input length. If the addition wraps, a small-looking result can pass the comparison despite the intended region being invalid. The revised condition first requires that the size fit within the total length, then compares the position with the remaining length after subtracting the size.
That ordering is important. Subtracting an unchecked size can itself wrap, so checking the size against the length must precede using the difference. The corrected expression encodes both conditions. A bounds check is only useful if the arithmetic used by the check cannot invalidate its meaning.
This is also why validating the Lua value type is insufficient. A position can be an integer and still describe an invalid region. The native implementation needs a range check consistent with its pointer arithmetic, including the conversion between the two indexing conventions.
The post deliberately describes the published corrections without assigning later vulnerability identifiers or importing later assessments. The June release and the patches already provide a concrete account of the unsafe operations and the intended repairs. That is enough to explain why a server running scripts needs the update.
SCAN must survive movement between hash tables
The scanning fix concerns a promise about iteration. Redis dictionaries can resize while an application carries a cursor between calls. The dictionary implementation explains that the iterator keeps no server-side state for an individual traversal. A caller starts with cursor zero and passes each returned cursor into the next call, finishing when the cursor returns to zero.
A conventional increasing bucket index does not suffice when the table changes size between calls. Redis uses power-of-two table sizes and derives a bucket from a mask of the hash. Increasing or decreasing the table size changes how many hash bits participate in that mask. Entries can consequently move between buckets that have related low-order bits.
The implementation advances a reversed-bit cursor. It reverses the bits, increments, then reverses them back. This visits the high-order cursor combinations in an order designed to accommodate resizing. The cursor is therefore traversal state, not a count of completed entries or a stable ordinal position in the keyspace.
During rehashing, two tables coexist. The scan identifies the smaller table, visits the relevant bucket there, and visits corresponding expanded buckets in the larger table. Completing that set of related buckets is necessary because entries can occupy either table during the migration.
The shrinkage fix corrects how the cursor advances through those expansions. The old code incremented the bits outside the smaller mask and then performed a separate reversed-cursor increment after the rehashing branch. In the replacement, the reversed cursor advances inside the larger-table loop, using the larger mask. It also makes the non-rehashing branch perform its own increment.
These details explain why a scan can finish yet fail its completeness contract. Returning cursor zero only establishes that the algorithm reached its termination state. If its sequence skipped a related bucket during shrinkage, termination does not imply that it visited every required entry. The repair changes the traversal itself, so the caller does not need a new command or cursor format.
Completeness does not imply a snapshot
The dictionary comment explicitly allows duplicate returns. A caller must not infer from the completeness guarantee that each entry appears exactly once. Resizing-compatible iteration can revisit an entry, and processing code must account for that documented behavior.
A full scan also spans multiple calls while the dictionary can change. It should not be described as a frozen image of the dataset at one instant. The specific bug is that table shrinkage can make the iterator miss elements it should cover. Fixing that error preserves the existing incremental design, including its stated limitations.
I prefer this explanation to treating SCAN as a generic pagination facility. The cursor encodes progress through an internal bucket traversal. Its usefulness depends on precise guarantees about that traversal, while the application decides how to handle repeated results and concurrent changes.
The release notes say a regression test can trigger the issue frequently when it is present. That is an appropriate complement to the algorithm explanation. Resizing during iteration is the behavior under test, so a collection of scans against an unchanged small table would not exercise the important condition.
Replication needs the correct snapshot contents
The release also changes RDB saving for partial resynchronization. Its RDB patch removes the save-time decision to skip a key solely because its expiry time has passed. The code still writes the expiry metadata. The release notes explain that omitting those keys can make the snapshot an incorrect base for PSYNC2.
An expiration deadline and the replication state at a particular point are different facts. Constructing a base for subsequent replicated changes must preserve the state those changes expect. Filtering the saved contents independently can break that relationship, even if the filter appears reasonable for an ordinary application read.
Redis 4.0.10 therefore repairs several assumptions at the implementation level. Native Lua helpers must validate the memory operations they request. A cursor must visit the required bucket expansions during resize, and a replication snapshot must represent the appropriate base state. Each correction has a specific affected path that the source and patch make possible to inspect.