Release v2.26.0 #284

Merged
TapTap merged 210 commits from dev into main 2026-09-18 19:05:52 +02:00
2 changed files with 53 additions and 0 deletions
Showing only changes of commit e48f19ee2b - Show all commits
+14
View File
@@ -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);
+39
View File
@@ -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();