xref: /netbsd/usr.sbin/inetd/ratelimit.c (revision 0d9085f5)
1 /*	$NetBSD: ratelimit.c,v 1.2 2021/10/12 22:51:28 rillig Exp $	*/
2 
3 /*-
4  * Copyright (c) 2021 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: ratelimit.c,v 1.2 2021/10/12 22:51:28 rillig Exp $");
33 
34 #include <sys/queue.h>
35 
36 #include <arpa/inet.h>
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <syslog.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <errno.h>
44 #include <stddef.h>
45 
46 #include "inetd.h"
47 
48 union addr {
49 	struct in_addr	ipv4_addr;
50 	/* ensure aligned for comparison in rl_ipv6_eq (already is on 64-bit) */
51 #ifdef INET6
52 	struct in6_addr	ipv6_addr __attribute__((aligned(16)));
53 #endif
54 	char		other_addr[NI_MAXHOST];
55 };
56 
57 static void	rl_reset(struct servtab *, time_t);
58 static time_t	rl_time(void);
59 static void	rl_get_name(struct servtab *, int, union addr *);
60 static void	rl_drop_connection(struct servtab *, int);
61 static struct rl_ip_node	*rl_add(struct servtab *, union addr *);
62 static struct rl_ip_node	*rl_try_get_ip(struct servtab *, union addr *);
63 static bool	rl_ip_eq(struct servtab *, union addr *, struct rl_ip_node *);
64 #ifdef INET6
65 static bool	rl_ipv6_eq(struct in6_addr *, struct in6_addr *);
66 #endif
67 #ifdef DEBUG_ENABLE
68 static void	rl_print_found_node(struct servtab *, struct rl_ip_node *);
69 #endif
70 static void	rl_log_address_exceed(struct servtab *, struct rl_ip_node *);
71 static const char	*rl_node_tostring(struct servtab *, struct rl_ip_node *, char[NI_MAXHOST]);
72 static bool	rl_process_service_max(struct servtab *, int, time_t *);
73 static bool	rl_process_ip_max(struct servtab *, int, time_t *);
74 
75 /* Return 0 on allow, -1 if connection should be blocked */
76 int
rl_process(struct servtab * sep,int ctrl)77 rl_process(struct servtab *sep, int ctrl)
78 {
79 	time_t now = -1;
80 
81 	DPRINTF(SERV_FMT ": processing rate-limiting",
82 	    SERV_PARAMS(sep));
83 	DPRINTF(SERV_FMT ": se_service_max "
84 	    "%zu and se_count %zu", SERV_PARAMS(sep),
85 	    sep->se_service_max, sep->se_count);
86 
87 	if (sep->se_count == 0) {
88 		now = rl_time();
89 		sep->se_time = now;
90 	}
91 
92 	if (!rl_process_service_max(sep, ctrl, &now)
93 	    || !rl_process_ip_max(sep, ctrl, &now)) {
94 		return -1;
95 	}
96 
97 	DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep));
98 
99 	/* se_count is only incremented if rl_process will return 0 */
100 	sep->se_count++;
101 	return 0;
102 }
103 
104 /*
105  * Get the identifier for the remote peer based on sep->se_socktype and
106  * sep->se_family
107  */
108 static void
rl_get_name(struct servtab * sep,int ctrl,union addr * out)109 rl_get_name(struct servtab *sep, int ctrl, union addr *out)
110 {
111 	union {
112 		struct sockaddr_storage ss;
113 		struct sockaddr sa;
114 		struct sockaddr_in sin;
115 		struct sockaddr_in6 sin6;
116 	} addr;
117 
118 	/* Get the sockaddr of socket ctrl */
119 	switch (sep->se_socktype) {
120 	case SOCK_STREAM: {
121 		socklen_t len = sizeof(struct sockaddr_storage);
122 		if (getpeername(ctrl, &addr.sa, &len) == -1) {
123 			/* error, log it and skip ip rate limiting */
124 			syslog(LOG_ERR,
125 			    SERV_FMT " failed to get peer name of the "
126 			    "connection", SERV_PARAMS(sep));
127 			exit(EXIT_FAILURE);
128 		}
129 		break;
130 	}
131 	case SOCK_DGRAM: {
132 		struct msghdr header = {
133 			.msg_name = &addr.sa,
134 			.msg_namelen = sizeof(struct sockaddr_storage),
135 			/* scatter/gather and control info is null */
136 		};
137 		ssize_t count;
138 
139 		/* Peek so service can still get the packet */
140 		count = recvmsg(ctrl, &header, MSG_PEEK);
141 		if (count == -1) {
142 			syslog(LOG_ERR,
143 			    "failed to get dgram source address: %s; exiting",
144 			    strerror(errno));
145 			exit(EXIT_FAILURE);
146 		}
147 		break;
148 	}
149 	default:
150 		DPRINTF(SERV_FMT ": ip_max rate limiting not supported for "
151 		    "socktype", SERV_PARAMS(sep));
152 		syslog(LOG_ERR, SERV_FMT
153 		    ": ip_max rate limiting not supported for socktype",
154 		    SERV_PARAMS(sep));
155 		exit(EXIT_FAILURE);
156 	}
157 
158 	/* Convert addr to to rate limiting address */
159 	switch (sep->se_family) {
160 		case AF_INET:
161 			out->ipv4_addr = addr.sin.sin_addr;
162 			break;
163 #ifdef INET6
164 		case AF_INET6:
165 			out->ipv6_addr = addr.sin6.sin6_addr;
166 			break;
167 #endif
168 		default: {
169 			int res = getnameinfo(&addr.sa,
170 			    (socklen_t)addr.sa.sa_len,
171 			    out->other_addr, NI_MAXHOST,
172 			    NULL, 0,
173 			    NI_NUMERICHOST
174 			);
175 			if (res != 0) {
176 				syslog(LOG_ERR,
177 				    SERV_FMT ": failed to get name info of "
178 				    "the incoming connection: %s; exiting",
179 				    SERV_PARAMS(sep), gai_strerror(res));
180 				exit(EXIT_FAILURE);
181 			}
182 			break;
183 		}
184 	}
185 }
186 
187 static void
rl_drop_connection(struct servtab * sep,int ctrl)188 rl_drop_connection(struct servtab *sep, int ctrl)
189 {
190 
191 	if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
192 		/*
193 		 * If the fd isn't a listen socket,
194 		 * close the individual connection too.
195 		 */
196 		close(ctrl);
197 		return;
198 	}
199 	if (sep->se_socktype != SOCK_DGRAM) {
200 		return;
201 	}
202 	/*
203 	 * Drop the single datagram the service would have
204 	 * consumed if nowait. If this is a wait service, this
205 	 * will consume 1 datagram, and further received packets
206 	 * will be removed in the same way.
207 	 */
208 	struct msghdr header = {
209 		/* All fields null, just consume one message */
210 	};
211 	ssize_t count;
212 
213 	count = recvmsg(ctrl, &header, 0);
214 	if (count == -1) {
215 		syslog(LOG_ERR,
216 		    SERV_FMT ": failed to consume nowait dgram: %s",
217 		    SERV_PARAMS(sep), strerror(errno));
218 		exit(EXIT_FAILURE);
219 	}
220 	DPRINTF(SERV_FMT ": dropped dgram message",
221 	    SERV_PARAMS(sep));
222 }
223 
224 static time_t
rl_time(void)225 rl_time(void)
226 {
227 	struct timespec ts;
228 	if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
229 		syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; "
230 		    "exiting", strerror(errno));
231 		/* Exit inetd if rate limiting fails */
232 		exit(EXIT_FAILURE);
233 	}
234 	return ts.tv_sec;
235 }
236 
237 /* Add addr to IP tracking or return NULL if malloc fails */
238 static struct rl_ip_node *
rl_add(struct servtab * sep,union addr * addr)239 rl_add(struct servtab *sep, union addr *addr)
240 {
241 
242 	struct rl_ip_node *node;
243 	size_t node_size, bufsize;
244 #ifdef DEBUG_ENABLE
245 	char buffer[NI_MAXHOST];
246 #endif
247 
248 	switch(sep->se_family) {
249 	case AF_INET:
250 		/* ip_node to end of IPv4 address */
251 		node_size = offsetof(struct rl_ip_node, ipv4_addr)
252 		    + sizeof(struct in_addr);
253 		break;
254 	case AF_INET6:
255 		/* ip_node to end of IPv6 address */
256 		node_size = offsetof(struct rl_ip_node, ipv6_addr)
257 		    + sizeof(struct in6_addr);
258 		break;
259 	default:
260 		/* ip_node to other_addr plus size of string + NULL */
261 		bufsize = strlen(addr->other_addr) + sizeof(char);
262 		node_size = offsetof(struct rl_ip_node, other_addr) + bufsize;
263 		break;
264 	}
265 
266 	node = malloc(node_size);
267 	if (node == NULL) {
268 		if (errno == ENOMEM) {
269 			return NULL;
270 		} else {
271 			syslog(LOG_ERR, "malloc failed unexpectedly: %s",
272 			    strerror(errno));
273 			exit(EXIT_FAILURE);
274 		}
275 	}
276 
277 	node->count = 0;
278 
279 	/* copy the data into the new allocation */
280 	switch(sep->se_family) {
281 	case AF_INET:
282 		node->ipv4_addr = addr->ipv4_addr;
283 		break;
284 	case AF_INET6:
285 		/* Hopefully this is inlined, means the same thing as memcpy */
286 		__builtin_memcpy(&node->ipv6_addr, &addr->ipv6_addr,
287 		    sizeof(struct in6_addr));
288 		break;
289 	default:
290 		strlcpy(node->other_addr, addr->other_addr, bufsize);
291 		break;
292 	}
293 
294 	/* initializes 'entries' member to NULL automatically */
295 	SLIST_INSERT_HEAD(&sep->se_rl_ip_list, node, entries);
296 
297 	DPRINTF(SERV_FMT ": add '%s' to rate limit tracking (%zu byte record)",
298  	    SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer), node_size);
299 
300 	return node;
301 }
302 
303 static void
rl_reset(struct servtab * sep,time_t now)304 rl_reset(struct servtab *sep, time_t now)
305 {
306 	DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ",
307 	    SERV_PARAMS(sep), (intmax_t)(now - sep->se_time));
308 
309 	sep->se_count = 0;
310 	sep->se_time = now;
311 	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
312 		rl_clear_ip_list(sep);
313 	}
314 }
315 
316 void
rl_clear_ip_list(struct servtab * sep)317 rl_clear_ip_list(struct servtab *sep)
318 {
319 	while (!SLIST_EMPTY(&sep->se_rl_ip_list)) {
320 		struct rl_ip_node *node = SLIST_FIRST(&sep->se_rl_ip_list);
321 		SLIST_REMOVE_HEAD(&sep->se_rl_ip_list, entries);
322 		free(node);
323 	}
324 }
325 
326 /* Get the node associated with addr, or NULL */
327 static struct rl_ip_node *
rl_try_get_ip(struct servtab * sep,union addr * addr)328 rl_try_get_ip(struct servtab *sep, union addr *addr)
329 {
330 
331 	struct rl_ip_node *cur;
332 	SLIST_FOREACH(cur, &sep->se_rl_ip_list, entries) {
333 		if (rl_ip_eq(sep, addr, cur)) {
334 			return cur;
335 		}
336 	}
337 
338 	return NULL;
339 }
340 
341 /* Return true if passed service rate limiting checks, false if blocked */
342 static bool
rl_process_service_max(struct servtab * sep,int ctrl,time_t * now)343 rl_process_service_max(struct servtab *sep, int ctrl, time_t *now)
344 {
345 	if (sep->se_count >= sep->se_service_max) {
346 		if (*now == -1) {
347 			/* Only get the clock time if we didn't already */
348 			*now = rl_time();
349 		}
350 
351 		if (*now - sep->se_time > CNT_INTVL) {
352 			rl_reset(sep, *now);
353 		} else {
354 			syslog(LOG_ERR, SERV_FMT
355 			    ": max spawn rate (%zu in %ji seconds) "
356 			    "already met; closing for %ju seconds",
357 			    SERV_PARAMS(sep),
358 			    sep->se_service_max,
359 			    (intmax_t)CNT_INTVL,
360 			    (uintmax_t)RETRYTIME);
361 			DPRINTF(SERV_FMT
362 			    ": max spawn rate (%zu in %ji seconds) "
363 			    "already met; closing for %ju seconds",
364 			    SERV_PARAMS(sep),
365 			    sep->se_service_max,
366 			    (intmax_t)CNT_INTVL,
367 			    (uintmax_t)RETRYTIME);
368 
369 			rl_drop_connection(sep, ctrl);
370 
371 			/* Close the server for 10 minutes */
372 			close_sep(sep);
373 			if (!timingout) {
374 				timingout = true;
375 				alarm(RETRYTIME);
376 			}
377 
378 			return false;
379 		}
380 	}
381 	return true;
382 }
383 
384 /* Return true if passed IP rate limiting checks, false if blocked */
385 static bool
rl_process_ip_max(struct servtab * sep,int ctrl,time_t * now)386 rl_process_ip_max(struct servtab *sep, int ctrl, time_t *now) {
387 	if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
388 		struct rl_ip_node *node;
389 		union addr addr;
390 
391 		rl_get_name(sep, ctrl, &addr);
392 		node = rl_try_get_ip(sep, &addr);
393 		if (node == NULL) {
394 			node = rl_add(sep, &addr);
395 			if (node == NULL) {
396 				/* If rl_add can't allocate, reject request */
397 				DPRINTF("Cannot allocate rl_ip_node");
398 				return false;
399 			}
400 		}
401 #ifdef DEBUG_ENABLE
402 		else {
403 			/*
404 			 * in a separate function to prevent large stack
405 			 * frame
406 			 */
407 			rl_print_found_node(sep, node);
408 		}
409 #endif
410 
411 		DPRINTF(
412 		    SERV_FMT ": se_ip_max %zu and ip_count %zu",
413 		    SERV_PARAMS(sep), sep->se_ip_max, node->count);
414 
415 		if (node->count >= sep->se_ip_max) {
416 			if (*now == -1) {
417 				*now = rl_time();
418 			}
419 
420 			if (*now - sep->se_time > CNT_INTVL) {
421 				rl_reset(sep, *now);
422 				node = rl_add(sep, &addr);
423 				if (node == NULL) {
424 					DPRINTF("Cannot allocate rl_ip_node");
425 					return false;
426 				}
427 			} else {
428 				if (debug && node->count == sep->se_ip_max) {
429 					/*
430 					 * Only log first failed request to
431 					 * prevent DoS attack writing to system
432 					 * log
433 					 */
434 					rl_log_address_exceed(sep, node);
435 				} else {
436 					DPRINTF(SERV_FMT
437 					    ": service not started",
438 					    SERV_PARAMS(sep));
439 				}
440 
441 				rl_drop_connection(sep, ctrl);
442 				/*
443 				 * Increment so debug-syslog message will
444 				 * trigger only once
445 				 */
446 				if (node->count < SIZE_MAX) {
447 					node->count++;
448 				}
449 				return false;
450 			}
451 		}
452 		node->count++;
453 	}
454 	return true;
455 }
456 
457 static bool
rl_ip_eq(struct servtab * sep,union addr * addr,struct rl_ip_node * cur)458 rl_ip_eq(struct servtab *sep, union addr *addr, struct rl_ip_node *cur) {
459 	switch(sep->se_family) {
460 	case AF_INET:
461 		if (addr->ipv4_addr.s_addr == cur->ipv4_addr.s_addr) {
462 			return true;
463 		}
464 		break;
465 #ifdef INET6
466 	case AF_INET6:
467 		if (rl_ipv6_eq(&addr->ipv6_addr, &cur->ipv6_addr)) {
468 			return true;
469 		}
470 		break;
471 #endif
472 	default:
473 		if (strncmp(cur->other_addr, addr->other_addr, NI_MAXHOST)
474 		    == 0) {
475 			return true;
476 		}
477 		break;
478 	}
479 	return false;
480 }
481 
482 #ifdef INET6
483 static bool
rl_ipv6_eq(struct in6_addr * a,struct in6_addr * b)484 rl_ipv6_eq(struct in6_addr *a, struct in6_addr *b)
485 {
486 #if UINTMAX_MAX >= UINT64_MAX
487 	{ /* requires 8 byte aligned structs */
488 		uint64_t *ap = (uint64_t *)a->s6_addr;
489 		uint64_t *bp = (uint64_t *)b->s6_addr;
490 		return (ap[0] == bp[0]) & (ap[1] == bp[1]);
491 	}
492 #else
493 	{ /* requires 4 byte aligned structs */
494 		uint32_t *ap = (uint32_t *)a->s6_addr;
495 		uint32_t *bp = (uint32_t *)b->s6_addr;
496 		return ap[0] == bp[0] && ap[1] == bp[1] &&
497 			ap[2] == bp[2] && ap[3] == bp[3];
498 	}
499 #endif
500 }
501 #endif
502 
503 static const char *
rl_node_tostring(struct servtab * sep,struct rl_ip_node * node,char buffer[NI_MAXHOST])504 rl_node_tostring(struct servtab *sep, struct rl_ip_node *node,
505     char buffer[NI_MAXHOST])
506 {
507 	switch (sep->se_family) {
508 	case AF_INET:
509 #ifdef INET6
510 	case AF_INET6:
511 #endif
512 		/* ipv4_addr/ipv6_addr share same address */
513 		return inet_ntop(sep->se_family, (void*)&node->ipv4_addr,
514 		    (char*)buffer, NI_MAXHOST);
515 	default:
516 		return (char *)&node->other_addr;
517 	}
518 }
519 
520 #ifdef DEBUG_ENABLE
521 /* Separate function due to large buffer size */
522 static void
rl_print_found_node(struct servtab * sep,struct rl_ip_node * node)523 rl_print_found_node(struct servtab *sep, struct rl_ip_node *node)
524 {
525 	char buffer[NI_MAXHOST];
526 	DPRINTF(SERV_FMT ": found record for address '%s'",
527 	    SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer));
528 }
529 #endif
530 
531 /* Separate function due to large buffer sie */
532 static void
rl_log_address_exceed(struct servtab * sep,struct rl_ip_node * node)533 rl_log_address_exceed(struct servtab *sep, struct rl_ip_node *node)
534 {
535 	char buffer[NI_MAXHOST];
536 	const char * name = rl_node_tostring(sep, node, buffer);
537 	syslog(LOG_ERR, SERV_FMT
538 	    ": max ip spawn rate (%zu in "
539 	    "%ji seconds) for "
540 	    "'%." TOSTRING(NI_MAXHOST) "s' "
541 	    "already met; service not started",
542 	    SERV_PARAMS(sep),
543 	    sep->se_ip_max,
544 	    (intmax_t)CNT_INTVL,
545 	    name);
546 	DPRINTF(SERV_FMT
547 	    ": max ip spawn rate (%zu in "
548 	    "%ji seconds) for "
549 	    "'%." TOSTRING(NI_MAXHOST) "s' "
550 	    "already met; service not started",
551 	    SERV_PARAMS(sep),
552 	    sep->se_ip_max,
553 	    (intmax_t)CNT_INTVL,
554 	    name);
555 }
556