fix: address low/informational sec-parser follow-ups

- client_cli: capture errno before output_escape() in
  read_patterns_from_file() so an over-long line is still reported as
  EFBIG instead of the (possibly malloc-clobbered) errno.
- file_list: guard string_list_add() capacity doubling against
  overflow (capacity > INT_MAX / 2), matching filter_rule_list_add();
  callers already surface the false as a memory-allocation error.
- compression: ZSTD_isError() is true for ZSTD_CONTENTSIZE_UNKNOWN,
  which made the 3x unknown-size fallback dead code.  Test the
  CONTENTSIZE_ERROR/UNKNOWN sentinels explicitly so unknown-size frames
  reach the estimate path (still bounded by the existing hard limit)
  while invalid frames are rejected.  Known-size frames and the 100 MB
  ceiling/overflow checks are unchanged.
- tests: add an unknown-content-size-frame decompression test.

Tests: ./build/tests and ./build-asan/tests all pass (42/42);
clang-format + cppcheck clean.
This commit is contained in:
2026-09-14 17:11:02 +02:00
parent 10c4ffebdf
commit 34abaadb9a
4 changed files with 73 additions and 5 deletions
+5 -2
View File
@@ -2045,13 +2045,16 @@ static int read_patterns_from_file(const char* filepath, char*** patterns, int*
while (true) {
ssize_t n = utils_getdelim_bounded(fp, &line, &line_size, '\n', UTILS_MAX_LINE_LEN);
if (n < 0) {
/* output_escape() may allocate (and clobber errno): capture the reader's
* errno first so an over-long line is still reported as EFBIG. */
int saved_errno = errno;
char* escaped = output_escape(filepath, false);
if (errno == EFBIG) {
if (saved_errno == EFBIG) {
log_message(LOG_LEVEL_ERROR, "pattern file '%s' has a line exceeding %d bytes",
escaped ? escaped : "<allocation failed>", (int)UTILS_MAX_LINE_LEN);
} else {
log_message(LOG_LEVEL_ERROR, "could not read pattern file '%s': %s",
escaped ? escaped : "<allocation failed>", strerror(errno));
escaped ? escaped : "<allocation failed>", strerror(saved_errno));
}
free(escaped);
free(line);
+7 -3
View File
@@ -243,9 +243,13 @@ Data* data_decompress_limited(Data* compressed_data, size_t maximum_size) {
log_debug_message(LOG_DEBUG_UTIL, "Start to decompress data");
unsigned long long dst_size =
ZSTD_getFrameContentSize(compressed_data->data, compressed_data->size);
if (ZSTD_isError(dst_size)) {
log_message(LOG_LEVEL_ERROR, "Failed to get decompressed size: %s",
ZSTD_getErrorName(dst_size));
/* ZSTD_isError() is also true for ZSTD_CONTENTSIZE_ERROR and
* ZSTD_CONTENTSIZE_UNKNOWN (both are encoded near (size_t)-1), so test the
* sentinels explicitly instead of blanket-rejecting every error-ish value:
* only CONTENTSIZE_ERROR means an unreadable header, while CONTENTSIZE_UNKNOWN
* must reach the estimate fallback below. */
if (dst_size == ZSTD_CONTENTSIZE_ERROR) {
log_message(LOG_LEVEL_ERROR, "Failed to get decompressed size: invalid zstd frame");
return NULL;
}
+2
View File
@@ -23,6 +23,8 @@ static void string_list_destroy(StringList* list) {
static bool string_list_add(StringList* list, const char* text) {
if (list->count == list->capacity) {
if (list->capacity > INT_MAX / 2)
return false;
int new_cap = list->capacity > 0 ? list->capacity * 2 : 16;
char** grown = realloc(list->items, (size_t)new_cap * sizeof(char*));
if (!grown)