From 3a8cc8a3269bb9d431090bc824e7354db3017035 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 23:03:48 +0200 Subject: [PATCH] Exercise encrypted UDP INIT request body boundaries --- tests/test_etcp_init_liveness.c | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/test_etcp_init_liveness.c b/tests/test_etcp_init_liveness.c index 44a60c4c..deb449cc 100644 --- a/tests/test_etcp_init_liveness.c +++ b/tests/test_etcp_init_liveness.c @@ -100,6 +100,7 @@ static void setup(unsigned swapped, unsigned count) { etcp_add_link_status_cbk(ep[side].inst, link_event, &ep[side]); CHECK(etcp_bind(ep[side].inst, 253, receive) == 0); } + if (!count) return; /* Отдельная проверка входящего INIT до создания транспорта. */ ep[0].conn = etcp_connection_create(ep[0].inst, "liveness"); CHECK(ep[0].conn); ep[0].conn->peer_node_id = ep[1].inst->node_id; CHECK(sc_init_ctx(&ep[0].conn->crypto_ctx, &ep[0].inst->my_keys) == SC_OK); @@ -198,6 +199,84 @@ static void healthy_alternative(void) { teardown(); } +/* Настоящий wire INIT: криптография не исправляет намеренно усечённое тело запроса. */ +static void send_init_request_body(const struct ETCP_INIT_REQUEST_PKT *request, unsigned length, unsigned flag) { + uint8_t plain[ETCP_ENCRYPTED_HDR_SIZE + ETCP_INIT_REQ_SIZE + 32] = {0}; + CHECK(length <= sizeof(plain) - ETCP_ENCRYPTED_HDR_SIZE); + plain[2] = flag; + unsigned copied = length < sizeof(*request) ? length : sizeof(*request); + if (copied) memcpy(plain + ETCP_ENCRYPTED_HDR_SIZE, request, copied); + sc_context_t crypto; + CHECK(sc_init_ctx(&crypto, &ep[0].inst->my_keys) == SC_OK); + CHECK(sc_set_peer_public_key(&crypto, ep[1].inst->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK); + uint8_t wire[sizeof(plain) + SC_ENCRYPT_OVERHEAD + SC_PUBKEY_ENC_SIZE]; size_t encrypted = 0; + CHECK(sc_encrypt(&crypto, plain, ETCP_ENCRYPTED_HDR_SIZE + length, wire, &encrypted) == SC_OK); + uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE] = {1,2,3,4,5,6,7,8}; + memcpy(wire + encrypted, salt, sizeof(salt)); + CHECK(sc_obfuscate_pubkey(salt, ep[1].inst->my_keys.public_key, ep[0].inst->my_keys.public_key, + wire + encrypted + sizeof(salt)) == SC_OK); + size_t size = encrypted + SC_PUBKEY_ENC_SIZE; + CHECK(socket_sendto(ep[0].socket->fd, wire, size, (const struct sockaddr*)&ep[1].socket->interface_addr, + sizeof(struct sockaddr_in)) == (ssize_t)size); + etcp_connections_read_callback_socket(ep[1].socket->fd, ep[1].socket); +} + +static void request_boundaries(unsigned established) { + setup(0, established); + struct ETCP_INIT_REQUEST_PKT request = {0}; + for (unsigned i = 0; i < 8; i++) { + request.node_id[i] = ep[0].inst->node_id >> (56 - i * 8); + request.reset_id[i] = (ep[0].conn ? ep[0].conn->reset_id : UINT64_C(0x1020304050607080)) >> (56 - i * 8); + } + request.mtu[0] = ep[0].socket->mtu >> 8; request.mtu[1] = ep[0].socket->mtu; + request.keepalive[0] = ep[0].inst->keepalive_interval >> 8; + request.keepalive[1] = ep[0].inst->keepalive_interval; + request.link_id = established ? first_link(0)->local_link_id : 1; + request.socket_id = ep[0].socket->sock_id; request.type = ep[0].socket->type; + request.device_type = ep[0].inst->client_type; + memcpy(request.ed25519_pubkey, ep[0].inst->my_ed25519_pubkey, SC_PUBKEY_SIZE); + /* Прогреть пул, чтобы отказ первого пакета не менял размер пула. */ + void *warm = memory_pool_alloc(ep[1].inst->pkt_pool); CHECK(warm); memory_pool_free(ep[1].inst->pkt_pool, warm); + unsigned rejected = 0, accepted = 0; + for (unsigned variant = 0; variant < 2; variant++) { + request.code = variant ? ETCP_INIT_REQUEST_NOINIT : ETCP_INIT_REQUEST; + for (unsigned length = 0; length < sizeof(request); length++) { + struct ETCP_CONN before_conn; struct ETCP_LINK before_link; + if (established) { memcpy(&before_conn, ep[1].conn, sizeof(before_conn)); memcpy(&before_link, first_link(1), sizeof(before_link)); } + int connections = ep[1].inst->connections->count, pool_free = ep[1].inst->pkt_pool->free_count; + size_t errors = ep[1].socket->pkt_format_errors; + send_init_request_body(&request, length, 0); + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "INIT_REQUEST boundary: established=%u code=%u body=%u errors=%zu->%zu", + established, request.code, length, errors, ep[1].socket->pkt_format_errors); + CHECK(ep[1].socket->pkt_format_errors == errors + 1 && ep[1].socket->errorcode == 7); + CHECK(ep[1].inst->connections->count == connections && ep[1].inst->pkt_pool->free_count == pool_free); + if (established) { + CHECK(!memcmp(&before_conn, ep[1].conn, sizeof(before_conn))); + CHECK(!memcmp(&before_link, first_link(1), sizeof(before_link))); + } + rejected++; + } + } + for (unsigned variant = 0; variant < 2; variant++) { + request.code = variant ? ETCP_INIT_REQUEST_NOINIT : ETCP_INIT_REQUEST; + const unsigned lengths[] = {sizeof(request), sizeof(request) + 1, sizeof(request) + 32}; + for (unsigned i = 0; i < 3; i++) for (unsigned flag = 0; flag < 2; flag++) { + size_t errors = ep[1].socket->pkt_format_errors; + send_init_request_body(&request, lengths[i], flag); + CHECK(ep[1].socket->pkt_format_errors == errors); + if (!ep[1].conn) { + ep[1].conn = instance_find_conn(ep[1].inst, ep[0].inst->node_id); CHECK(ep[1].conn); + CHECK(etcp_conn_ref_take(ep[1].conn) == 0); + } + struct ETCP_LINK *link = first_link(1); CHECK(link); + CHECK(link->initialized && link->remote_keepalive == flag && link->recv_keepalive); + accepted++; + } + } + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "INIT_REQUEST boundaries established=%u: rejected=%u accepted=%u", established, rejected, accepted); + teardown(); +} + /* Граница тела не включает timestamp/flag_up; отказ не меняет линк, соединение и владение pkt. */ static void response_boundaries(void) { setup(0, 1); @@ -286,6 +365,7 @@ int main(void) { if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG); test_clock_init(10); CHECK(socket_platform_init() == 0); size_t baseline = u_get_allocated_count(); + for (unsigned established = 0; established < 2; established++) { request_boundaries(established); check_memory(baseline); } response_boundaries(); check_memory(baseline); for (unsigned swapped = 0; swapped < 2; swapped++) for (unsigned order = 0; order < 2; order++) { one_way(swapped, 1 - order); check_memory(baseline);