gdhcp in ConnMan through 1.41 could be used by network-adjacent attackers to cause a denial of service, terminating the connman process.
Buffer Overflow
ConnMan
- ConnMan - Versions from 0.55 to 1.41 are affected (all versions up to the current moment of connman with gdhcp).
Affected module: gdhcp Affected function: https://kernel.googlesource.com/pub/scm/network/connman/connman/+/refs/tags/1.41/gdhcp/client.c#1319
- Remote
- Denial of Service
The listener_event function in the ./gdhcp/client.c file is called to accept DHCP packets from the DHCP server.
When dhcp_client->listen_mode == L2 (the value at the start of DHCP requests), the dhcp_recv_l2_packet function will be called.
This function reads IP, UDP and DHCP headers into a packet buffer, and writes the number of bytes read into a bytes variable. But then the bytes variable is overwritten with the controlled value ntohs(packet.ip.tot_len) from the packet:
static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd,
struct sockaddr_in *dst_addr)
{
int bytes;
struct ip_udp_dhcp_packet packet;
uint16_t check;
memset(&packet, 0, sizeof(packet));
bytes = read(fd, &packet, sizeof(packet));
if (bytes < 0)
return -1;
if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp)))
return -1;
if (bytes < ntohs(packet.ip.tot_len))
/* packet is bigger than sizeof(packet), we did partial read */
return -1;
/* ignore any extra garbage bytes */
bytes = ntohs(packet.ip.tot_len); // <-- ASSIGNMENT OF CONTROLLED DATA
// [ ... ]
}Pre and post checks in the current code are not enough. For example, the value ntohs(packet.ip.tot_len) could be 0, so the pre-checks would pass, and the value 0 would then be assigned to the variable bytes.
In the sanity_check function, all checks can be passed, since all values are controlled. For the last check it is necessary to precalculate the value of packet->udp.len.
static bool sanity_check(struct ip_udp_dhcp_packet *packet, int bytes)
{
// [ ... ]
if (ntohs(packet->udp.len) != (uint16_t)(bytes - sizeof(packet->ip)))
return false;
// [ ... ]
}In the case of the binaries I had and the value 0 in the variable bytes, the value for ntohs(packet->udp.len) was 0xffec.
Then in the dhcp_recv_l2_packet function checksums are checked.
The checksum value for the IP header can be precomputed. And for the UDP header is skipped by setting the value of 0 as a checksum value.
static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd,
struct sockaddr_in *dst_addr)
{
// [ ... ]
check = packet.ip.check;
packet.ip.check = 0;
if (check != dhcp_checksum(&packet.ip, sizeof(packet.ip)))
return -1;
/* verify UDP checksum. IP header has to be modified for this */
memset(&packet.ip, 0, offsetof(struct iphdr, protocol));
/* ip.xx fields which are not memset: protocol, check, saddr, daddr */
packet.ip.tot_len = packet.udp.len; /* yes, this is needed */
check = packet.udp.check;
packet.udp.check = 0;
if (check && check != dhcp_checksum(&packet, bytes))
return -1;
// [ ... ]
}Then, in the dhcp_recv_l2_packet function, a call to the memcpy function follows, where the underflow occurs in the argument for the copy size:
static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd,
struct sockaddr_in *dst_addr)
{
// [ ... ]
memcpy(dhcp_pkt, &packet.data, bytes - (sizeof(packet.ip) +
sizeof(packet.udp))); // <-- STACK BUFFER OVERFLOW
// [ ... ]
}Because of this, stack corruption (stack buffer overflow) occurs, which leads to access to an invalid address, overwriting the value of the stack canary, or returning to an incorrect address when exiting the function, depending on the compilation settings. As a result, the process is terminated, resulting in a Denial of Service.
This vulnerability can be triggered by response to a connman DHCP client.
For example, it can be triggered on DHCP Offer or DHCP ACK responses from the server to the client.
Vulnerability is presented on all versions up to the current moment of connman with gdhcp (from 0.55 to 1.41).