Exercise encrypted UDP INIT request body boundaries

This commit is contained in:
evgeny committed 2026-10-23 23:03:48 +02:00
1 parent bf7464a4d9
commit 3a8cc8a326
1 file changed
+80
+80
View File
@@ -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);