Test parser rejection, CIDR boundaries and allocation rollback

This commit is contained in:
evgeny committed 2026-10-26 09:08:23 +01:00
1 parent d21af324ac
commit 46bf5b8541
3 files changed
+117 -4

No files matched your search

+4
View File
@@ -286,6 +286,10 @@ test_socket_monitor_LDFLAGS = -Wl,--wrap=socket -Wl,--wrap=bind -Wl,--wrap=close
-Wl,--wrap=get_default_route_netif_index -Wl,--wrap=get_outgoing_local_ip -Wl,--wrap=etcp_socket_cbk_fire
if OS_LINUX
test_socket_monitor_LDFLAGS += -Wl,--wrap=recvfrom -Wl,--wrap=__recvfrom_chk
check_PROGRAMS += test_config_parser
test_config_parser_SOURCES = test_config_parser.c
test_config_parser_LDADD = $(LIBUTUN) $(COMMON_LIBS)
test_config_parser_LDFLAGS = -Wl,--wrap=u_calloc_impl -Wl,--wrap=u_realloc_impl
else
test_socket_monitor_LDFLAGS += -Wl,--wrap=read
endif
+111
View File
@@ -0,0 +1,111 @@
/* Контракт INI: границы значений, единый отказ файла/буфера, rollback памяти и безопасный dump. */
#include "config_parser.h"
#include "../lib/mem.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static unsigned fail_at, allocation;
static int inject(const char* where) { return fail_at && strstr(where, "config_parser.c") && ++allocation == fail_at; }
extern void* __real_u_calloc_impl(uint32_t, uint32_t, const char*);
extern void* __real_u_realloc_impl(void*, uint32_t, const char*);
void* __wrap_u_calloc_impl(uint32_t n, uint32_t size, const char* where) {
return inject(where) ? NULL : __real_u_calloc_impl(n, size, where);
}
void* __wrap_u_realloc_impl(void* p, uint32_t size, const char* where) {
return inject(where) ? NULL : __real_u_realloc_impl(p, size, where);
}
static struct utun_config* parse(const char* text) { return parse_config_from_buf(text, strlen(text), "parser-contract"); }
static void test_values(void) {
struct utun_config* cfg = parse("[routing]\nmy_subnet=::1/32\nroute_subnet=2001:db8::/0\nroute_subnet=192.0.2.0/0\n"
"[control]\nport=0\nip=127.0.0.2\nallow=0.0.0.0/0\nallow=192.0.2.123/24\n"
"[server:udp]\naddr=127.0.0.1:0\n[client:peer]\nlink=udp:127.0.0.2:65535\n"
"[chat]\nstorage_total_size=18446744073709551615\nstorage_unit_size=17179869183G\n"
"[tcp_proxy_client]\nenabled=no\nmtu=1280\n");
assert(cfg && cfg->my_subnets->netmask == 32 && cfg->route_subnets->netmask == 0 && cfg->route_subnets->next->netmask == 0);
assert(cfg->global.control_sock.ss_family == AF_INET &&
ntohl(((struct sockaddr_in*)&cfg->global.control_sock)->sin_addr.s_addr) == 0x7f000002);
assert(!cfg->global.control_allows[0].network && !cfg->global.control_allows[0].netmask);
assert(cfg->global.control_allows[1].network == 0xc0000200 && cfg->global.control_allows[1].netmask == 0xffffff00);
assert(cfg->clients->links->local_srv == cfg->servers && !strcmp(cfg->clients->links->server_name, "udp"));
assert(cfg->global.chatserver_storage_total_size == UINT64_MAX);
assert(cfg->global.chatserver_storage_unit_size == UINT64_C(17179869183) * 1024 * 1024 * 1024);
assert(!cfg->global.tcp_proxy_client_enabled); free_config(cfg);
cfg = parse("[routing]\nmy_subnet=::1\n[server:v6]\naddr=temporary_ipv6:65535\naddr=[::1]:0\n"
"[control]\nip=::1\nport=65535\n[reality]\nversion=0.255.1\n");
assert(cfg && cfg->my_subnets->netmask == 128 && !cfg->servers->ipv6_mode);
assert(cfg->global.control_sock.ss_family == AF_INET6 && cfg->global.reality.version[1] == 255); free_config(cfg);
cfg = parse("[chat]\n[nat]\n[tcp_proxy_client]\n[tcp_proxy_server]\n");
assert(cfg && cfg->global.chatserver_enabled && cfg->global.db_sync_enabled && cfg->global.nat_enabled &&
cfg->global.tcp_proxy_client_enabled && cfg->global.tcp_proxy_server_enabled); free_config(cfg);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS IPv4/IPv6 CIDR, control order, port bounds, size limits, link names and section defaults");
}
static void test_rejections(void) {
const char* bad[] = {
"[routing]\nmy_subnet=::1/129\n", "[routing]\nmy_subnet=::1/32junk\n",
"[routing]\nmy_subnet=127.0.0.1/256\n", "[routing]\nmy_subnet=127.0.0.1/-1\n",
"[routing]\nmy_subnet=127.0.0.1/\n", "[routing]\nmy_subnet=127.0.0.1/33\n",
"[routing]\nwrong=1\n", "[control]\nallow=127.0.0.1/256\n", "[control]\nallow=::1/0\n",
"[control]\nport=65536\n", "[control]\nport=80x\n", "[control]\nport=\n", "[control]\nport=http\n",
"[control]\nport=1000\nip=invalid.invalid\n", "[server:s]\naddr=127.0.0.1:65536\n",
"[server:s]\naddr=temporary_ipv6:-1\n", "[server:s]\naddr=permanent_ipv6:65536\n",
"[server:s]\ntransport=typo\n", "[server:s]\ntype=typo\n", "[client:c]\nlink=broken\n",
"[client:c]\nshort_id=bad\n", "[reality]\nversion=1.2.3tail\n", "[reality]\nversion=256.0.0\n",
"[reality]\nversion=9999999999999999999999.1.1\n", "[reality]\nshort_ids=01\n",
"[socks]\naddr=:1080\n", "[socks]\naddr=127.0.0.1:1080x\n", "[socks]\naddr=127.0.0.1:-1\n",
"[chat]\nstorage_total_size=-1\n", "[chat]\nstorage_unit_size=1MB\n",
"[chat]\nstorage_total_size=18446744073709551616\n", "[chat]\nstorage_total_size=17179869184G\n",
"[allowed_keys]\nkey=bad\n", "[network:n]\nid=100000000000000\n", "[network:n]\npubkey=1g\n",
"[network:n]\nid=1\n", "[ntp]\nserver=\n", "[tcp_proxy_client]\nforward=0 -> 1.2.3.4:80\n",
"[server:s]\ninvalid\n"
};
char path[] = "/tmp/utun-parser-contract-XXXXXX";
int fd = mkstemp(path); assert(fd >= 0); close(fd);
for (size_t i = 0; i < sizeof(bad) / sizeof(*bad); i++) {
size_t before = u_get_allocated_count();
assert(!parse(bad[i]) && u_get_allocated_count() == before);
FILE* file = fopen(path, "wb"); assert(file && fputs(bad[i], file) >= 0 && !fclose(file));
assert(!parse_config(path) && u_get_allocated_count() == before);
}
unlink(path);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS %zu invalid configurations rejected identically from file/buffer without leaks", sizeof(bad) / sizeof(*bad));
}
static void test_allocations(void) {
const char* text = "[global]\nmy_node_name=allocation\n[server:a]\naddr=127.0.0.1:0\n[client:b]\nlink=a:127.0.0.2:1\n"
"[routing]\nmy_subnet=127.0.0.1/32\n[firewall]\nallow=127.0.0.1\n[control]\nallow=127.0.0.1/32\n"
"[allowed_keys]\nkey=0101010101010101010101010101010101010101010101010101010101010101\n"
"[ntp]\nserver=localhost\n[network:n]\nid=1\n"
"pubkey=0202020202020202020202020202020202020202020202020202020202020202\n"
"signing_key=0303030303030303030303030303030303030303030303030303030303030303\n";
size_t baseline = u_get_allocated_count();
unsigned tested;
for (tested = 1; tested < 64; tested++) {
allocation = 0; fail_at = tested;
struct utun_config* cfg = parse(text);
unsigned calls = allocation; fail_at = 0;
if (calls < tested) { assert(cfg); free_config(cfg); break; }
assert(!cfg && u_get_allocated_count() == baseline);
}
assert(tested > 8 && tested < 64 && u_get_allocated_count() == baseline);
struct utun_config* cfg = parse(text); assert(cfg);
strcpy(cfg->global.my_private_key_hex, "0404040404040404040404040404040404040404040404040404040404040404");
char path[] = "/tmp/utun-parser-dump-XXXXXX"; int fd = mkstemp(path); assert(fd >= 0); close(fd);
assert(!debug_enable_file_output(path, 1)); print_config(cfg); debug_disable_file_output();
FILE* file = fopen(path, "rb"); assert(file);
char output[8192]; size_t n = fread(output, 1, sizeof(output) - 1, file); output[n] = '\0'; fclose(file); unlink(path);
assert(strstr(output, "[network:n]") && !strstr(output, cfg->global.my_private_key_hex) && !strstr(output, cfg->networks->signing_key_hex));
assert(update_config_keys("/dev/full", cfg->global.my_private_key_hex, cfg->networks->pubkey_hex) < 0);
free_config(cfg); assert(u_get_allocated_count() == baseline);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS %u allocation faults, redacted independent network dump and failed key append", tested - 1);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); assert(!socket_platform_init());
test_values(); test_rejections(); test_allocations(); socket_platform_cleanup(); return 0;
}
+2 -4
View File
@@ -250,8 +250,7 @@ static void test_reality_and_validation(void) {
snprintf(text, sizeof(text), "[global]\nmy_private_key=%s\n[REALITY]\nenabled=1\nprivate_key=%s\n"
"short_ids=0102030405060708,bad\n[reality]\nshort_ids =\n", private_hex, invalid_hex);
struct utun_config *config = parse_config_from_buf(text, strlen(text), "malformed");
assert(config && !config->global.reality.has_private_key && !config->global.reality.short_id_count);
free_config(config);
assert(!config); // парсер отклоняет ошибку; updater по-прежнему исправляет исходный INI до загрузки
write_text(text);
assert(!config_ensure_keys_and_node_id(path));
check_identity(private_hex);
@@ -285,8 +284,7 @@ static void test_reality_and_validation(void) {
for (size_t i = 0; i < sizeof(bad_lists) / sizeof(*bad_lists); i++) {
snprintf(text, sizeof(text), "[reality]\nshort_ids=%s\n", bad_lists[i]);
config = parse_config_from_buf(text, strlen(text), "bad-list");
assert(config && !config->global.reality.short_id_count);
free_config(config);
assert(!config);
}
memset(text, 'x', 1023); text[1023] = '\n'; text[1024] = '\0';
write_text(text);