xref: /netbsd/external/mpl/bind/dist/lib/dns/acl.c (revision c0b5d9fb)
1 /*	$NetBSD: acl.c,v 1.8 2022/09/23 12:15:29 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 
18 #include <inttypes.h>
19 #include <stdbool.h>
20 
21 #include <isc/mem.h>
22 #include <isc/once.h>
23 #include <isc/string.h>
24 #include <isc/util.h>
25 
26 #include <dns/acl.h>
27 #include <dns/iptable.h>
28 
29 /*
30  * Create a new ACL, including an IP table and an array with room
31  * for 'n' ACL elements.  The elements are uninitialized and the
32  * length is 0.
33  */
34 isc_result_t
dns_acl_create(isc_mem_t * mctx,int n,dns_acl_t ** target)35 dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
36 	isc_result_t result;
37 	dns_acl_t *acl;
38 
39 	/*
40 	 * Work around silly limitation of isc_mem_get().
41 	 */
42 	if (n == 0) {
43 		n = 1;
44 	}
45 
46 	acl = isc_mem_get(mctx, sizeof(*acl));
47 
48 	acl->mctx = NULL;
49 	isc_mem_attach(mctx, &acl->mctx);
50 
51 	acl->name = NULL;
52 
53 	isc_refcount_init(&acl->refcount, 1);
54 
55 	result = dns_iptable_create(mctx, &acl->iptable);
56 	if (result != ISC_R_SUCCESS) {
57 		isc_mem_put(mctx, acl, sizeof(*acl));
58 		return (result);
59 	}
60 
61 	acl->elements = NULL;
62 	acl->alloc = 0;
63 	acl->length = 0;
64 	acl->has_negatives = false;
65 
66 	ISC_LINK_INIT(acl, nextincache);
67 	/*
68 	 * Must set magic early because we use dns_acl_detach() to clean up.
69 	 */
70 	acl->magic = DNS_ACL_MAGIC;
71 
72 	acl->elements = isc_mem_get(mctx, n * sizeof(dns_aclelement_t));
73 	acl->alloc = n;
74 	memset(acl->elements, 0, n * sizeof(dns_aclelement_t));
75 	*target = acl;
76 	return (ISC_R_SUCCESS);
77 }
78 
79 /*
80  * Create a new ACL and initialize it with the value "any" or "none",
81  * depending on the value of the "neg" parameter.
82  * "any" is a positive iptable entry with bit length 0.
83  * "none" is the same as "!any".
84  */
85 static isc_result_t
dns_acl_anyornone(isc_mem_t * mctx,bool neg,dns_acl_t ** target)86 dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) {
87 	isc_result_t result;
88 	dns_acl_t *acl = NULL;
89 
90 	result = dns_acl_create(mctx, 0, &acl);
91 	if (result != ISC_R_SUCCESS) {
92 		return (result);
93 	}
94 
95 	result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg);
96 	if (result != ISC_R_SUCCESS) {
97 		dns_acl_detach(&acl);
98 		return (result);
99 	}
100 
101 	*target = acl;
102 	return (result);
103 }
104 
105 /*
106  * Create a new ACL that matches everything.
107  */
108 isc_result_t
dns_acl_any(isc_mem_t * mctx,dns_acl_t ** target)109 dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
110 	return (dns_acl_anyornone(mctx, false, target));
111 }
112 
113 /*
114  * Create a new ACL that matches nothing.
115  */
116 isc_result_t
dns_acl_none(isc_mem_t * mctx,dns_acl_t ** target)117 dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
118 	return (dns_acl_anyornone(mctx, true, target));
119 }
120 
121 /*
122  * If pos is true, test whether acl is set to "{ any; }"
123  * If pos is false, test whether acl is set to "{ none; }"
124  */
125 static bool
dns_acl_isanyornone(dns_acl_t * acl,bool pos)126 dns_acl_isanyornone(dns_acl_t *acl, bool pos) {
127 	/* Should never happen but let's be safe */
128 	if (acl == NULL || acl->iptable == NULL ||
129 	    acl->iptable->radix == NULL || acl->iptable->radix->head == NULL ||
130 	    acl->iptable->radix->head->prefix == NULL)
131 	{
132 		return (false);
133 	}
134 
135 	if (acl->length != 0 || dns_acl_node_count(acl) != 1) {
136 		return (false);
137 	}
138 
139 	if (acl->iptable->radix->head->prefix->bitlen == 0 &&
140 	    acl->iptable->radix->head->data[0] != NULL &&
141 	    acl->iptable->radix->head->data[0] ==
142 		    acl->iptable->radix->head->data[1] &&
143 	    *(bool *)(acl->iptable->radix->head->data[0]) == pos)
144 	{
145 		return (true);
146 	}
147 
148 	return (false); /* All others */
149 }
150 
151 /*
152  * Test whether acl is set to "{ any; }"
153  */
154 bool
dns_acl_isany(dns_acl_t * acl)155 dns_acl_isany(dns_acl_t *acl) {
156 	return (dns_acl_isanyornone(acl, true));
157 }
158 
159 /*
160  * Test whether acl is set to "{ none; }"
161  */
162 bool
dns_acl_isnone(dns_acl_t * acl)163 dns_acl_isnone(dns_acl_t *acl) {
164 	return (dns_acl_isanyornone(acl, false));
165 }
166 
167 /*
168  * Determine whether a given address or signer matches a given ACL.
169  * For a match with a positive ACL element or iptable radix entry,
170  * return with a positive value in match; for a match with a negated ACL
171  * element or radix entry, return with a negative value in match.
172  */
173 
174 isc_result_t
dns_acl_match(const isc_netaddr_t * reqaddr,const dns_name_t * reqsigner,const dns_acl_t * acl,const dns_aclenv_t * env,int * match,const dns_aclelement_t ** matchelt)175 dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
176 	      const dns_acl_t *acl, const dns_aclenv_t *env, int *match,
177 	      const dns_aclelement_t **matchelt) {
178 	uint16_t bitlen;
179 	isc_prefix_t pfx;
180 	isc_radix_node_t *node = NULL;
181 	const isc_netaddr_t *addr = reqaddr;
182 	isc_netaddr_t v4addr;
183 	isc_result_t result;
184 	int match_num = -1;
185 	unsigned int i;
186 
187 	REQUIRE(reqaddr != NULL);
188 	REQUIRE(matchelt == NULL || *matchelt == NULL);
189 
190 	if (env != NULL && env->match_mapped && addr->family == AF_INET6 &&
191 	    IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
192 	{
193 		isc_netaddr_fromv4mapped(&v4addr, addr);
194 		addr = &v4addr;
195 	}
196 
197 	/* Always match with host addresses. */
198 	bitlen = (addr->family == AF_INET6) ? 128 : 32;
199 	NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
200 
201 	/* Assume no match. */
202 	*match = 0;
203 
204 	/* Search radix. */
205 	result = isc_radix_search(acl->iptable->radix, &node, &pfx);
206 
207 	/* Found a match. */
208 	if (result == ISC_R_SUCCESS && node != NULL) {
209 		int fam = ISC_RADIX_FAMILY(&pfx);
210 		match_num = node->node_num[fam];
211 		if (*(bool *)node->data[fam]) {
212 			*match = match_num;
213 		} else {
214 			*match = -match_num;
215 		}
216 	}
217 
218 	isc_refcount_destroy(&pfx.refcount);
219 
220 	/* Now search non-radix elements for a match with a lower node_num. */
221 	for (i = 0; i < acl->length; i++) {
222 		dns_aclelement_t *e = &acl->elements[i];
223 
224 		/* Already found a better match? */
225 		if (match_num != -1 && match_num < e->node_num) {
226 			break;
227 		}
228 
229 		if (dns_aclelement_match(reqaddr, reqsigner, e, env, matchelt))
230 		{
231 			if (match_num == -1 || e->node_num < match_num) {
232 				if (e->negative) {
233 					*match = -e->node_num;
234 				} else {
235 					*match = e->node_num;
236 				}
237 			}
238 			break;
239 		}
240 	}
241 
242 	return (ISC_R_SUCCESS);
243 }
244 
245 /*
246  * Merge the contents of one ACL into another.  Call dns_iptable_merge()
247  * for the IP tables, then concatenate the element arrays.
248  *
249  * If pos is set to false, then the nested ACL is to be negated.  This
250  * means reverse the sense of each *positive* element or IP table node,
251  * but leave negatives alone, so as to prevent a double-negative causing
252  * an unexpected positive match in the parent ACL.
253  */
254 isc_result_t
dns_acl_merge(dns_acl_t * dest,dns_acl_t * source,bool pos)255 dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos) {
256 	isc_result_t result;
257 	unsigned int newalloc, nelem, i;
258 	int max_node = 0, nodes;
259 
260 	/* Resize the element array if needed. */
261 	if (dest->length + source->length > dest->alloc) {
262 		void *newmem;
263 
264 		newalloc = dest->alloc + source->alloc;
265 		if (newalloc < 4) {
266 			newalloc = 4;
267 		}
268 
269 		newmem = isc_mem_get(dest->mctx,
270 				     newalloc * sizeof(dns_aclelement_t));
271 
272 		/* Zero. */
273 		memset(newmem, 0, newalloc * sizeof(dns_aclelement_t));
274 
275 		/* Copy in the original elements */
276 		memmove(newmem, dest->elements,
277 			dest->length * sizeof(dns_aclelement_t));
278 
279 		/* Release the memory for the old elements array */
280 		isc_mem_put(dest->mctx, dest->elements,
281 			    dest->alloc * sizeof(dns_aclelement_t));
282 		dest->elements = newmem;
283 		dest->alloc = newalloc;
284 	}
285 
286 	/*
287 	 * Now copy in the new elements, increasing their node_num
288 	 * values so as to keep the new ACL consistent.  If we're
289 	 * negating, then negate positive elements, but keep negative
290 	 * elements the same for security reasons.
291 	 */
292 	nelem = dest->length;
293 	dest->length += source->length;
294 	for (i = 0; i < source->length; i++) {
295 		if (source->elements[i].node_num > max_node) {
296 			max_node = source->elements[i].node_num;
297 		}
298 
299 		/* Copy type. */
300 		dest->elements[nelem + i].type = source->elements[i].type;
301 
302 		/* Adjust node numbering. */
303 		dest->elements[nelem + i].node_num =
304 			source->elements[i].node_num + dns_acl_node_count(dest);
305 
306 		/* Duplicate nested acl. */
307 		if (source->elements[i].type == dns_aclelementtype_nestedacl &&
308 		    source->elements[i].nestedacl != NULL)
309 		{
310 			dns_acl_attach(source->elements[i].nestedacl,
311 				       &dest->elements[nelem + i].nestedacl);
312 		}
313 
314 		/* Duplicate key name. */
315 		if (source->elements[i].type == dns_aclelementtype_keyname) {
316 			dns_name_init(&dest->elements[nelem + i].keyname, NULL);
317 			dns_name_dup(&source->elements[i].keyname, dest->mctx,
318 				     &dest->elements[nelem + i].keyname);
319 		}
320 
321 #if defined(HAVE_GEOIP2)
322 		/* Duplicate GeoIP data */
323 		if (source->elements[i].type == dns_aclelementtype_geoip) {
324 			dest->elements[nelem + i].geoip_elem =
325 				source->elements[i].geoip_elem;
326 		}
327 #endif /* if defined(HAVE_GEOIP2) */
328 
329 		/* reverse sense of positives if this is a negative acl */
330 		if (!pos && !source->elements[i].negative) {
331 			dest->elements[nelem + i].negative = true;
332 		} else {
333 			dest->elements[nelem + i].negative =
334 				source->elements[i].negative;
335 		}
336 	}
337 
338 	/*
339 	 * Merge the iptables.  Make sure the destination ACL's
340 	 * node_count value is set correctly afterward.
341 	 */
342 	nodes = max_node + dns_acl_node_count(dest);
343 	result = dns_iptable_merge(dest->iptable, source->iptable, pos);
344 	if (result != ISC_R_SUCCESS) {
345 		return (result);
346 	}
347 	if (nodes > dns_acl_node_count(dest)) {
348 		dns_acl_node_count(dest) = nodes;
349 	}
350 
351 	return (ISC_R_SUCCESS);
352 }
353 
354 /*
355  * Like dns_acl_match, but matches against the single ACL element 'e'
356  * rather than a complete ACL, and returns true iff it matched.
357  *
358  * To determine whether the match was positive or negative, the
359  * caller should examine e->negative.  Since the element 'e' may be
360  * a reference to a named ACL or a nested ACL, a matching element
361  * returned through 'matchelt' is not necessarily 'e' itself.
362  */
363 
364 bool
dns_aclelement_match(const isc_netaddr_t * reqaddr,const dns_name_t * reqsigner,const dns_aclelement_t * e,const dns_aclenv_t * env,const dns_aclelement_t ** matchelt)365 dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
366 		     const dns_aclelement_t *e, const dns_aclenv_t *env,
367 		     const dns_aclelement_t **matchelt) {
368 	dns_acl_t *inner = NULL;
369 	int indirectmatch;
370 	isc_result_t result;
371 
372 	switch (e->type) {
373 	case dns_aclelementtype_keyname:
374 		if (reqsigner != NULL && dns_name_equal(reqsigner, &e->keyname))
375 		{
376 			if (matchelt != NULL) {
377 				*matchelt = e;
378 			}
379 			return (true);
380 		} else {
381 			return (false);
382 		}
383 
384 	case dns_aclelementtype_nestedacl:
385 		inner = e->nestedacl;
386 		break;
387 
388 	case dns_aclelementtype_localhost:
389 		if (env == NULL || env->localhost == NULL) {
390 			return (false);
391 		}
392 		inner = env->localhost;
393 		break;
394 
395 	case dns_aclelementtype_localnets:
396 		if (env == NULL || env->localnets == NULL) {
397 			return (false);
398 		}
399 		inner = env->localnets;
400 		break;
401 
402 #if defined(HAVE_GEOIP2)
403 	case dns_aclelementtype_geoip:
404 		if (env == NULL || env->geoip == NULL) {
405 			return (false);
406 		}
407 		return (dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem));
408 #endif /* if defined(HAVE_GEOIP2) */
409 	default:
410 		UNREACHABLE();
411 	}
412 
413 	result = dns_acl_match(reqaddr, reqsigner, inner, env, &indirectmatch,
414 			       matchelt);
415 	INSIST(result == ISC_R_SUCCESS);
416 
417 	/*
418 	 * Treat negative matches in indirect ACLs as "no match".
419 	 * That way, a negated indirect ACL will never become a
420 	 * surprise positive match through double negation.
421 	 * XXXDCL this should be documented.
422 	 */
423 	if (indirectmatch > 0) {
424 		if (matchelt != NULL) {
425 			*matchelt = e;
426 		}
427 		return (true);
428 	}
429 
430 	/*
431 	 * A negative indirect match may have set *matchelt, but we don't
432 	 * want it set when we return.
433 	 */
434 	if (matchelt != NULL) {
435 		*matchelt = NULL;
436 	}
437 
438 	return (false);
439 }
440 
441 void
dns_acl_attach(dns_acl_t * source,dns_acl_t ** target)442 dns_acl_attach(dns_acl_t *source, dns_acl_t **target) {
443 	REQUIRE(DNS_ACL_VALID(source));
444 
445 	isc_refcount_increment(&source->refcount);
446 	*target = source;
447 }
448 
449 static void
destroy(dns_acl_t * dacl)450 destroy(dns_acl_t *dacl) {
451 	unsigned int i;
452 
453 	INSIST(!ISC_LINK_LINKED(dacl, nextincache));
454 
455 	for (i = 0; i < dacl->length; i++) {
456 		dns_aclelement_t *de = &dacl->elements[i];
457 		if (de->type == dns_aclelementtype_keyname) {
458 			dns_name_free(&de->keyname, dacl->mctx);
459 		} else if (de->type == dns_aclelementtype_nestedacl) {
460 			dns_acl_detach(&de->nestedacl);
461 		}
462 	}
463 	if (dacl->elements != NULL) {
464 		isc_mem_put(dacl->mctx, dacl->elements,
465 			    dacl->alloc * sizeof(dns_aclelement_t));
466 	}
467 	if (dacl->name != NULL) {
468 		isc_mem_free(dacl->mctx, dacl->name);
469 	}
470 	if (dacl->iptable != NULL) {
471 		dns_iptable_detach(&dacl->iptable);
472 	}
473 	isc_refcount_destroy(&dacl->refcount);
474 	dacl->magic = 0;
475 	isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl));
476 }
477 
478 void
dns_acl_detach(dns_acl_t ** aclp)479 dns_acl_detach(dns_acl_t **aclp) {
480 	REQUIRE(aclp != NULL && DNS_ACL_VALID(*aclp));
481 	dns_acl_t *acl = *aclp;
482 	*aclp = NULL;
483 
484 	if (isc_refcount_decrement(&acl->refcount) == 1) {
485 		destroy(acl);
486 	}
487 }
488 
489 static isc_once_t insecure_prefix_once = ISC_ONCE_INIT;
490 static isc_mutex_t insecure_prefix_lock;
491 static bool insecure_prefix_found;
492 
493 static void
initialize_action(void)494 initialize_action(void) {
495 	isc_mutex_init(&insecure_prefix_lock);
496 }
497 
498 /*
499  * Called via isc_radix_process() to find IP table nodes that are
500  * insecure.
501  */
502 static void
is_insecure(isc_prefix_t * prefix,void ** data)503 is_insecure(isc_prefix_t *prefix, void **data) {
504 	/*
505 	 * If all nonexistent or negative then this node is secure.
506 	 */
507 	if ((data[0] == NULL || !*(bool *)data[0]) &&
508 	    (data[1] == NULL || !*(bool *)data[1]))
509 	{
510 		return;
511 	}
512 
513 	/*
514 	 * If a loopback address found and the other family
515 	 * entry doesn't exist or is negative, return.
516 	 */
517 	if (prefix->bitlen == 32 &&
518 	    htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK &&
519 	    (data[1] == NULL || !*(bool *)data[1]))
520 	{
521 		return;
522 	}
523 
524 	if (prefix->bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) &&
525 	    (data[0] == NULL || !*(bool *)data[0]))
526 	{
527 		return;
528 	}
529 
530 	/* Non-negated, non-loopback */
531 	insecure_prefix_found = true; /* LOCKED */
532 	return;
533 }
534 
535 /*
536  * Return true iff the acl 'a' is considered insecure, that is,
537  * if it contains IP addresses other than those of the local host.
538  * This is intended for applications such as printing warning
539  * messages for suspect ACLs; it is not intended for making access
540  * control decisions.  We make no guarantee that an ACL for which
541  * this function returns false is safe.
542  */
543 bool
dns_acl_isinsecure(const dns_acl_t * a)544 dns_acl_isinsecure(const dns_acl_t *a) {
545 	unsigned int i;
546 	bool insecure;
547 
548 	RUNTIME_CHECK(isc_once_do(&insecure_prefix_once, initialize_action) ==
549 		      ISC_R_SUCCESS);
550 
551 	/*
552 	 * Walk radix tree to find out if there are any non-negated,
553 	 * non-loopback prefixes.
554 	 */
555 	LOCK(&insecure_prefix_lock);
556 	insecure_prefix_found = false;
557 	isc_radix_process(a->iptable->radix, is_insecure);
558 	insecure = insecure_prefix_found;
559 	UNLOCK(&insecure_prefix_lock);
560 	if (insecure) {
561 		return (true);
562 	}
563 
564 	/* Now check non-radix elements */
565 	for (i = 0; i < a->length; i++) {
566 		dns_aclelement_t *e = &a->elements[i];
567 
568 		/* A negated match can never be insecure. */
569 		if (e->negative) {
570 			continue;
571 		}
572 
573 		switch (e->type) {
574 		case dns_aclelementtype_keyname:
575 		case dns_aclelementtype_localhost:
576 			continue;
577 
578 		case dns_aclelementtype_nestedacl:
579 			if (dns_acl_isinsecure(e->nestedacl)) {
580 				return (true);
581 			}
582 			continue;
583 
584 #if defined(HAVE_GEOIP2)
585 		case dns_aclelementtype_geoip:
586 #endif /* if defined(HAVE_GEOIP2) */
587 		case dns_aclelementtype_localnets:
588 			return (true);
589 
590 		default:
591 			UNREACHABLE();
592 		}
593 	}
594 
595 	/* No insecure elements were found. */
596 	return (false);
597 }
598 
599 /*%
600  * Check whether an address/signer is allowed by a given acl/aclenv.
601  */
602 bool
dns_acl_allowed(isc_netaddr_t * addr,const dns_name_t * signer,dns_acl_t * acl,dns_aclenv_t * aclenv)603 dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl,
604 		dns_aclenv_t *aclenv) {
605 	int match;
606 	isc_result_t result;
607 
608 	if (acl == NULL) {
609 		return (true);
610 	}
611 	result = dns_acl_match(addr, signer, acl, aclenv, &match, NULL);
612 	if (result == ISC_R_SUCCESS && match > 0) {
613 		return (true);
614 	}
615 	return (false);
616 }
617 
618 /*
619  * Initialize ACL environment, setting up localhost and localnets ACLs
620  */
621 isc_result_t
dns_aclenv_init(isc_mem_t * mctx,dns_aclenv_t * env)622 dns_aclenv_init(isc_mem_t *mctx, dns_aclenv_t *env) {
623 	isc_result_t result;
624 
625 	env->localhost = NULL;
626 	env->localnets = NULL;
627 	result = dns_acl_create(mctx, 0, &env->localhost);
628 	if (result != ISC_R_SUCCESS) {
629 		goto cleanup_nothing;
630 	}
631 	result = dns_acl_create(mctx, 0, &env->localnets);
632 	if (result != ISC_R_SUCCESS) {
633 		goto cleanup_localhost;
634 	}
635 	env->match_mapped = false;
636 #if defined(HAVE_GEOIP2)
637 	env->geoip = NULL;
638 #endif /* if defined(HAVE_GEOIP2) */
639 	return (ISC_R_SUCCESS);
640 
641 cleanup_localhost:
642 	dns_acl_detach(&env->localhost);
643 cleanup_nothing:
644 	return (result);
645 }
646 
647 void
dns_aclenv_copy(dns_aclenv_t * t,dns_aclenv_t * s)648 dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) {
649 	dns_acl_detach(&t->localhost);
650 	dns_acl_attach(s->localhost, &t->localhost);
651 	dns_acl_detach(&t->localnets);
652 	dns_acl_attach(s->localnets, &t->localnets);
653 	t->match_mapped = s->match_mapped;
654 #if defined(HAVE_GEOIP2)
655 	t->geoip = s->geoip;
656 #endif /* if defined(HAVE_GEOIP2) */
657 }
658 
659 void
dns_aclenv_destroy(dns_aclenv_t * env)660 dns_aclenv_destroy(dns_aclenv_t *env) {
661 	if (env->localhost != NULL) {
662 		dns_acl_detach(&env->localhost);
663 	}
664 	if (env->localnets != NULL) {
665 		dns_acl_detach(&env->localnets);
666 	}
667 }
668