From 7593120bd90ecc0191fb35a01f55056dfce604e2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Oct 2026 00:06:51 +0100 Subject: [PATCH] Parse CIDR, control endpoints and storage sizes without truncation --- src/config_parser.c | 145 +++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 75 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 8c58e059..f2c00508 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -52,56 +52,58 @@ typedef enum { } section_type_t; static int assign_string(char *dest, size_t dest_size, const char *src) { - if (!dest || !src || strlen(src) >= dest_size) return -1; + if (!dest || !src || strlen(src) >= dest_size) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "configuration string exceeds capacity=%zu or has a missing argument", dest_size); + return -1; + } strcpy(dest, src); return 0; } +/* Полное беззнаковое число без знака и хвоста; значение меняется только после всех проверок. */ +static int parse_unsigned(const char* text, int base, uint64_t maximum, uint64_t* out) { + char* end; + errno = 0; + unsigned long long value = strtoull(text, &end, base); + if ((base == 16 ? config_ini_nibble((unsigned char)*text) < 0 : !isdigit((unsigned char)*text)) || + end == text || *end || errno || value > maximum) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid unsigned base-%d value '%s', maximum=%llu", base, text, (unsigned long long)maximum); + return -1; + } + *out = value; return 0; +} + static int parse_ip_with_netmask(const char *str, struct IP *ip, uint8_t *netmask) { char ip_str[64]; - strncpy(ip_str, str, sizeof(ip_str) - 1); - ip_str[sizeof(ip_str) - 1] = '\0'; - + if (assign_string(ip_str, sizeof(ip_str), str)) return -1; char *slash = strchr(ip_str, '/'); - if (slash) { - *slash = '\0'; - *netmask = atoi(slash + 1); - } else { - *netmask = 32; // Default IPv4 netmask + if (slash) *slash++ = '\0'; + struct IP parsed = {0}; + unsigned maximum; + if (inet_pton(AF_INET, ip_str, &parsed.addr.v4) == 1) { parsed.family = AF_INET; maximum = 32; } + else if (inet_pton(AF_INET6, ip_str, &parsed.addr.v6) == 1) { parsed.family = AF_INET6; maximum = 128; } + else { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid IP address: %s", str); return -1; } - - // Try IPv4 first - struct in_addr addr4; - if (inet_pton(AF_INET, ip_str, &addr4) == 1) { - ip->family = AF_INET; - ip->addr.v4 = addr4; - return 0; - } - - // Try IPv6 - struct in6_addr addr6; - if (inet_pton(AF_INET6, ip_str, &addr6) == 1) { - ip->family = AF_INET6; - ip->addr.v6 = addr6; - if (*netmask == 32) *netmask = 128; // Default IPv6 netmask - return 0; - } - - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_ip_with_netmask: invalid IP address format: %s", str); - return -1; + uint64_t prefix = maximum; + if (slash && parse_unsigned(slash, 10, maximum, &prefix)) return -1; + *ip = parsed; *netmask = (uint8_t)prefix; return 0; } static int parse_sockaddr(const char *addr_str, const char *port_str, struct sockaddr_storage *sockaddr) { struct addrinfo hints = {0}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICSERV; + uint64_t port; + if (!addr_str[0] || parse_unsigned(port_str, 10, UINT16_MAX, &port)) return -1; char clean_addr[MAX_ADDR_LEN]; const char *a = addr_str; if (addr_str[0] == '[') { size_t len = strlen(addr_str); if (len > 2 && addr_str[len - 1] == ']') { - if (len - 2 >= sizeof(clean_addr)) return -1; + if (len - 2 >= sizeof(clean_addr)) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "bracketed address is too long"); return -1; } memcpy(clean_addr, addr_str + 1, len - 2); clean_addr[len - 2] = '\0'; a = clean_addr; @@ -109,10 +111,15 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc } struct addrinfo *result; - if (getaddrinfo(a, port_str, &hints, &result) != 0) { + int rc = getaddrinfo(a, port_str, &hints, &result); + if (rc != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "cannot resolve configured endpoint host=%s port=%s error=%d", a, port_str, rc); return -1; } - + if (!result || result->ai_addrlen > sizeof(*sockaddr)) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "resolved endpoint has an invalid address size"); freeaddrinfo(result); return -1; + } + memset(sockaddr, 0, sizeof(*sockaddr)); memcpy(sockaddr, result->ai_addr, result->ai_addrlen); freeaddrinfo(result); return 0; @@ -120,11 +127,10 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc static int parse_address_and_port(const char *str, struct sockaddr_storage *sockaddr) { char addr_copy[MAX_ADDR_LEN]; - if (strlen(str) >= sizeof(addr_copy)) return -1; - strcpy(addr_copy, str); + if (assign_string(addr_copy, sizeof(addr_copy), str)) return -1; char *port_str = strrchr(addr_copy, ':'); - if (!port_str) return -1; + if (!port_str) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "endpoint requires host:port: %s", str); return -1; } *port_str = '\0'; port_str++; @@ -151,6 +157,7 @@ static struct CFG_CLIENT_LINK* create_client_link(struct CFG_SERVER* local_srv, } link->local_srv = local_srv; + if (local_srv) snprintf(link->server_name, sizeof(link->server_name), "%s", local_srv->name); if (parse_address_and_port(remote_addr, &link->remote_addr) < 0) { u_free(link); @@ -271,31 +278,19 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa } static int parse_control_allow(const char *rule_str, struct global_config *global) { - if (!rule_str || !global) return -1; - - char rule_copy[64]; - strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1); - rule_copy[sizeof(rule_copy) - 1] = '\0'; - config_ini_trim(rule_copy); - - uint8_t cidr = 32; - char *slash = strchr(rule_copy, '/'); - if (slash) { - *slash = '\0'; - cidr = (uint8_t)atoi(slash + 1); - if (cidr > 32) cidr = 32; - } - - struct in_addr addr; - if (inet_pton(AF_INET, rule_copy, &addr) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str); + struct IP address; + uint8_t cidr; + if (parse_ip_with_netmask(rule_str, &address, &cidr) || address.family != AF_INET) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "control allow requires IPv4[/0..32]: %s", rule_str); return -1; } - - uint32_t ip = ntohl(addr.s_addr); - uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr)); + uint32_t ip = ntohl(address.addr.v4.s_addr); + uint32_t mask = cidr ? UINT32_MAX << (32 - cidr) : 0; if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) { + if ((uint32_t)global->control_allow_count > UINT32_MAX / sizeof(struct CFG_CONTROL_ALLOW) - INITIAL_ARRAY_CAPACITY) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "too many control allow rules"); return -1; + } int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY; struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows, new_cap * sizeof(struct CFG_CONTROL_ALLOW)); @@ -461,21 +456,19 @@ static int parse_global(const char *key, const char *value, struct global_config static int parse_control(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) { if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) { - strncpy(global->control_ip, value, sizeof(global->control_ip) - 1); - global->control_ip[sizeof(global->control_ip) - 1] = '\0'; + if (assign_string(global->control_ip, sizeof(global->control_ip), value)) return -1; + if (global->control_sock.ss_family) { + uint16_t port = global->control_sock.ss_family == AF_INET ? + ntohs(((struct sockaddr_in*)&global->control_sock)->sin_port) : + ntohs(((struct sockaddr_in6*)&global->control_sock)->sin6_port); + char port_str[6]; snprintf(port_str, sizeof(port_str), "%u", port); + return parse_sockaddr(value, port_str, &global->control_sock); + } return 0; } if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) { - char control_ip[MAX_ADDR_LEN]; - if (global->control_ip[0] != '\0') { - strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1); - } else { - strcpy(control_ip, "127.0.0.1"); - } - char port_str[16]; - snprintf(port_str, sizeof(port_str), "%s", value); - parse_sockaddr(control_ip, port_str, &global->control_sock); - return 0; + const char* control_ip = global->control_ip[0] ? global->control_ip : "127.0.0.1"; + return parse_sockaddr(control_ip, value, &global->control_sock); } if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) { return parse_control_allow(value, global); @@ -824,15 +817,17 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c } static int parse_size_with_suffix(const char *value, uint64_t *out_bytes) { - if (!value || !out_bytes) return -1; - char *end = NULL; + char *end; + errno = 0; uint64_t num = strtoull(value, &end, 10); - if (end == value) return -1; + if (!isdigit((unsigned char)*value) || errno || end == value) return -1; while (*end && isspace((unsigned char)*end)) end++; - if (*end == '\0') { *out_bytes = num; return 0; } - if (*end == 'M' || *end == 'm') { *out_bytes = num * 1024ULL * 1024ULL; return 0; } - if (*end == 'G' || *end == 'g') { *out_bytes = num * 1024ULL * 1024ULL * 1024ULL; return 0; } - return -1; + uint64_t scale = 1; + if (*end == 'M' || *end == 'm') { scale = UINT64_C(1024) * 1024; end++; } + else if (*end == 'G' || *end == 'g') { scale = UINT64_C(1024) * 1024 * 1024; end++; } + while (*end && isspace((unsigned char)*end)) end++; + if (*end || num > UINT64_MAX / scale) return -1; + *out_bytes = num * scale; return 0; } static int parse_chatserver(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {