Ping reads raw IP packets from the network to process responses in the
pr_pack function. As part of processing a response ping has to
reconstruct the IP header, the ICMP header and if present a 'quoted
packet', which represents the packet that generated an ICMP error. The
quoted packet again has an IP header and an ICMP header.
IP headers carry two fields that describe their length, the IP Header
Len (IHL) and the Total Packet len.
The IHL describes the length of the IP header and is packed into the
second nibble of the first byte of the packet. The first nibble contains
the IP version (4).
The IHL contains the number of 4 octet blocks the header takes up. It has
a minimum value of 5 (20 bytes) and a maximum value of 15 (60) bytes. If
the IHL is larger than 5, then IP options are expected following the
header and before any carried data.
Ping reads the IHL from received and quoted packets correctly, but fails
to do any verification on the received values. The IHL is then used to
perform a copy from a buffer into a struct ip.
This occurs from the outer packet in ping.c on line 1163:
/* * Get size of IP header of the received packet. The * information is contained in the lower four bits of the * first byte. */ memcpy(&l, buf, sizeof(l)); hlen = (l & 0x0f) << 2; memcpy(&ip, buf, hlen);
and for the inner packet on line 1307:
memcpy(&oip_header_len, icmp_data_raw, sizeof(oip_header_len)); oip_header_len = (oip_header_len & 0x0f) << 2; memcpy(&oip, icmp_data_raw, oip_header_len); oicmp_raw = icmp_data_raw + oip_header_len; memcpy(&oicmp, oicmp_raw, offsetof(struct icmp, icmp_id) + sizeof(oicmp.icmp_id));
struct ip is well defined and does not have space to carry any IP
If an IP packet or a quoted packet carries any option data (or is
deliberately corrupted) an overflow will occur with the copies above.
There can be up to 40 bytes of options carried in an IP Packet.
Any host running ping that can be sent an ICMP packet with IP options
or a malformed IHL can trigger stack corruption in ping.
I have included a scapy script that can trigger an ASAN failure for both
of the above cases. The first is triggered by running the script with
the opts command the second with the pip command. To test this I left
ing running on a host to receive packets:
$ ping -i 60 192.168.1.12 $ sudo python3 ipopts.py opts 192.168.1.2 # host ping is running on $ sudo python3 ipopts.py pip 192.168.1.2 # host ping is running on
My patch also includes a fix for this line:
memcpy(&oicmp, oicmp_raw, offsetof(struct icmp, icmp_id) + sizeof(oicmp.icmp_id));
I think it is trying to do a copy of ICMP_MINLEN data, but has picked
the wrong field and instead seems to be copying 6 bytes rather than 8.
My fix is to require an entire struct icmp worth of data in the quoted
packet which might not be be correct.