From 7929c2defe0e645e008852bff40999cee9a3c42c Mon Sep 17 00:00:00 2001 From: Filippo Tedeschi Date: Wed, 23 Sep 2026 20:29:12 +0200 Subject: [PATCH] decompress: fix memory leak and size_t overflow in DDictHashSet_expand Two related defects in ZSTD_DDictHashSet_expand(): 1. Memory leak on rehash failure (CWE-401) FORWARD_IF_ERROR on emplaceDDict() returned early without freeing oldTable. The old pointer is stashed in a local variable, the hashSet already points to the new allocation, so oldTable leaks permanently. Fix: call ZSTD_customFree(oldTable) before returning the error, mirroring the cleanup on the success path. 2. Integer overflow guard on newTableSize (CWE-190) newTableSize = ddictPtrTableSize * DDICT_HASHSET_RESIZE_FACTOR has no overflow check. On a 32-bit size_t (or a pathologically large hashset) the multiplication wraps to a value <= oldTableSize, and the subsequent calloc() would allocate a too-small buffer. Fix: RETURN_ERROR_IF(newTableSize <= oldTableSize) before the allocation so the expand is rejected cleanly instead of producing a heap buffer overflow. --- lib/decompress/zstd_decompress.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 325f0b54dfc..12716058e85 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -130,11 +130,15 @@ static size_t ZSTD_DDictHashSet_emplaceDDict(ZSTD_DDictHashSet* hashSet, const Z */ static size_t ZSTD_DDictHashSet_expand(ZSTD_DDictHashSet* hashSet, ZSTD_customMem customMem) { size_t newTableSize = hashSet->ddictPtrTableSize * DDICT_HASHSET_RESIZE_FACTOR; - const ZSTD_DDict** newTable = (const ZSTD_DDict**)ZSTD_customCalloc(sizeof(ZSTD_DDict*) * newTableSize, customMem); + const ZSTD_DDict** newTable; const ZSTD_DDict** oldTable = hashSet->ddictPtrTable; size_t oldTableSize = hashSet->ddictPtrTableSize; size_t i; + /* Guard against integer overflow: newTableSize must be strictly larger than the old size */ + RETURN_ERROR_IF(newTableSize <= oldTableSize, memory_allocation, "DDict hash set size overflow on expand!"); + newTable = (const ZSTD_DDict**)ZSTD_customCalloc(sizeof(ZSTD_DDict*) * newTableSize, customMem); + DEBUGLOG(4, "Expanding DDict hash table! Old size: %zu new size: %zu", oldTableSize, newTableSize); RETURN_ERROR_IF(!newTable, memory_allocation, "Expanded hashset allocation failed!"); hashSet->ddictPtrTable = newTable; @@ -142,7 +146,14 @@ static size_t ZSTD_DDictHashSet_expand(ZSTD_DDictHashSet* hashSet, ZSTD_customMe hashSet->ddictPtrCount = 0; for (i = 0; i < oldTableSize; ++i) { if (oldTable[i] != NULL) { - FORWARD_IF_ERROR(ZSTD_DDictHashSet_emplaceDDict(hashSet, oldTable[i]), ""); + size_t const err = ZSTD_DDictHashSet_emplaceDDict(hashSet, oldTable[i]); + if (ZSTD_isError(err)) { + /* Free the old table before propagating the error to avoid a memory leak. + * The new table is already referenced by hashSet->ddictPtrTable and will + * be freed when the hashSet itself is freed. */ + ZSTD_customFree((void*)oldTable, customMem); + return err; + } } } ZSTD_customFree((void*)oldTable, customMem);