CVE-2026-109923 CWE-197 / CWE-681 Severidad media

LLP64 bulk-length truncation in hiredis processBulkItem → heap out-of-bounds read / DoS

Integer truncation of the bulk-string length on LLP64 targets (Windows x64) in the hiredis RESP reader leads to a heap out-of-bounds read / denial of service (incomplete fix of the 2018 bulk-length-truncation guard, commit 1091975; related to CVE-2021-32765)

Original release date
Agosto 2026
Descubierto por
Daniel Fernández Bleda
Severidad
6.5 / 10 CVSS v3.1
Producto afectado
hiredis (redis/hiredis)
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Background

hiredis is the official minimal C client for Redis, embedded in a very large number of C/C++ applications. Its reply reader (read.c) is a stand-alone RESP (REdis Serialization Protocol) parser: bytes arriving from a Redis server are handed to redisReaderFeed() and turned into redisReply objects by redisReaderGetReply(). The higher-level redisCommand() / redisGetReply() drive exactly this parser over the socket, so every byte the server sends is parsed by this code.

RESP encodes a bulk string as a length line followed by the payload:

$<len>\r\n<len bytes of payload>\r\n

<len> is attacker-controlled decimal digits: the client trusts the server to state the payload size. Commit 1091975 (2018-05-20, "Fix bulk and multi-bulk length truncation") added a range check to processBulkItem precisely to stop a malicious <len> from truncating and under-sizing the copy — its commit message states "processBulkItem had the same truncation issue … createString takes size_t, so bound to SIZE_MAX." The later CVE-2021-32765 fix (commit 76a7b1000, 2021, in hiredis 1.0.1) hardened the sibling multi-bulk count path but did not touch this bulk-string guard. This advisory shows that the 2018 bulk-string guard is incomplete on LLP64 targets: it silently fails to bound <len> and a subsequent 32-bit truncation re-opens the very truncation bug it was meant to fix.

Description

processBulkItem() stores the running item size in a variable of type unsigned long. On the LP64 platforms the fix was validated against, unsigned long is 64-bit and matches size_t, so the guard and the size arithmetic behave as intended. On Windows (both x64 LLP64 and 32-bit ILP32), unsigned long is 32-bit while size_t is 64-bit (x64) — two independent consequences follow:

  1. The range check added by that 2018 fix (commit 1091975), len > (long long)SIZE_MAX, is written behind LLONG_MAX > SIZE_MAX, which is false on every 64-bit target, so that clause is dead code and len is only floored at -1. An attacker may declare len up to LLONG_MAX.

  2. The "is the whole item present in the buffer yet?" size, bytelen, is computed in 64-bit but stored back into the 32-bit unsigned long, truncating modulo 2³². By choosing len ≈ 2³² the attacker makes the truncated bytelen tiny, so the completeness check passes with only the length line in the buffer — while the untruncated 64-bit len is then passed to createStringObject(), which allocates and memcpys that full length from a source buffer that holds two bytes. The copy runs off the end of the reader buffer → heap out-of-bounds read → the process crashes.

Technical details

Affected code

read.c, processBulkItem() (HEAD 1.5.0-dev, lines as shown; the same code is at read.c:402-444 in release 1.3.0):

static int processBulkItem(redisReader *r) {
    ...
    long long len;                 /* L459  — 64-bit */
    unsigned long bytelen;         /* L460  — 32-bit on LLP64/ILP32 (Windows) */
    ...
    if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { ...bad length... }
    if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {   /* L475 */
        ...out of range...         /* second clause is DEAD on any 64-bit box */
    }
    ...
    } else {
        bytelen += len+2;                       /* L490 — 64-bit math, TRUNCATED into 32-bit bytelen */
        if (r->pos+bytelen <= r->len) {         /* L491 — completeness check uses the truncated value */
            ...
            obj = r->fn->createString(cur,s+2,len);   /* L501 — copies the FULL 64-bit len */
            ...

The oversized copy itself, createStringObject() in hiredis.c (≈ lines 151-154):

buf = hi_malloc(len+1);            /* len ≈ 2^32 → ~4 GB allocation */
...
memcpy(buf,str,len);               /* reads ~4 GB from a 2-byte source → OOB read → crash */

The RESP3 verbatim string type (=) reaches the same processBulkItem path and the createStringObject VERB branch (hiredis.c ≈ 142-147), so it is affected identically.

Root cause

The 2018 truncation fix (commit 1091975) implicitly assumed sizeof(unsigned long) == sizeof(size_t). That holds on LP64 but not on Windows, where unsigned long is 32-bit. Two defects compound: a range check that is dead on 64-bit (LLONG_MAX > SIZE_MAX), and a size accumulator (bytelen) narrower than the value it must hold.

Attacker control

The full RESP byte stream, including the bulk-string length line $<len>\r\n (and the RESP3 verbatim =<len>\r\n). The attacker chooses len so that (header + len + 2) mod 2³² is small. The minimum viable len is ≈ 2³² (about 4 GB); the payload bytes need not be present.

Reachability

The parser is fed by redisReaderGetReply(), which redisCommand() / redisGetReply() invoke on every reply. Any hiredis consumer that connects to a Redis endpoint and issues commands returning bulk strings (GET, HGET, HGETALL, LRANGE, SCAN values, …) reaches processBulkItem on attacker-controlled bytes if:

  • the Redis server is malicious or has been compromised, or
  • an attacker is on-path on the (by default cleartext) client↔server connection (MITM).

Either condition lets the server-side bytes be fully chosen by the attacker.

Proof of concept

The crafted reply (15 bytes)

24 34 32 39 34 39 36 37 32 39 36 0d 0a 58 58      $4294967296\r\nXX

$ + the ASCII digits 4294967296 (= 2³²) + \r\n + two filler bytes XX.

Walk-through on Windows x64 (LLP64):

  1. len = 4294967296. The L475 guard passes (dead second clause; len only floored at -1).
  2. bytelen = 12 (10 digit bytes + \r\n), then bytelen += len + 2 = 4294967310; stored into the 32-bit unsigned long this becomes 14.
  3. r->pos(1) + 14 = 15 ≤ r->len(15) → the completeness check passes with the whole 15-byte buffer.
  4. createStringObject(cur, s+2, 4294967296) → hi_malloc(~4 GB) succeeds, then memcpy(dst, s+2, ~4 GB) reads off the end of the 15-byte reader buffer → access violation.

On LP64, step 2 keeps bytelen = 4294967310 (64-bit, no truncation), so r->pos + bytelen (4294967311) ≤ r->len (15) is false: the reader simply waits for more data. No crash. This divergence is the confirmation.

Harness (essential lines)

A byte-for-byte stand-in for how redisGetReply() consumes the socket — feed the bytes to the reader in small chunks and pull replies:

redisReader *r = redisReaderCreate();
/* feed the file in 64-byte chunks, exactly like socket reads */
redisReaderFeed(r, chunk, n);
void *reply;
while (redisReaderGetReply(r, &reply) == REDIS_OK && reply) { freeReplyObject(reply); }

Results

Windows x64, built with MSVC 14.38 (cl /Od /Zi, LLP64), hiredis HEAD 574c1586:

> hr_harness_win.exe good.resp        (control: $16\r\n4111111111111111\r\n)
entrada=23 bytes
replies=1 rc=0                         → clean
> hr_harness_win.exe poc.resp         ($4294967296\r\nXX, 15 bytes)
entrada=15 bytes
                                       → process exit code -1073741819 = 0xC0000005
                                         (ACCESS VIOLATION; the "replies=" line is never reached)

Linux x64, built with GCC (LP64), same sources, same 15-byte input — cross-platform control:

$ ./hr_harness poc.resp
replies=0 rc=0                         → no crash; reader waits for more data
$ valgrind -q ./hr_harness poc.resp
(no errors)                            → clean; the defect is absent on LP64

The control (a well-formed bulk string) parses cleanly on both platforms; only the crafted length crashes, and only on the LLP64 build.

Risk rating (CVSS 3.1)

6.5 (Medium) — AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H. The base with UI:N is 7.5 (High).

Network vector: the malicious bytes originate from the Redis server (or an on-path attacker). Attack complexity is Low — a single short reply. Availability is High: the process crashes. Confidentiality is scored None: although this is technically an out-of-bounds read, the copy faults before the data is usable by the caller, so it is a crash, not a practical information leak.

Three honest qualifications place the practical severity below a bare network High:

  • Platform-gated. Only LLP64/ILP32 (Windows) builds are affected. LP64 builds are not.
  • Feature-gated. The consumer must actually connect to the attacker-influenced Redis endpoint (in the affected product, an opt-in remote-database scan); a build that never talks to an untrusted Redis is not exposed. This is why we score UI:R.
  • Allocation-gated. The crash requires the ~4 GB destination allocation to succeed (then the read faults). If it fails, the result is a clean out-of-memory error, not a crash. On hosts with ample memory/commit — the usual case for the workstations that run such scans — the allocation succeeds and the crash is reliable; we reproduced it directly.

Business impact

An application that uses hiredis to read from a Redis endpoint it does not fully control — a compromised server, a server reachable across a trust boundary, or any deployment where the client↔server link can be intercepted — can be crashed by a single crafted bulk reply when built for Windows. In a per-connection or per-request worker model, one crafted reply aborts the worker, denying service. Because the trigger is in the reply reader, ordinary application-level error handling around the command call does not prevent the process from terminating.

Systems affected

  • hiredis HEAD 1.5.0-dev (commit 574c1586) and release 1.3.0, on Windows x64 (LLP64) and Windows 32-bit (ILP32), confirmed by source analysis and Windows runtime reproduction.
  • All releases containing the 2018 guard (commit 1091975, present since well before 1.0.1) with the same unsigned long bytelen and dead LLONG_MAX > SIZE_MAX clause are expected to be affected on LLP64/ILP32; we did not audit a range we did not read.
  • LP64 targets (Linux, macOS, FreeBSD, Solaris, AIX) are not affected.

Vendor position

The report was handled by INCIBE-CERT, which coordinated with the vendor, and was reviewed by Redis Product Security.

  • 2026-10-09 — Redis Product Security acknowledged the report, confirmed it would be studied, and rated it Medium (CVSS v4.0 6.9) — the same score Internet Security Auditors submitted.
  • 2026-10-15 — hiredis development team determined that Windows is not an officially supported platform and that the issue, being limited in scope to Windows, will not receive a code fix. The project's security policy states that hiredis officially supports Unix-like systems (Linux, macOS, BSD) only, that Windows is not an officially supported platform, and that there are known incompatibilities on Windows "including security vulnerabilities" (https://github.com/redis/hiredis/blob/master/SECURITY.md).

Consequence. No upstream fix will be issued for the affected platforms. The defect is nonetheless real on LLP64/ILP32 (Windows) builds, as reproduced. The remediation below is therefore provided by Internet Security Auditors for consumers who ship hiredis on Windows; it has not been accepted or validated by the hiredis maintainers, and anyone applying it should review and test it against their own build.

Related advisories: Further findings from the same third-party review programme concern unrelated projects and are disclosed separately to their respective maintainers.

Solution

Note. The following is Internet Security Auditors' proposed remediation. It has not been accepted or validated by the hiredis project, which has declined to fix the issue (see Vendor position). Consumers who build hiredis for Windows can apply it to a local/patched build.

Two complementary corrections:

1. Use a size_t accumulator so the completeness size cannot truncate:

size_t bytelen;                 /* was: unsigned long */

2. Make the range check effective on all targets by clamping len against SIZE_MAX unconditionally (the LLONG_MAX > SIZE_MAX gate is what disables it on 64-bit):

if (len < -1 || (unsigned long long)len > (unsigned long long)SIZE_MAX) {
    __redisReaderSetError(r, REDIS_ERR_PROTOCOL, "Bulk string length out of range");
    return REDIS_ERR;
}

With bytelen widened to size_t, bytelen += len + 2 no longer truncates and the completeness check r->pos + bytelen <= r->len correctly rejects an under-length buffer, as it already does on LP64.

Because no upstream fix will be issued, Windows consumers should carry the above as a local patch. Defence in depth that does not require patching hiredis: build the affected component only for LP64 targets where possible; use TLS/authenticated connections to Redis so that a network attacker cannot inject replies; and treat remote-database scanning of untrusted endpoints as a privileged operation.

Credits

This vulnerability was discovered and reported by Daniel Fernández Bleda (Internet Security Auditors).

Revision history

1.0 · 2026-08-08 Initial release: source analysis plus Windows-x64 runtime reproduction (access violation) and LP64 cross-platform control.
1.1 · 2026-08-09 Attribution corrected: the dead processBulkItem guard was introduced by commit 1091975 (2018, "Fix bulk and multi-bulk length truncation"), not by the CVE-2021-32765 fix (76a7b1000, 2021, which hardened the sibling multi-bulk path). Framing and references updated accordingly.
1.2 · 2026-08-20 Prepared for submission: internal notes removed, cross-references limited to advisories the recipient also receives, and the INCIBE-CERT submission recorded in the disclosure timeline.
1.3 · 2026-08-29 Submission date for the batch set to 2026-08-29.
1.4 · 2026-09-15 Transcribed onto the INCIBE-CERT vulnerability reporting form at their request. No finding, no evidence and no technical claim was changed. A CVSS v4.0 score and vector were added because the form requires them; the CVSS 3.1 assessment is unchanged and retained.
2.0 · 2026-11-02 Published. CVE-2026-109923 assigned by INCIBE-CERT (2026-10-13). Redis Product Security reviewed the report and rated it Medium (CVSS v4.0 6.9); the hiredis development team declined a code fix because Windows is not an officially supported platform. A Vendor position section was added and the proposed remediation is retained but explicitly marked as not accepted or validated by the project. No technical finding was changed.

Disclosure timeline

2026-08-08 Identified during source review of hiredis HEAD; recognised as an LLP64-specific incomplete fix of the 2018 bulk-length-truncation guard (commit 1091975); related to CVE-2021-32765. Reproduced on Windows x64 (MSVC) — 15-byte reply → 0xC0000005; confirmed absent on LP64 Linux (control + Valgrind). Confirmed the same code is present in release 1.3.0.
2026-08-29 Submitted to INCIBE-CERT (Spanish national CERT, CVE Numbering Authority) requesting CVE assignment and coordinated disclosure.
2026-09-15 INCIBE-CERT replied that the submission must be made on their own vulnerability reporting form. The full content was transcribed onto that form without alteration of any finding, and a CVSS v4.0 score was added as the form requires.
2026-10-09 Redis Product Security acknowledged the report, confirmed it would be studied, and rated it Medium (CVSS v4.0 6.9).
2026-10-13 INCIBE-CERT assigns CVE-2026-109923.
2026-10-15 The hiredis development team determined that Windows is not an officially supported platform and that the issue will not receive a code fix, as it is limited in scope to Windows.
2026-11-02 Advisory published, with the vendor's position recorded. No upstream fix will be issued; the remediation is Internet Security Auditors' and is not vendor-validated.

References

  • CVE-2026-109923
  • hiredis commit 1091975 "Fix bulk and multi-bulk length truncation" (2018-05-20) — introduced the processBulkItem guard this advisory shows is dead on LLP64
  • CVE-2021-32765 / hiredis commit 76a7b1000 (2021, in 1.0.1) — related fix hardening the sibling multi-bulk count path (not the bulk-string guard)
About Internet Security Auditors

Compañía con sede en España y Colombia, especializada en auditoría de aplicaciones web, seguridad de red, test de intrusión e implantación y evaluación de cumplimiento. Nuestros clientes incluyen algunas de las mayores compañías de finanzas, telecomunicaciones, seguros y TIC. Proveedor independiente de fabricante, con experiencia profunda desde 2001. Nuestro esfuerzo en I+D incluye investigación de vulnerabilidades, colaboración en proyectos abiertos de seguridad, whitepapers y participación en eventos del sector.

¿Quieres que revisemos tu producto?

Contacta con nosotros
Contacto

¿Necesitas una auditoría de seguridad?

Cuéntanos tu situación y nuestro equipo se pondrá en contacto contigo para orientarte o agendar una llamada técnica.

Solicitar información

Nos pondremos en contacto contigo lo antes posible.
Presencia Global

Nuestras oficinas

Barcelona

C. Santander, 101. Edificio A-2
E-08030 Barcelona (España)

+34 93 305 13 18
Madrid

C. Arequipa, 1
E-28043 Madrid (España)

+34 91 763 40 47
Bogotá

Transversal 22 # 98-82, Ed. Porta 100 Of. 1003
110221 Bogotá (Colombia)

+57 601 638 68 88
25
años de experiencia en ciberseguridad
Contacta con nosotros