Page MenuHomeFreeBSD

D37210.id112417.diff
No OneTemporary

D37210.id112417.diff

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_ip));
icmp_data_raw = buf + hlen + offsetof(struct icmp, icmp_data);
#endif
@@ -1306,10 +1321,27 @@
*/
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)
+ warn("inner IHL too short (%d bytes) from %s",
+ oip_header_len, inet_ntoa(from->sin_addr));
+ return;
+ }
+
+ if (icmp_data_raw_len <
+ (ssize_t)(oip_header_len + sizeof(struct icmp))) {
+ if (options & F_VERBOSE)
+ warn("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
@@ -6,9 +6,13 @@
PACKAGE= tests
ATF_TESTS_SH+= ping_test
+# Exclusive because each injection test case uses the same IP addresses
+TEST_METADATA.ping_test+= is_exclusive="true"
+
${PACKAGE}FILES+= ping_c1_s56_t1.out
${PACKAGE}FILES+= ping_6_c1_s8_t1.out
${PACKAGE}FILES+= ping_c1_s56_t1_S127.out
${PACKAGE}FILES+= ping_c1_s8_t1_S1.out
+${PACKAGE}FILES+= injection.py
.include <bsd.test.mk>
diff --git a/sbin/ping/tests/injection.py b/sbin/ping/tests/injection.py
new file mode 100644
--- /dev/null
+++ b/sbin/ping/tests/injection.py
@@ -0,0 +1,80 @@
+#! /usr/bin/env python3
+# Used to inject various malformed packets
+
+import errno
+import subprocess
+import sys
+
+from scapy.all import Ether, IP, ICMP, IPOption
+import scapy.layers.all
+from scapy.layers.inet import ICMPEcho_am
+from scapy.layers.tuntap import TunTapInterface
+
+SRC_ADDR = "192.0.2.14"
+DST_ADDR = "192.0.2.15"
+
+mode = sys.argv[1]
+ip = None
+
+# fill opts with nop (0x01)
+opts = b''
+for x in range(40):
+ opts += b'\x01'
+
+
+# Create and configure a tun interface with an RFC5737 nonrouteable address
+create_proc = subprocess.run(
+ args=["ifconfig", "tun", "create"],
+ capture_output=True,
+ check=True,
+ text=True)
+iface = create_proc.stdout.strip()
+tun = TunTapInterface(iface)
+with open("tun.txt", "w") as f:
+ f.write(iface)
+subprocess.run(["ifconfig", tun.iface, "up"])
+subprocess.run(["ifconfig", tun.iface, SRC_ADDR, DST_ADDR])
+
+ping = subprocess.Popen(
+ args=["/sbin/ping", "-v", "-c1", "-t1", DST_ADDR],
+ text=True
+)
+# Wait for /sbin/ping to ping us
+echo_req = tun.recv()
+
+# Construct the response packet
+if mode == "opts":
+ # Sending reply with IP options
+ echo_reply = IP(
+ dst=SRC_ADDR,
+ src=DST_ADDR,
+ options=IPOption(opts)
+ )/ICMP(type=0, code=0, id=echo_req.payload.id)/echo_req.payload.payload
+elif mode == "pip":
+ # packet in packet (inner has options)
+
+ inner = IP(
+ dst=SRC_ADDR,
+ src=DST_ADDR,
+ options=IPOption(opts)
+ )/ICMP(type=0, code=0, id=echo_req.payload.id)/echo_req.payload.payload
+ outer = IP(
+ dst=SRC_ADDR,
+ src=DST_ADDR
+ )/ICMP(type=3, code=1) # host unreach
+
+ echo_reply = outer/inner
+elif mode == "reply":
+ # Sending normal echo reply
+ echo_reply = IP(
+ dst=SRC_ADDR,
+ src=DST_ADDR,
+ )/ICMP(type=0, code=0, id=echo_req.payload.id)/echo_req.payload.payload
+else:
+ print("unknown mode {}".format(mode))
+ exit(1)
+
+tun.send(echo_reply)
+outs, errs = ping.communicate()
+
+sys.exit(ping.returncode)
diff --git a/sbin/ping/tests/ping_test.sh b/sbin/ping/tests/ping_test.sh
--- a/sbin/ping/tests/ping_test.sh
+++ b/sbin/ping/tests/ping_test.sh
@@ -125,6 +125,47 @@
atf_check -s exit:1 -e ignore ping6 -4 -6
}
+atf_test_case "inject_opts" "cleanup"
+inject_opts_head() {
+ atf_set "descr" "Inejct an ECHO REPLY with IP options"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "scapy"
+}
+inject_opts_body() {
+ atf_check -s exit:0 -o match:"wrong total length" -o match:"NOP" python3 $(atf_get_srcdir)/injection.py opts
+}
+inject_opts_cleanup() {
+ ifconfig `cat tun.txt` destroy
+}
+
+atf_test_case "inject_pip" "cleanup"
+inject_pip_head() {
+ atf_set "descr" "Inject an ICMP error with a quoted packet with IP options"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "scapy"
+}
+inject_pip_body() {
+ atf_check -s exit:2 -o match:"Destination Host Unreachable" python3 $(atf_get_srcdir)/injection.py pip
+}
+inject_pip_cleanup() {
+ ifconfig `cat tun.txt` destroy
+}
+
+# This is redundant with the ping_ tests, but it serves to ensure that scapy.py
+# is working correctly.
+atf_test_case "inject_reply" "cleanup"
+inject_reply_head() {
+ atf_set "descr" "Basic ping test with packet injection"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "scapy"
+}
+inject_reply_body() {
+ atf_check -s exit:0 -o match:"1 packets transmitted, 1 packets received" python3 $(atf_get_srcdir)/injection.py reply
+}
+inject_reply_cleanup() {
+ ifconfig `cat tun.txt` destroy
+}
+
atf_init_test_cases() {
atf_add_test_case ping_c1_s56_t1
@@ -136,6 +177,9 @@
atf_add_test_case ping6_c1t4
atf_add_test_case ping_46
atf_add_test_case ping6_46
+ atf_add_test_case inject_opts
+ atf_add_test_case inject_pip
+ atf_add_test_case inject_reply
}
check_ping_statistics() {

File Metadata

Mime Type
text/plain
Expires
Thu, Mar 5, 2:28 PM (14 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29269508
Default Alt Text
D37210.id112417.diff (7 KB)

Event Timeline