diff --git a/sbin/ping/Makefile b/sbin/ping/Makefile --- a/sbin/ping/Makefile +++ b/sbin/ping/Makefile @@ -11,6 +11,9 @@ BINMODE=4555 LIBADD= m +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer +#CFLAGS+=-O0 -g + .if ${MK_INET_SUPPORT}!= "no" CFLAGS+= -DINET SRCS+= ping.c utils.c diff --git a/sbin/ping/ping.c b/sbin/ping/ping.c --- a/sbin/ping/ping.c +++ b/sbin/ping/ping.c @@ -1144,8 +1144,10 @@ struct icmp icp; struct ip ip; const u_char *icmp_data_raw; + ssize_t icmp_data_raw_len; double triptime; - int dupflag, hlen, i, j, recv_len; + int dupflag, i, j, recv_len; + uint8_t hlen; uint16_t seq; static int old_rrlen; static char old_rr[MAX_IPOPTLEN]; @@ -1155,15 +1157,27 @@ const u_char *oicmp_raw; /* - * Get size of IP header of the received packet. The - * information is contained in the lower four bits of the - * first byte. + * Get size of IP header of the received packet. + * The header length is contained in the lower four bits of the first + * byte and represents the number of 4 byte ocets the header takes up. + * + * The IHL minimum value is 5 (20 bytes) and its maximum value is 15 + * (60 bytes). */ memcpy(&l, buf, sizeof(l)); hlen = (l & 0x0f) << 2; - memcpy(&ip, buf, hlen); - /* Check the IP header */ + /* Reject IP packets with a short header */ + if (hlen < sizeof(struct ip)) { + if (options & F_VERBOSE) + warn("IHL too short (%d bytes) from %s", hlen, + inet_ntoa(from->sin_addr)); + return; + } + + memcpy(&ip, buf, sizeof(struct ip)); + + /* Check packet has enough data to carry a valid ICMP header */ recv_len = cc; if (cc < hlen + ICMP_MINLEN) { if (options & F_VERBOSE) @@ -1175,6 +1189,7 @@ #ifndef icmp_data icmp_data_raw = buf + hlen + offsetof(struct icmp, icmp_ip); #else + icmp_data_raw_len = cc - (hlen + offsetof(struct icmp, icmp_data)); icmp_data_raw = buf + hlen + offsetof(struct icmp, icmp_data); #endif @@ -1304,12 +1319,44 @@ * as root to avoid leaking information not normally * available to those not running as root. */ + + /* + * If we don't have enough bytes for a quoted IP header and an + * ICMP header then stop. + */ + if (icmp_data_raw_len < (sizeof(struct ip) + sizeof(struct icmp))) { + if (options & F_VERBOSE) + warnx("quoted packet too short (%d bytes) from %s", + oip_header_len, inet_ntoa(from->sin_addr)); + return; + } + 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); + + /* Reject IP packets with a short header */ + if (oip_header_len < sizeof(struct ip)) { + if (options & F_VERBOSE) + warnx("inner IHL too short (%d bytes) from %s", + oip_header_len, inet_ntoa(from->sin_addr)); + return; + } + + /* + * Check against the actual IHL length, to protect against + * quoated packets carrying IP options. + */ + if (icmp_data_raw_len < + (ssize_t)(oip_header_len + sizeof(struct icmp))) { + if (options & F_VERBOSE) + warnx("inner packet too short (%zd bytes) from %s", + icmp_data_raw_len, inet_ntoa(from->sin_addr)); + return; + } + + memcpy(&oip, icmp_data_raw, sizeof(struct ip)); oicmp_raw = icmp_data_raw + oip_header_len; - memcpy(&oicmp, oicmp_raw, offsetof(struct icmp, icmp_id) + - sizeof(oicmp.icmp_id)); + memcpy(&oicmp, oicmp_raw, sizeof(struct icmp)); if (((options & F_VERBOSE) && uid == 0) || (!(options & F_QUIET2) && diff --git a/sbin/ping/tests/Makefile b/sbin/ping/tests/Makefile --- a/sbin/ping/tests/Makefile +++ b/sbin/ping/tests/Makefile @@ -5,6 +5,9 @@ PACKAGE= tests +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer -O0 -g +#CFLAGS+=-O0 -g + ATF_TESTS_SH+= ping_test ${PACKAGE}FILES+= ping_c1_s56_t1.out ${PACKAGE}FILES+= ping_6_c1_s8_t1.out