提出 #895119: Valkey Project Valkey 9.1.0 Out-of-bounds Read情報

タイトルValkey Project Valkey 9.1.0 Out-of-bounds Read
説明I identified a server-side memory-safety vulnerability in Valkey 9.1.0 in the RDB loading path for atomic slot migration metadata. The issue is triggered when the official valkey-server starts in cluster mode and loads a crafted dump.rdb that contains an RDB_OPCODE_SLOT_IMPORT record with a malformed short job_name field. The vulnerable code path accepts a variable-length string object from the RDB stream and then consumes it as if it were a fixed-size 40-byte cluster migration job name. This mismatch results in a heap out-of-bounds read and causes the server process to crash during startup RDB loading. The bug is located in the implementation of slot migration import persistence and recovery. In Valkey 9.1.0, clusterRDBSaveSlotImports() serializes the import job name using rdbSaveRawString(..., job->name, CLUSTER_NAMELEN), i.e. it writes a 40-byte value because the internal migration job structure stores the field as a fixed-size name buffer. The code base defines CLUSTER_NAMELEN as 40 ("sha1 hex length"), and slotMigrationJob contains fixed-size arrays target_node_name[CLUSTER_NAMELEN], source_node_name[CLUSTER_NAMELEN], and name[CLUSTER_NAMELEN]. In other words, the slot migration subsystem clearly treats job names as fixed-width 40-byte values at the structure and serialization level. The inconsistency appears during deserialization. When the RDB parser encounters RDB_OPCODE_SLOT_IMPORT, rdbLoadRioWithLoadingCtx() dispatches to clusterRDBLoadSlotImport(). That loader calls rdbLoadStringObject(rdb) to obtain job_name. This API returns a regular Valkey string object and does not enforce that the returned payload length must equal CLUSTER_NAMELEN. After loading the string object and the slot range list, clusterRDBLoadSlotImport() directly calls createSlotImportJob(NULL, NULL, objectGetVal(job_name), slot_ranges). No validation is performed to ensure that the loaded string is actually 40 bytes long. The sink is createSlotImportJob(). That function allocates a slotMigrationJob and then unconditionally executes memcpy(job->name, name, CLUSTER_NAMELEN). Because the destination is a fixed 40-byte array, the copy length is always 40. If the source string loaded from the RDB is shorter than 40 bytes, memcpy reads past the end of the source heap object. This is not a write overflow into the destination. The destination buffer is the expected size. The actual problem is that the source buffer is attacker-controlled and too short, so the fixed-length memcpy becomes a heap out-of-bounds read. I reproduced the issue on the official src/valkey-server binary built from Valkey 9.1.0 with AddressSanitizer enabled. My build procedure used the upstream build system with "make distclean" followed by "make -j$(nproc) SANITIZER=address OPTIMIZATION=-O0". I then generated a clean baseline RDB using the official server itself, saved it, and used that file as a template. Instead of creating an arbitrary hand-written RDB from scratch, I took the valid server-generated empty.rdb, removed the final EOF/checksum trailer, appended a single crafted RDB_OPCODE_SLOT_IMPORT record, and recomputed the trailing CRC64 checksum using a small helper linked against Valkey's own CRC64 implementation. This minimizes ambiguity and shows that the crash is caused by the malformed slot import record rather than by unrelated file corruption. The malicious record I used contains a one-byte job_name ("A"), num_slot_ranges = 1, and a single slot range 0-0. The target server is then started with cluster-enabled yes and dbfilename dump.rdb pointing to the crafted file. On startup, the official server attempts to load the slot import metadata from disk and crashes before normal service initialization completes. The sanitizer evidence is consistent and strong. The crash report shows "AddressSanitizer: heap-buffer-overflow" with "READ of size 40" in createSlotImportJob(), called from clusterRDBLoadSlotImport(), called from rdbLoadRioWithLoadingCtx(), then rdbLoad(), loadDataFromDisk(), and main(). The report also shows that the source object was allocated by createEmbeddedStringObjectWithKeyAndExpire() through createEmbeddedStringObject(), tryCreateStringObject(), rdbGenericLoadStringObject(), and rdbLoadStringObject(). This allocation trace matters because it proves that the source name came directly from the RDB loader as a normal short string object, not from an unrelated stale pointer or a module-specific data path. I additionally confirmed the root cause in GDB by breaking at the loader and sink sites. Immediately after rdbLoadStringObject() returned in clusterRDBLoadSlotImport(), the loaded job_name object had a payload length of 1 according to sdslen(objectGetVal(job_name)). The visible bytes started with 0x41 ('A'), and no evidence existed that the object contained a valid 40-byte job identifier. At the call site to createSlotImportJob(), the argument remained the same 1-byte string. Inside createSlotImportJob(), just before memcpy(job->name, name, CLUSTER_NAMELEN), the pointer "name" still referred to the same short payload and sdslen(name) still evaluated to 1. The vulnerable condition is therefore directly observable: source length 1, copy size 40. This issue is a contract mismatch between validation and consumption. The implementation uses a fixed-width job name invariant in the migration subsystem, but the RDB recovery path restores that field using a generic variable-length string loader and never re-establishes the 40-byte invariant before passing control to code that assumes it. The problem is therefore deeper than a mere malformed input parse failure. It is a core memory-safety flaw in the official server's startup loading path. I did not rely on any third-party modules, private harnesses, or direct calls into internal helper functions. The victim process is the stock official valkey-server. The trigger point is the normal startup RDB loading logic. The issue is therefore relevant to real deployments that may load an attacker-controlled or otherwise untrusted RDB in cluster mode. Based on my testing, the demonstrated security impact is a reliable denial of service due to server process termination during startup. My current evidence supports classification as a heap out-of-bounds read. I have not claimed arbitrary code execution, and I have not claimed a verified information disclosure primitive beyond the memory-safety violation itself. The affected version I analyzed is Valkey 9.1.0. The release tag resolves to commit c9e8005e9d0ec817e26c7db318861cb821409249. The vulnerable logic is present in the 9.1.0 source tree in src/cluster_migrateslots.c and is reachable from src/rdb.c when processing RDB_OPCODE_SLOT_IMPORT. The fixed-length assumption is also reflected by CLUSTER_NAMELEN in src/cluster.h. A minimal and appropriate fix is to validate the loaded name length before calling createSlotImportJob(). In practice, clusterRDBLoadSlotImport() should reject any job_name whose sdslen(objectGetVal(job_name)) is not exactly CLUSTER_NAMELEN and treat the RDB as malformed. Another defensively useful hardening measure would be to make createSlotImportJob() itself validate the incoming name length before performing the memcpy, so that the invariant is enforced both at the deserialization boundary and at the structure-construction boundary. However, the deserialization fix is the most direct repair because the bug originates from accepting an arbitrary-length RDB string where the subsystem expects a fixed 40-byte job identifier. From a security reporting perspective, I consider this a valid vulnerability rather than a mere correctness bug because an attacker-controlled persisted input can trigger a real memory-safety violation in the official server process and cause a reliable crash. The practical impact demonstrated so far is denial of service. The vulnerability can be described as a server-side heap out-of-bounds read in Valkey's RDB slot import loader caused by missing length validation for the slot migration job name.
ソース⚠️ https://github.com/valkey-io/valkey/issues/4207
ユーザー
 VULL (UID 98817)
送信2026年07月18日 19:54 (2 月 ago)
モデレーション2026年09月04日 09:57 (2 months later)
ステータス承諾済み
VulDBエントリ398707 [valkey-io valkey 迄 9.5.4/9.1.0 Slot Migration cluster_migrateslots.c createSlotImportJob job_name 情報漏えい]
ポイント20

Do you know our Splunk app?

Download it now for free!