1
0
mirror of https://github.com/lz4/lz4.git synced 2026-10-24 01:48:57 +03:00

lz4hc: LZ4_saveDictHC() before any compression no longer corrupts the next block

On a new or reset stream (prefixStart == NULL), LZ4_saveDictHC() installed @safeBuffer as prefix,
which skipped the initialization done by the next compression (LZ4HC_init_internal()).
The stream then started at index 0, which the hash table uses for empty entries :
positions matched themselves (offset 0), and the block decoded into wrong data, at all HC levels,
also with an attached dictionary. Present in v1.10.0.
It now saves nothing, leaving the stream to be initialized by the next compression.
Found by lz4frame with uncompressed then compressed segments in linked blocks.
tests : fuzzer.c saveDictHC test #926 compresses after LZ4_saveDictHC(), without and with an attached dictionary.
This commit is contained in:
Yann Collet
2026-10-21 14:06:14 -07:00
parent 124e0d4da8
commit 35d74f85f7
3 changed files with 32 additions and 0 deletions

1
NEWS
View File

@@ -4,6 +4,7 @@ fix : `LZ4_compress_fast_continue()` produced wrong data when a block overwrote
fix : `LZ4_compress_destSize_extState()` corrupted data with `acceleration <= 0`, and could read out of bounds (since v1.10.0)
fix : negative sizes crashed `LZ4_decompress_safe_partial()` (since v1.9.3) and `LZ4_decompress_fast()` (since v1.9.4)
fix : `LZ4_saveDict()` with a negative `dictSize` copied up to 64 KB into `safeBuffer`, by @arthuz-th
fix : `LZ4_saveDictHC()` before any compression (new or reset stream) corrupted the next compressed block
fix : `LZ4_decompress_safe_partial()` could leave previous `dst` content in decoded data with an invalid offset 0 (since v1.8.3)
fix : heap overflow with an invalid `blockSizeID` in `LZ4F_compressBegin()` and `LZ4F_writeOpen()`, by @jaypatrickhoward
fix : `LZ4F_compressBegin()` failing on allocation left an inconsistent frame in progress : it is now invalidated

View File

@@ -1793,6 +1793,9 @@ int LZ4_saveDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictS
LZ4HC_CCtx_internal* const streamPtr = &LZ4_streamHCPtr->internal_donotuse;
int const prefixSize = (int)(streamPtr->end - streamPtr->prefixStart);
DEBUGLOG(5, "LZ4_saveDictHC(%p, %p, %d)", LZ4_streamHCPtr, safeBuffer, dictSize);
/* nothing compressed yet (new or reset stream) : nothing to save,
* the stream remains to be initialized by next compression (attached dictionary included) */
if (streamPtr->prefixStart == NULL) return 0;
assert(prefixSize >= 0);
if (dictSize > 64 KB) dictSize = 64 KB;
if (dictSize < 4) dictSize = 0;

View File

@@ -1747,6 +1747,34 @@ static void FUZ_unitTests(int compressionLevel)
{ char tmp_buffer[240] = { 0 };
FUZ_CHECKTEST( LZ4_saveDictHC(ctx, tmp_buffer, sizeof(tmp_buffer)) != 0,
"LZ4_saveDictHC() can't save anything since compression hasn't started");
}
/* next block is compressed as the first one of the stream,
* without (attach==0) or with (attach==1) an attached dictionary */
{ LZ4_streamHC_t* const dictHC = LZ4_createStreamHC();
int const levels[] = { LZ4HC_CLEVEL_DEFAULT, 3, LZ4HC_CLEVEL_OPT_MIN, LZ4HC_CLEVEL_MAX }; /* first : level of LZ4_initStreamHC() */
int attach, l;
FUZ_CHECKTEST(dictHC == NULL, "LZ4_createStreamHC() allocation failed");
for (l = 0; l < (int)(sizeof(levels) / sizeof(levels[0])); l++) {
for (attach = 0; attach <= 1; attach++) {
char saveBuffer[240];
int cSize, dSize;
if (l || attach) LZ4_resetStreamHC_fast(ctx, levels[l]); /* first test : right after init */
if (attach) {
LZ4_resetStreamHC_fast(dictHC, levels[l]);
FUZ_CHECKTEST(LZ4_loadDictHC(dictHC, testInput, 64 KB) != 64 KB, "LZ4_loadDictHC() failed");
LZ4_attach_HC_dictionary(ctx, dictHC);
}
FUZ_CHECKTEST( LZ4_saveDictHC(ctx, saveBuffer, sizeof(saveBuffer)) != 0,
"LZ4_saveDictHC() can't save anything since compression hasn't started");
cSize = LZ4_compress_HC_continue(ctx, testInput + 64 KB, testCompressed, 64 KB, testCompressedSize);
FUZ_CHECKTEST(cSize <= 0, "LZ4_compress_HC_continue() failed");
dSize = LZ4_decompress_safe_usingDict(testCompressed, testVerify, cSize, 64 KB,
testInput, attach ? 64 KB : 0);
FUZ_CHECKTEST(dSize != 64 KB || memcmp(testVerify, testInput + 64 KB, 64 KB),
"level %i, dictionary %i : block compressed after LZ4_saveDictHC() is corrupted", levels[l], attach);
} }
LZ4_freeStreamHC(dictHC);
} }
DISPLAYLEVEL(3, "OK \n");