Release v2.26.0 #284
@@ -324,6 +324,20 @@ Data* data_decompress_limited(Data* compressed_data, size_t maximum_size) {
|
||||
uncompressed_data->data = new_data;
|
||||
output.dst = new_data;
|
||||
output.size = buf_size;
|
||||
/* Re-attempt with the larger output buffer; the truncated-frame check
|
||||
* below must not reject a complete frame that merely filled the previous
|
||||
* buffer exactly. */
|
||||
continue;
|
||||
}
|
||||
/* A positive hint with all input consumed means the frame is incomplete: a
|
||||
* truncated stream would otherwise spin here forever (ZSTD_decompressStream
|
||||
* keeps returning the same hint). Fail instead of burning CPU. */
|
||||
if (ret != 0 && input.pos == input.size) {
|
||||
log_message(LOG_LEVEL_ERROR,
|
||||
"Truncated zstd frame: input exhausted with %zu bytes still expected", ret);
|
||||
data_destroy(uncompressed_data);
|
||||
uncompressed_data = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
} while (ret > 0);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "utils.h"
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -211,9 +212,47 @@ static void test_data_compress_reused_contexts_multithreaded() {
|
||||
compression_free_thread_contexts();
|
||||
}
|
||||
|
||||
/* A truncated zstd frame used to make the decompressor spin forever: the
|
||||
* stream call keeps returning a positive hint with all input consumed. Run the
|
||||
* decompression in a child with an alarm so a regression (infinite loop) is
|
||||
* caught as a timeout failure instead of hanging the whole unit suite. */
|
||||
static void test_data_decompress_truncated_frame_fails() {
|
||||
const char* original =
|
||||
"The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.";
|
||||
size_t len = strlen(original);
|
||||
char* buf = malloc(len);
|
||||
EXPECT_NOT_NULL(buf);
|
||||
memcpy(buf, original, len);
|
||||
Data* input = data_create(buf, len);
|
||||
EXPECT_NOT_NULL(input);
|
||||
|
||||
pid_t pid = fork();
|
||||
EXPECT_TRUE(pid >= 0);
|
||||
if (pid == 0) {
|
||||
alarm(10); /* kills the child if the decompressor hangs */
|
||||
Data* compressed = data_compress(input, 3);
|
||||
if (compressed && compressed->size > 1) {
|
||||
compressed->size -= 1; /* drop the final byte: frame is now incomplete */
|
||||
Data* out = data_decompress(compressed);
|
||||
bool failed_cleanly = (out == NULL);
|
||||
data_destroy(out);
|
||||
data_destroy(compressed);
|
||||
_exit(failed_cleanly ? 0 : 1);
|
||||
}
|
||||
data_destroy(compressed);
|
||||
_exit(2);
|
||||
}
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
|
||||
data_destroy(input);
|
||||
}
|
||||
|
||||
void test_compression() {
|
||||
test_data_compress_decompress_roundtrip();
|
||||
test_data_compress_decompress_large();
|
||||
test_data_decompress_truncated_frame_fails();
|
||||
test_skip_compress_suffix_matching();
|
||||
test_data_compress_with_threads_roundtrip();
|
||||
test_data_compress_reused_contexts_multithreaded();
|
||||
|
||||
Reference in New Issue
Block a user