Fix allocation size overflow, zero-size OOM and debug category parsing

This commit is contained in:
evgeny committed 2026-10-21 17:07:11 +02:00
1 parent 00819654cf
commit 7e39f16e0f
6 files changed
+217 -73

No files matched your search

+11 -18
View File
@@ -460,7 +460,7 @@ void debug_set_log_hook(debug_log_hook_fn hook) {
g_log_hook = hook;
}
/* Parse debug configuration from string (e.g., "ll_queue:debug,uasync:info,memory:warn,etcp:error") */
/* Настройка отдельных категорий: "sys=debug,etcp=error"; разделитель ':' также допустим. */
int debug_parse_config(const char* config_string) {
if (!config_string || !*config_string) {
return 0;
@@ -468,12 +468,10 @@ int debug_parse_config(const char* config_string) {
char* str = u_strdup(config_string);
if (!str) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to allocate debug configuration copy");
return -1;
}
debug_category_t categories = 0;
debug_level_t level = DEBUG_LEVEL_NONE;
char* token = strtok(str, ",");
while (token) {
/* Trim leading spaces */
@@ -481,15 +479,16 @@ int debug_parse_config(const char* config_string) {
token++;
}
char* colon = strchr(token, ':');
if (!colon) {
char* separator = strpbrk(token, "=:");
if (!separator) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Missing '=' or ':' in debug setting '%s'", token);
u_free(str);
return -1;
}
*colon = '\0';
*separator = '\0';
char* category_name = token;
char* level_name = colon + 1;
char* level_name = separator + 1;
/* Trim leading spaces in level_name */
while (isspace(*level_name)) {
@@ -497,22 +496,16 @@ int debug_parse_config(const char* config_string) {
}
debug_category_t cat = get_category_by_name(category_name);
if (cat == DEBUG_CATEGORY_NONE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name);
if (cat <= DEBUG_CATEGORY_NONE || cat >= DEBUG_CATEGORY_COUNT) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown or unsupported debug category '%s' (expected an individual category)", category_name);
u_free(str);
return -1;
}
debug_level_t lev = debug_level_from_name(level_name);
/* Set per-category level */
int idx = 0;
while (!(cat & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) {
idx++;
}
if (idx < DEBUG_CATEGORY_COUNT) {
g_debug_config.category_levels[idx] = lev;
}
debug_set_category_level(cat, lev);
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Applied debug setting: %s=%s (category=%d, level=%d)", category_name, level_name, cat, lev);
token = strtok(NULL, ",");
}
+3 -1
View File
@@ -173,7 +173,9 @@ void debug_output(debug_level_t level, debug_category_t category_idx,
typedef void (*debug_log_hook_fn)(int level, const char* category, const char* message);
void debug_set_log_hook(debug_log_hook_fn hook);
/* Parse debug configuration from string (e.g. "etcp=trace,config=info") */
/* Настройка отдельных категорий: "etcp=trace,config=info" (также ':' вместо '=').
* NULL/пустая строка ничего не меняет. Возвращает 0 при успехе, -1 при ошибке;
* настройки до ошибочного элемента остаются применёнными. */
int debug_parse_config(const char* config_string);
/* Convenience macros for debug output */
+15 -6
View File
@@ -13,6 +13,7 @@
#define CANARY 0xDEADBEEF
#define PADDING_FILL 0xAA
#define METADATA_SIZE 128
#define ALLOCATION_OVERHEAD (12 + (size_t)2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE)
#define POINTER_SIZE sizeof(void*)
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
#define PREV_OFFSET LOCATION_MAX
@@ -89,7 +90,7 @@ void u_check(void* ptr, const char* text, const char* location) {
uint8_t* prefix_start = base + METADATA_SIZE;
uint32_t* pre_canary = (uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE);
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4);
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
size_t total = (size_t)size + ALLOCATION_OVERHEAD;
uint8_t* user = (uint8_t*)ptr;
bool error = false;
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
@@ -117,7 +118,7 @@ void u_check(void* ptr, const char* text, const char* location) {
}
if (error) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Allocated at: %s", location, text, alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes", location, text, size, total);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Memory block size (user): %u bytes, total allocated: %zu bytes", location, text, size, total);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s: %s: Full memory dump at base %p:", location, text, base);
hex_dump(base, total);
exit(EXIT_FAILURE);
@@ -125,13 +126,21 @@ void u_check(void* ptr, const char* text, const char* location) {
}
void* u_malloc_impl(uint32_t size, const char* location) {
size_t total = size;
if (total > SIZE_MAX - ALLOCATION_OVERHEAD) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "MALLOC OVERFLOW: %u user bytes + %zu overhead bytes at %s", size, ALLOCATION_OVERHEAD, location);
return NULL;
}
if (size >= LARGE_ALLOC_LOG_THRESHOLD) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "LARGE ALLOC: %u bytes (%.1f MB) at %s",
size, (double)size / (1024.0 * 1024.0), location);
}
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
total += ALLOCATION_OVERHEAD;
uint8_t* base = (uint8_t*)malloc(total);
if (!base && size > 0) return NULL;
if (!base) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "MALLOC FAILED: %u user bytes, %zu total bytes at %s", size, total, location);
return NULL;
}
memset(base, PADDING_FILL, total);
// Set metadata
size_t loc_len = strlen(location);
@@ -209,7 +218,7 @@ void u_free_impl(void* ptr, const char* location) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Base: %p", (void*)base);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Allocated at: %s", alloc_loc);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "User size: %u bytes", size);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Total allocated: %zu bytes", (size_t)size + ALLOCATION_OVERHEAD);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Active allocs: %zu", active_count);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8));
hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8);
@@ -231,7 +240,7 @@ void u_free_impl(void* ptr, const char* location) {
allocated_count--;
frees_count++;
UNLOCK();
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
size_t total = (size_t)size + ALLOCATION_OVERHEAD;
memset(base, 0, total);
free(base);
}
+5
View File
@@ -96,6 +96,7 @@ check_PROGRAMS = \
if OS_LINUX
check_PROGRAMS += \
test_mem \
test_stun_nat \
test_reality_fallback \
test_etcp_padding \
@@ -290,6 +291,10 @@ test_pool_corruption_log_SOURCES = test_pool_corruption_log.c
test_pool_corruption_log_LDADD = $(COMMON_LIBS)
# Linux/ELF: faults внедряются линкером, тестовых веток в библиотеках нет.
test_mem_SOURCES = test_mem.c
test_mem_LDADD = $(COMMON_LIBS)
test_mem_LDFLAGS = -Wl,--wrap=malloc
async_regression_sources = async_regression.c async_regression.h
async_regression_ldflags = -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl -Wl,--wrap=u_realloc_impl \
-Wl,--wrap=gettimeofday -Wl,--wrap=epoll_wait -Wl,--wrap=epoll_ctl -Wl,--wrap=eventfd -Wl,--wrap=epoll_create1
+130
View File
@@ -0,0 +1,130 @@
/* Проверяем размеры реальных malloc и OOM без выделения гигабайт памяти. */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "debug_config.h"
#include "mem.h"
#define CHECK(expr) do { \
if (!(expr)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #expr); \
exit(EXIT_FAILURE); \
} \
} while (0)
static bool fail_malloc;
static size_t malloc_calls;
static size_t malloc_size;
void* __real_malloc(size_t size);
void* __wrap_malloc(size_t size) {
malloc_calls++;
malloc_size = size;
if (fail_malloc) {
fail_malloc = false;
return NULL;
}
return __real_malloc(size);
}
static void test_small_allocations(void) {
const uint32_t sizes[] = {0, 1, 3, 4, 255, 256, 257, 1024};
size_t count = u_get_allocated_count();
for (size_t i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
uint8_t* ptr = u_malloc(sizes[i]);
CHECK(ptr != NULL);
CHECK(u_get_allocated_count() == count + 1);
memset(ptr, 0x5a, sizes[i]);
u_check(ptr, "small allocation", LOCATION);
u_free(ptr);
CHECK(u_get_allocated_count() == count);
}
puts("PASS: zero, odd and ordinary allocation sizes");
}
static void test_oom(void) {
size_t count = u_get_allocated_count();
fail_malloc = true;
CHECK(u_malloc(0) == NULL);
CHECK(!fail_malloc);
fail_malloc = true;
CHECK(u_calloc(0, UINT32_MAX) == NULL);
CHECK(!fail_malloc);
fail_malloc = true;
CHECK(u_calloc(UINT32_MAX, 0) == NULL);
CHECK(!fail_malloc);
fail_malloc = true;
CHECK(u_realloc(NULL, 0) == NULL);
CHECK(!fail_malloc);
fail_malloc = true;
CHECK(u_malloc(17) == NULL);
CHECK(!fail_malloc);
CHECK(u_get_allocated_count() == count);
char* ptr = u_strdup("preserve on OOM");
CHECK(ptr != NULL);
fail_malloc = true;
CHECK(u_realloc(ptr, 128) == NULL);
CHECK(!fail_malloc);
CHECK(strcmp(ptr, "preserve on OOM") == 0);
CHECK(u_get_allocated_count() == count + 1);
u_free(ptr);
CHECK(u_get_allocated_count() == count);
puts("PASS: OOM for zero/nonzero sizes and realloc preserves its old block");
}
static void test_large_sizes(void) {
/* Измеряем служебный размер по запросу malloc для пустого блока. */
void* empty = u_malloc(0);
CHECK(empty != NULL);
size_t overhead = malloc_size;
u_free(empty);
CHECK(overhead > 0 && overhead <= 1024);
const uint32_t sizes[] = {
UINT32_MAX - 1024, UINT32_MAX - (uint32_t)overhead, UINT32_MAX - (uint32_t)overhead + 1, UINT32_MAX - 1, UINT32_MAX
};
size_t count = u_get_allocated_count();
char* old = u_strdup("preserve on large OOM");
CHECK(old != NULL);
for (size_t i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
for (int api = 0; api < 3; api++) {
size_t calls = malloc_calls;
fail_malloc = true;
void* ptr;
if (api == 0) ptr = u_malloc(sizes[i]);
else if (api == 1) ptr = u_calloc(sizes[i], 1);
else ptr = u_realloc(old, sizes[i]);
CHECK(ptr == NULL);
if ((uint64_t)sizes[i] + overhead > SIZE_MAX) {
CHECK(malloc_calls == calls);
CHECK(fail_malloc);
fail_malloc = false;
} else {
CHECK(malloc_calls == calls + 1);
CHECK(!fail_malloc);
CHECK(malloc_size == (uint64_t)sizes[i] + overhead);
}
CHECK(u_get_allocated_count() == count + 1);
CHECK(strcmp(old, "preserve on large OOM") == 0);
}
}
u_free(old);
CHECK(u_get_allocated_count() == count);
puts("PASS: sizes near UINT32_MAX reach malloc intact or are rejected before overflow");
}
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_WARN);
test_small_allocations();
test_oom();
test_large_sizes();
CHECK(u_get_allocated_count() == 0);
puts("PASS: mem regression tests");
return 0;
}
+53 -48
View File
@@ -1,13 +1,11 @@
/**
* Test for memory pool optimization and configuration file support
* Проверка пулов памяти и настройки категорий отладки из строки.
*/
#include "test_utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
@@ -22,12 +20,60 @@ static void test_waiter_callback(struct ll_queue* q, void* arg) {
test_callback_count++;
}
static int test_debug_config(void) {
const char* configs[] = {
"sys=debug,etcp=error,crypto=disabled,discovery=trace",
"sys:debug,etcp:error,crypto:disabled,discovery:trace"
};
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); i++) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_WARN);
if (debug_parse_config(configs[i]) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse configuration '%s'", configs[i]);
return 1;
}
for (int category = 0; category < DEBUG_CATEGORY_COUNT; category++) {
debug_level_t expected = DEBUG_LEVEL_NONE;
if (category == DEBUG_CATEGORY_SYS) expected = DEBUG_LEVEL_DEBUG;
else if (category == DEBUG_CATEGORY_ETCP) expected = DEBUG_LEVEL_ERROR;
else if (category == DEBUG_CATEGORY_CRYPTO) expected = DEBUG_LEVEL_DISABLED;
else if (category == DEBUG_CATEGORY_DISCOVERY) expected = DEBUG_LEVEL_TRACE;
if (g_debug_config.category_levels[category] != expected) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Category %d: expected level=%d, got=%d for '%s'",
category, expected, g_debug_config.category_levels[category], configs[i]);
return 1;
}
debug_level_t effective = expected == DEBUG_LEVEL_NONE ? DEBUG_LEVEL_WARN : expected;
for (debug_level_t level = DEBUG_LEVEL_ERROR; level <= DEBUG_LEVEL_TRACE; level++) {
int enabled = effective != DEBUG_LEVEL_DISABLED && level <= effective;
if (debug_should_output(level, category) != enabled) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Category %d, level=%d: expected output=%d for '%s'",
category, level, enabled, configs[i]);
return 1;
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Configuration '%s': category indices and output filters PASS", configs[i]);
}
const char* invalid[] = {"sys", "unknown=debug", "all=debug"};
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) {
debug_level_t previous[DEBUG_CATEGORY_COUNT];
memcpy(previous, g_debug_config.category_levels, sizeof(previous));
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Expecting rejection of invalid configuration '%s'", invalid[i]);
if (debug_parse_config(invalid[i]) != -1 || memcmp(previous, g_debug_config.category_levels, sizeof(previous)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid configuration '%s' was accepted or changed category levels", invalid[i]);
return 1;
}
}
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Invalid debug configurations rejected: PASS");
return 0;
}
int main() {
debug_config_init();
debug_set_level(DEBUG_LEVEL_TRACE);
debug_set_categories(DEBUG_CATEGORY_ALL);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== Memory Pool and Config File Test ===");
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== Memory Pool and Debug Configuration Test ===");
/* Любой размер блока, включая невыровненный хвост, допустим для malloc/realloc/free. */
for (uint32_t size = 1; size <= 17; size++) {
unsigned char* p = u_malloc(size);
@@ -97,49 +143,8 @@ int main() {
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Memory pool test: PASS");
// Test 2: Configuration file support
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Test 2: Configuration file support...");
// Create a test configuration file
const char* config_file = "/tmp/debug_test.conf";
FILE* f = fopen(config_file, "w");
if (f) {
fprintf(f, "# Test debug configuration file\n");
fprintf(f, "ll_queue:debug\n");
fprintf(f, "uasync:info\n");
fprintf(f, "memory:warn\n");
fprintf(f, "# This is a comment\n");
fprintf(f, "etcp:error\n");
fclose(f);
// Parse the configuration string (we don't have file parsing, so use string parsing)
const char* config_string = "ll_queue:debug,uasync:info,memory:warn,etcp:error";
if (debug_parse_config(config_string) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Configuration parsed successfully");
// Test that configuration was applied
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_SYS)) {
DEBUG_INFO(DEBUG_CATEGORY_SYS, "LL_QUEUE debug level correctly set");
}
if (debug_should_output(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_SYS)) {
DEBUG_INFO(DEBUG_CATEGORY_SYS, "UASYNC info level correctly set");
}
if (!debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_ETCP)) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "ETCP correctly limited to error level");
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Note: Hot reload functionality not implemented in current debug system");
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse configuration");
}
test_unlink(config_file);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to create test configuration file");
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Configuration file test: PASS");
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Test 2: Debug configuration strings...");
if (test_debug_config() != 0) return 1;
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== All tests completed successfully ===");
return 0;