From 2c8802e64b583a253c1c3c5a020f0dcfc1a4e4c6 Mon Sep 17 00:00:00 2001 From: Dmitry Verenitsin Date: Sun, 9 Aug 2026 01:39:40 +0500 Subject: [PATCH] [core] Validate IPv6 XOR-MAPPED-ADDRESS length in STUN parser (#3115) `switch_stun_packet_attribute_get_xor_mapped_address()` read and XOR-rewrote a 16-byte IPv6 address whenever the `family` byte was 2, without checking the attribute value was that long. Reject `family == 2` values shorter than `sizeof(switch_stun_ipv6_t)` and clear the output address and port. Adds a regression test. --- src/switch_stun.c | 8 +++++++ tests/unit/switch_stun.c | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/switch_stun.c b/src/switch_stun.c index 93a571f783..3dbd81d5b9 100644 --- a/src/switch_stun.c +++ b/src/switch_stun.c @@ -434,6 +434,14 @@ SWITCH_DECLARE(uint8_t) switch_stun_packet_attribute_get_xor_mapped_address(swit if (ip->family == 2) { uint8_t *v6addr; + if (attribute->length < sizeof(switch_stun_ipv6_t)) { + /* attribute value too short to hold an IPv6 address; leave outputs defined */ + *ipstr = 0; + *port = 0; + + return 0; + } + ipv6 = (switch_stun_ipv6_t *)attribute->value; v6addr = (uint8_t *) &ipv6->address; v6_xor(v6addr, (uint8_t *)header->id); diff --git a/tests/unit/switch_stun.c b/tests/unit/switch_stun.c index 02e3a32863..92e62510e1 100644 --- a/tests/unit/switch_stun.c +++ b/tests/unit/switch_stun.c @@ -106,6 +106,53 @@ FST_TEARDOWN_END() } FST_TEST_END() + FST_TEST_BEGIN(test_stun_get_xor_mapped_address_short_ipv6) + { + /* + * An XOR-MAPPED-ADDRESS attribute whose family byte claims IPv6 (2) + * but whose value is only the 8-byte IPv4 size must be rejected: + * switch_stun_packet_attribute_get_xor_mapped_address must not read + * or XOR a 20-byte IPv6 address out of the 8-byte value. The packet + * is routed through switch_stun_packet_parse first so the attribute + * length is in host byte order, as the live callers see it. + */ + uint8_t buf[512] = { 0 }; + switch_stun_packet_t *packet; + switch_stun_packet_attribute_t *attr; + char out_ip[64]; + uint16_t out_port = 0xffff; + uint8_t ret; + + packet = switch_stun_packet_build_header(SWITCH_STUN_BINDING_RESPONSE, NULL, buf); + + /* Value layout: wasted(1) + family(1) + port(2) + address(4) = 8 bytes. */ + attr = (switch_stun_packet_attribute_t *)packet->first_attribute; + attr->type = htons(SWITCH_STUN_ATTR_XOR_MAPPED_ADDRESS); + attr->length = htons(8); + attr->value[0] = 0; /* wasted */ + attr->value[1] = 2; /* family byte claims IPv6 */ + attr->value[2] = 0; + attr->value[3] = 0; + attr->value[4] = 0x01; + attr->value[5] = 0x02; + attr->value[6] = 0x03; + attr->value[7] = 0x04; + packet->header.length = htons(4 + 8); + + packet = switch_stun_packet_parse(buf, 20 + 4 + 8); + fst_requires(packet != NULL); + + attr = (switch_stun_packet_attribute_t *)packet->first_attribute; + fst_xcheck(attr->length == 8, "parse accepts the 8-byte family-2 XOR-MAPPED-ADDRESS attribute"); + + memset(out_ip, 'x', sizeof(out_ip)); + ret = switch_stun_packet_attribute_get_xor_mapped_address(attr, &packet->header, out_ip, sizeof(out_ip), &out_port); + fst_xcheck(ret == 0, "short IPv6 XOR-MAPPED-ADDRESS attribute is rejected"); + fst_xcheck(out_ip[0] == '\0', "output address string left empty on reject"); + fst_xcheck(out_port == 0, "output port left defined on reject"); + } + FST_TEST_END() + FST_TEST_BEGIN(test_stun_add_binded_address_ipv4) { /*