xref: /xnu-8792.41.9/tests/icmp_fragmented_payload.c (revision 5c2921b07a2480ab43ec66f5b9e41cb872bc554f)
1 /*
2  * Copyright (c) 2021 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #define _IP_VHL 1
30 
31 #include <sys/fcntl.h>
32 #include <sys/socket.h>
33 #include <net/if.h>
34 #include <netinet/in.h>
35 #include <netinet/tcp.h>
36 #include <netinet/ip_icmp.h>
37 #include <arpa/inet.h>
38 
39 #include <darwintest.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 /*
44  * This test helps to reproduce a buffer overflow in the control plane:
45  * rdar://84355745
46  *
47  * The test allows to create a custom ICMP reply, and to send only a portion of it.
48  *
49  * To reproduce rdar://84355745, the test creates an ICMP "host unreachable" packet
50  * that contains a TCP header, and sends the first 28 bytes of the ICMP payload
51  * (48 including the outer IP header).
52  *
53  *  +-----+-----+-----+-----+
54  *  | IP  | ICMP| IP  | TCP |
55  *  +-----+-----+-----+-----+
56  *
57  *  <---------------->
58  *     sent payload
59  *
60  * This allows us to ensure that the parsing of the (potentially truncated) TCP
61  * headers is done in a secure way.
62  */
63 static void
init_sin_address(struct sockaddr_in * sin)64 init_sin_address(struct sockaddr_in *sin)
65 {
66 	memset(sin, 0, sizeof(struct sockaddr_in));
67 	sin->sin_len = sizeof(struct sockaddr_in);
68 	sin->sin_family = AF_INET;
69 }
70 
71 static uint16_t
checksum_buffer(uint16_t * buf,size_t len)72 checksum_buffer(uint16_t *buf, size_t len)
73 {
74 	unsigned long sum = 0;
75 	while (len > 1) {
76 		sum += *buf++;
77 		len -= 2;
78 		if (sum & 0x80000000) {
79 			sum = (sum >> 16) + (sum & 0xFFF);
80 		}
81 	}
82 	if (len == 1) {
83 		sum += ((unsigned long)(*(uint8_t*)buf) << 8);
84 	}
85 	while (sum >> 16) {
86 		sum = (sum >> 16) + (sum & 0xFFFF);
87 	}
88 
89 	return (uint16_t)~sum;
90 }
91 
92 #define MAXICMPBUFLEN 128
93 typedef struct icmp4_pcb {
94 	int             fd;
95 	int             id;
96 	int             seq;
97 	int             err;
98 	int             syserr;
99 	size_t          txlen;
100 	uint16_t        icmp_hdr_len;
101 	struct icmp    *icmp_hdr;
102 	uint16_t        inner_ip_hdr_len;
103 	struct ip      *inner_ip_hdr;
104 	uint16_t        inner_tcp_hdr_len;
105 	struct tcphdr  *inner_tcp_hdr;
106 	struct in_addr  in4addr_local;
107 	struct in_addr  in4addr_remote;
108 	uint64_t        buf[MAXICMPBUFLEN / 8];
109 } icmp4_pcb, *icmp4_pcb_t;
110 
111 static void
icmp4_pcb_print(icmp4_pcb_t pcb)112 icmp4_pcb_print(icmp4_pcb_t pcb)
113 {
114 	if (pcb == NULL) {
115 		fprintf(stdout, "icmp pcb: null");
116 		return;
117 	}
118 
119 	fprintf(stdout, "icmp pcb: \n"
120 	    "  fd=%d\n"
121 	    "  id=%d\n"
122 	    "  seq=%d\n"
123 	    "  err=%d\n"
124 	    "  syserr=%d\n"
125 	    "  txlen=%lu\n"
126 	    "  ICMP:\n"
127 	    "    len=%hu\n"
128 	    "    type=%d\n"
129 	    "    code=%d\n"
130 	    "    cksum=%hu\n"
131 	    "    icmp_id=%hu\n"
132 	    "    icmp_seq=%hu\n"
133 	    "    IP:\n"
134 	    "      len=%hu\n"
135 	    "      hl=%hu\n"
136 	    "      cksum=%hu\n"
137 	    "      TCP:\n"
138 	    "        len=%hu\n"
139 	    "        sport=%hu [%hu]\n"
140 	    "        dport=%hu [%hu]\n"
141 	    "        cksum=%hu\n",
142 	    pcb->id,
143 	    pcb->id,
144 	    pcb->seq,
145 	    pcb->err,
146 	    pcb->syserr,
147 	    pcb->txlen,
148 	    pcb->icmp_hdr_len,
149 	    (uint16_t)(pcb->icmp_hdr == NULL ? -1 : pcb->icmp_hdr->icmp_type),
150 	    (uint16_t)(pcb->icmp_hdr == NULL ? -1 : pcb->icmp_hdr->icmp_code),
151 	    (uint16_t)(pcb->icmp_hdr == NULL ? -1 : pcb->icmp_hdr->icmp_cksum),
152 	    (uint16_t)(pcb->icmp_hdr == NULL ? -1 : pcb->icmp_hdr->icmp_id),
153 	    (uint16_t)(pcb->icmp_hdr == NULL ? -1 : pcb->icmp_hdr->icmp_seq),
154 	    pcb->inner_ip_hdr_len,
155 	    (uint16_t)(pcb->inner_ip_hdr == NULL ? -1 : IP_VHL_HL(pcb->inner_ip_hdr->ip_vhl) << 2),
156 	    (uint16_t)(pcb->inner_ip_hdr == NULL ? -1 : pcb->inner_ip_hdr->ip_sum),
157 	    pcb->inner_tcp_hdr_len,
158 	    (uint16_t)(pcb->inner_tcp_hdr == NULL ? -1 : pcb->inner_tcp_hdr->th_sport),
159 	    (uint16_t)(pcb->inner_tcp_hdr == NULL ? -1 : ntohs(pcb->inner_tcp_hdr->th_sport)),
160 	    (uint16_t)(pcb->inner_tcp_hdr == NULL ? -1 : pcb->inner_tcp_hdr->th_dport),
161 	    (uint16_t)(pcb->inner_tcp_hdr == NULL ? -1 : ntohs(pcb->inner_tcp_hdr->th_dport)),
162 	    (uint16_t)(pcb->inner_tcp_hdr == NULL ? -1 : pcb->inner_tcp_hdr->th_sum));
163 }
164 
165 static void
icmp4_pcb_init(icmp4_pcb_t pcb)166 icmp4_pcb_init(icmp4_pcb_t pcb)
167 {
168 	memset(pcb, 0, sizeof(struct icmp4_pcb));
169 }
170 
171 static void
icmp4_pcb_close(icmp4_pcb_t pcb)172 icmp4_pcb_close(icmp4_pcb_t pcb)
173 {
174 	if (pcb->fd != -1) {
175 		close(pcb->fd);
176 		pcb->fd = -1;
177 	}
178 }
179 
180 static int
icmp4_pcb_open(icmp4_pcb_t pcb,struct in_addr * local,struct in_addr * remote)181 icmp4_pcb_open(icmp4_pcb_t pcb, struct in_addr *local, struct in_addr *remote)
182 {
183 	pcb->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
184 	if (pcb->fd == -1) {
185 		pcb->syserr = errno;
186 		pcb->err = -1;
187 		goto out;
188 	}
189 	int on = 1;
190 	if (setsockopt(pcb->fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)) == -1) {
191 		pcb->syserr = errno;
192 		close(pcb->fd);
193 		pcb->err = -2;
194 		goto out;
195 	}
196 
197 	struct sockaddr_in sin;
198 	memset(&sin, 0, sizeof(struct sockaddr_in));
199 	sin.sin_len = sizeof(struct sockaddr_in);
200 	sin.sin_family = AF_INET;
201 	memcpy(&sin.sin_addr, local, sizeof(struct in_addr));
202 
203 	if (bind(pcb->fd, (struct sockaddr*)&sin, sin.sin_len) == -1) {
204 		pcb->syserr = errno;
205 		pcb->err = -3;
206 		goto out;
207 	}
208 	memcpy(&(pcb->in4addr_local), local, sizeof(struct in_addr));
209 
210 	memcpy(&sin.sin_addr, remote, sizeof(struct in_addr));
211 	if (connect(pcb->fd, (struct sockaddr*)&sin, sin.sin_len) == -1) {
212 		pcb->syserr = errno;
213 		pcb->err = -4;
214 		goto out;
215 	}
216 	memcpy(&(pcb->in4addr_remote), remote, sizeof(struct in_addr));
217 
218 out:
219 	if (pcb->err != 0) {
220 		icmp4_pcb_close(pcb);
221 	}
222 	return pcb->err;
223 }
224 
225 static size_t
icmp4_pcb_get_payload_len(icmp4_pcb_t pcb)226 icmp4_pcb_get_payload_len(icmp4_pcb_t pcb)
227 {
228 	return pcb->icmp_hdr_len + pcb->inner_ip_hdr_len + pcb->inner_ip_hdr_len;
229 }
230 
231 static size_t
icmp4_pcb_set_payload(icmp4_pcb_t pcb,struct icmp * icmp_in,struct ip * ip_in,struct tcphdr * tcp_in)232 icmp4_pcb_set_payload(icmp4_pcb_t pcb, struct icmp *icmp_in, struct ip *ip_in, struct tcphdr *tcp_in)
233 {
234 	uint8_t *ptr = (uint8_t*)pcb->buf;
235 	pcb->icmp_hdr_len      = ICMP_MINLEN;
236 	pcb->inner_ip_hdr_len  = (uint16_t)(IP_VHL_HL(ip_in->ip_vhl) << 2);
237 	pcb->inner_tcp_hdr_len = sizeof(struct tcphdr);
238 
239 	pcb->inner_tcp_hdr           = (struct tcphdr*)(ptr + pcb->icmp_hdr_len + pcb->inner_ip_hdr_len);
240 	pcb->inner_tcp_hdr->th_sport = htons(tcp_in->th_sport);
241 	pcb->inner_tcp_hdr->th_dport = htons(tcp_in->th_dport);
242 	pcb->inner_tcp_hdr->th_seq   = htonl(tcp_in->th_seq);
243 	pcb->inner_tcp_hdr->th_ack   = htonl(tcp_in->th_ack);
244 	pcb->inner_tcp_hdr->th_flags = tcp_in->th_flags;
245 	pcb->inner_tcp_hdr->th_sum   = 0;
246 	pcb->inner_tcp_hdr->th_sum   = checksum_buffer((uint16_t*)pcb->inner_tcp_hdr, pcb->inner_tcp_hdr_len);
247 
248 	pcb->inner_ip_hdr            = (struct ip*)(ptr + pcb->icmp_hdr_len);
249 	pcb->inner_ip_hdr->ip_vhl    = ip_in->ip_vhl;
250 	pcb->inner_ip_hdr->ip_tos    = ip_in->ip_tos;
251 	pcb->inner_ip_hdr->ip_len    = pcb->inner_tcp_hdr_len + pcb->inner_ip_hdr_len;
252 	pcb->inner_ip_hdr->ip_id     = 1;
253 	pcb->inner_ip_hdr->ip_off    = 0;
254 	pcb->inner_ip_hdr->ip_ttl    = 64;
255 	pcb->inner_ip_hdr->ip_p      = IPPROTO_TCP;
256 	pcb->inner_ip_hdr->ip_sum    = 0;
257 	memcpy(&(pcb->inner_ip_hdr->ip_src), &(pcb->in4addr_local), sizeof(struct in_addr));
258 	memcpy(&(pcb->inner_ip_hdr->ip_dst), &(pcb->in4addr_remote), sizeof(struct in_addr));
259 	pcb->inner_ip_hdr->ip_sum    = checksum_buffer((uint16_t*)pcb->inner_ip_hdr, pcb->inner_ip_hdr_len);
260 
261 	pcb->icmp_hdr = (struct icmp*)pcb->buf;
262 
263 	pcb->icmp_hdr->icmp_type = icmp_in->icmp_type;
264 	pcb->icmp_hdr->icmp_code = icmp_in->icmp_code;
265 	pcb->icmp_hdr->icmp_cksum = 0;
266 	pcb->icmp_hdr->icmp_id = htons(pcb->id++);
267 	pcb->icmp_hdr->icmp_seq = htons(pcb->seq++);
268 	pcb->icmp_hdr->icmp_cksum = checksum_buffer((uint16_t*)pcb->icmp_hdr, sizeof(struct icmp));
269 
270 	return icmp4_pcb_get_payload_len(pcb);
271 }
272 
273 static int
icmp4_pcb_send_unreach(icmp4_pcb_t pcb,size_t maxlen)274 icmp4_pcb_send_unreach(icmp4_pcb_t pcb, size_t maxlen)
275 {
276 	size_t out_len = icmp4_pcb_get_payload_len(pcb);
277 	if (maxlen < out_len) {
278 		out_len = maxlen;
279 	}
280 
281 	fprintf(stderr, "Going to send %lu bytes of ICMP packet\n", out_len);
282 	ssize_t len = send(pcb->fd, pcb->buf, out_len, 0);
283 
284 	if (len < 0 || (size_t)len != out_len) {
285 		pcb->err = -6;
286 		pcb->syserr = errno;
287 	} else {
288 		pcb->err = 0;
289 	}
290 	return pcb->err;
291 }
292 
293 static void
icmp4_pcb_assert_payload_correct(icmp4_pcb_t pcb,size_t maxlen)294 icmp4_pcb_assert_payload_correct(icmp4_pcb_t pcb, size_t maxlen)
295 {
296 	if (pcb == NULL) {
297 		return;
298 	}
299 
300 	T_ASSERT_NE(pcb->inner_ip_hdr, NULL, "IP hdr not set");
301 
302 	int icmplen = icmp4_pcb_get_payload_len(pcb);
303 	T_ASSERT_LE(ICMP_MINLEN, icmplen, "ICMP payload smaller than minimal ICMP len");
304 
305 	T_ASSERT_GE(icmplen, ICMP_ADVLENMIN, "ICMP payload smaller than minimal advertised ICMP len");
306 
307 	// validate icmplen < ICMP_ADVLEN(icp) (ip_icmp.c:567)
308 	int inner_ip_hdr_len = (IP_VHL_HL(pcb->inner_ip_hdr->ip_vhl) << 2);
309 	int icmp_advlen = 8 + inner_ip_hdr_len + 8;
310 	T_ASSERT_GE(icmplen, icmp_advlen, "ICMP payload smaller than advertised ICMP len");
311 
312 	// validate inner IP header length (ip_icmp.c:568)
313 	T_ASSERT_GE(inner_ip_hdr_len, sizeof(struct ip), "IP payload smaller than IP header length");
314 
315 	// validate that the TCP header is outside of maxlen
316 	size_t tcp_hdr_offset = (size_t)((uint8_t*)(pcb->inner_tcp_hdr) - (uint8_t*)(pcb->icmp_hdr));
317 	fprintf(stdout, "tcp_hdr_offset: %lu, maxlen: %lu\n", tcp_hdr_offset, maxlen);
318 	T_ASSERT_LE(maxlen, tcp_hdr_offset, "TCP header within maxlen");
319 }
320 
321 T_DECL(icmp_send_malformed_packet_1, "ICMP packet with malformed TCP header")
322 {
323 	struct sockaddr_in sin = {};
324 
325 	init_sin_address(&sin);
326 	T_ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr), 1, NULL);
327 
328 	icmp4_pcb pcb;
329 	icmp4_pcb_init(&pcb);
330 
331 	T_ASSERT_EQ(icmp4_pcb_open(&pcb, &sin.sin_addr, &sin.sin_addr), 0, NULL);
332 
333 	struct icmp icmp_payload = {
334 		.icmp_type = ICMP_UNREACH,
335 		.icmp_code = ICMP_UNREACH_HOST,
336 	};
337 	struct ip ip_payload = {
338 		.ip_vhl = 0x45,
339 		.ip_tos = 0,
340 		.ip_len = sizeof(struct ip) + sizeof(struct tcphdr),
341 		.ip_id  = 1,
342 		.ip_off = 0,
343 		.ip_ttl = 64,
344 	};
345 	struct tcphdr tcp_payload = {
346 		.th_sport = 1234,
347 		.th_dport = 80,
348 		.th_seq = 1024,
349 		.th_ack = 4096,
350 		.th_flags = TH_FLAGS,
351 	};
352 
353 	T_ASSERT_GT(icmp4_pcb_set_payload(&pcb, &icmp_payload, &ip_payload, &tcp_payload), 0L, NULL);
354 
355 	icmp4_pcb_print(&pcb);
356 
357 	size_t sendlen = 28;
358 	icmp4_pcb_assert_payload_correct(&pcb, sendlen);
359 
360 	T_ASSERT_EQ(icmp4_pcb_send_unreach(&pcb, sendlen), 0, NULL);
361 
362 	icmp4_pcb_close(&pcb);
363 }
364