Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Internet Systems Consortium, Inc. ("ISC") |
3 | | * |
4 | | * SPDX-License-Identifier: MPL-2.0 |
5 | | * |
6 | | * This Source Code Form is subject to the terms of the Mozilla Public |
7 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
8 | | * file, you can obtain one at https://mozilla.org/MPL/2.0/. |
9 | | * |
10 | | * See the COPYRIGHT file distributed with this work for additional |
11 | | * information regarding copyright ownership. |
12 | | */ |
13 | | |
14 | | /*! \file */ |
15 | | |
16 | | #include <inttypes.h> |
17 | | #include <stdbool.h> |
18 | | |
19 | | #include <isc/mem.h> |
20 | | #include <isc/once.h> |
21 | | #include <isc/string.h> |
22 | | #include <isc/urcu.h> |
23 | | #include <isc/util.h> |
24 | | |
25 | | #include <dns/acl.h> |
26 | | #include <dns/iptable.h> |
27 | | |
28 | | #include "acl_p.h" |
29 | | |
30 | 2 | #define DNS_ACLENV_MAGIC ISC_MAGIC('a', 'c', 'n', 'v') |
31 | | #define VALID_ACLENV(a) ISC_MAGIC_VALID(a, DNS_ACLENV_MAGIC) |
32 | | |
33 | | /* |
34 | | * Create a new ACL, including an IP table and an array with room |
35 | | * for 'n' ACL elements. The elements are uninitialized and the |
36 | | * length is 0. |
37 | | */ |
38 | | void |
39 | 4 | dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) { |
40 | 4 | REQUIRE(target != NULL && *target == NULL); |
41 | | |
42 | 4 | dns_acl_t *acl = isc_mem_get(mctx, sizeof(*acl)); |
43 | 4 | *acl = (dns_acl_t){ |
44 | 4 | .references = ISC_REFCOUNT_INITIALIZER(1), |
45 | 4 | .nextincache = ISC_LINK_INITIALIZER, |
46 | 4 | .elements = isc_mem_cget(mctx, n, sizeof(acl->elements[0])), |
47 | 4 | .alloc = n, |
48 | 4 | .ports_and_transports = ISC_LIST_INITIALIZER, |
49 | 4 | .magic = DNS_ACL_MAGIC, |
50 | 4 | }; |
51 | | |
52 | 4 | isc_mem_attach(mctx, &acl->mctx); |
53 | 4 | dns_iptable_create(acl->mctx, &acl->iptable); |
54 | | |
55 | 4 | *target = acl; |
56 | 4 | } |
57 | | |
58 | | /* |
59 | | * Create a new ACL and initialize it with the value "any" or "none", |
60 | | * depending on the value of the "neg" parameter. |
61 | | * "any" is a positive iptable entry with bit length 0. |
62 | | * "none" is the same as "!any". |
63 | | */ |
64 | | static isc_result_t |
65 | 0 | dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) { |
66 | 0 | isc_result_t result; |
67 | 0 | dns_acl_t *acl = NULL; |
68 | |
|
69 | 0 | dns_acl_create(mctx, 0, &acl); |
70 | |
|
71 | 0 | result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg); |
72 | 0 | if (result != ISC_R_SUCCESS) { |
73 | 0 | dns_acl_detach(&acl); |
74 | 0 | return result; |
75 | 0 | } |
76 | | |
77 | 0 | *target = acl; |
78 | 0 | return result; |
79 | 0 | } |
80 | | |
81 | | /* |
82 | | * Create a new ACL that matches everything. |
83 | | */ |
84 | | isc_result_t |
85 | 0 | dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) { |
86 | 0 | return dns_acl_anyornone(mctx, false, target); |
87 | 0 | } |
88 | | |
89 | | /* |
90 | | * Create a new ACL that matches nothing. |
91 | | */ |
92 | | isc_result_t |
93 | 0 | dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) { |
94 | 0 | return dns_acl_anyornone(mctx, true, target); |
95 | 0 | } |
96 | | |
97 | | /* |
98 | | * If pos is true, test whether acl is set to "{ any; }" |
99 | | * If pos is false, test whether acl is set to "{ none; }" |
100 | | */ |
101 | | static bool |
102 | 0 | dns_acl_isanyornone(dns_acl_t *acl, bool pos) { |
103 | | /* Should never happen but let's be safe */ |
104 | 0 | if (acl == NULL || acl->iptable == NULL || |
105 | 0 | acl->iptable->radix == NULL || acl->iptable->radix->head == NULL || |
106 | 0 | acl->iptable->radix->head->prefix == NULL) |
107 | 0 | { |
108 | 0 | return false; |
109 | 0 | } |
110 | | |
111 | 0 | if (acl->length != 0 || dns_acl_node_count(acl) != 1) { |
112 | 0 | return false; |
113 | 0 | } |
114 | | |
115 | 0 | if (acl->iptable->radix->head->prefix->bitlen == 0 && |
116 | 0 | acl->iptable->radix->head->data[0] != NULL && |
117 | 0 | acl->iptable->radix->head->data[0] == |
118 | 0 | acl->iptable->radix->head->data[1] && |
119 | 0 | *(bool *)(acl->iptable->radix->head->data[0]) == pos) |
120 | 0 | { |
121 | 0 | return true; |
122 | 0 | } |
123 | | |
124 | 0 | return false; /* All others */ |
125 | 0 | } |
126 | | |
127 | | /* |
128 | | * Test whether acl is set to "{ any; }" |
129 | | */ |
130 | | bool |
131 | 0 | dns_acl_isany(dns_acl_t *acl) { |
132 | 0 | return dns_acl_isanyornone(acl, true); |
133 | 0 | } |
134 | | |
135 | | /* |
136 | | * Test whether acl is set to "{ none; }" |
137 | | */ |
138 | | bool |
139 | 0 | dns_acl_isnone(dns_acl_t *acl) { |
140 | 0 | return dns_acl_isanyornone(acl, false); |
141 | 0 | } |
142 | | |
143 | | /* |
144 | | * Determine whether a given address or signer matches a given ACL. |
145 | | * For a match with a positive ACL element or iptable radix entry, |
146 | | * return with a positive value in match; for a match with a negated ACL |
147 | | * element or radix entry, return with a negative value in match. |
148 | | */ |
149 | | |
150 | | isc_result_t |
151 | | dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, |
152 | | const dns_acl_t *acl, dns_aclenv_t *env, int *match, |
153 | 0 | const dns_aclelement_t **matchelt) { |
154 | 0 | uint16_t bitlen; |
155 | 0 | isc_prefix_t pfx; |
156 | 0 | isc_radix_node_t *node = NULL; |
157 | 0 | const isc_netaddr_t *addr = reqaddr; |
158 | 0 | isc_netaddr_t v4addr; |
159 | 0 | isc_result_t result; |
160 | 0 | int match_num = -1; |
161 | 0 | unsigned int i; |
162 | |
|
163 | 0 | REQUIRE(reqaddr != NULL); |
164 | 0 | REQUIRE(matchelt == NULL || *matchelt == NULL); |
165 | |
|
166 | 0 | if (env != NULL && env->match_mapped && addr->family == AF_INET6 && |
167 | 0 | IN6_IS_ADDR_V4MAPPED(&addr->type.in6)) |
168 | 0 | { |
169 | 0 | isc_netaddr_fromv4mapped(&v4addr, addr); |
170 | 0 | addr = &v4addr; |
171 | 0 | } |
172 | | |
173 | | /* Always match with host addresses. */ |
174 | 0 | bitlen = (addr->family == AF_INET6) ? 128 : 32; |
175 | 0 | NETADDR_TO_PREFIX_T(addr, pfx, bitlen); |
176 | | |
177 | | /* Assume no match. */ |
178 | 0 | *match = 0; |
179 | | |
180 | | /* Search radix. */ |
181 | 0 | result = isc_radix_search(acl->iptable->radix, &node, &pfx); |
182 | | |
183 | | /* Found a match. */ |
184 | 0 | if (result == ISC_R_SUCCESS && node != NULL) { |
185 | 0 | int fam = ISC_RADIX_FAMILY(&pfx); |
186 | 0 | match_num = node->node_num[fam]; |
187 | 0 | if (*(bool *)node->data[fam]) { |
188 | 0 | *match = match_num; |
189 | 0 | } else { |
190 | 0 | *match = -match_num; |
191 | 0 | } |
192 | 0 | } |
193 | |
|
194 | 0 | isc_refcount_destroy(&pfx.refcount); |
195 | | |
196 | | /* Now search non-radix elements for a match with a lower node_num. */ |
197 | 0 | for (i = 0; i < acl->length; i++) { |
198 | 0 | dns_aclelement_t *e = &acl->elements[i]; |
199 | | |
200 | | /* Already found a better match? */ |
201 | 0 | if (match_num != -1 && match_num < e->node_num) { |
202 | 0 | break; |
203 | 0 | } |
204 | | |
205 | 0 | if (dns_aclelement_match(reqaddr, reqsigner, e, env, matchelt)) |
206 | 0 | { |
207 | 0 | if (match_num == -1 || e->node_num < match_num) { |
208 | 0 | if (e->negative) { |
209 | 0 | *match = -e->node_num; |
210 | 0 | } else { |
211 | 0 | *match = e->node_num; |
212 | 0 | } |
213 | 0 | } |
214 | 0 | break; |
215 | 0 | } |
216 | 0 | } |
217 | |
|
218 | 0 | return ISC_R_SUCCESS; |
219 | 0 | } |
220 | | |
221 | | isc_result_t |
222 | | dns_acl_match_port_transport(const isc_netaddr_t *reqaddr, |
223 | | const in_port_t local_port, |
224 | | const isc_nmsocket_type_t transport, |
225 | | const bool encrypted, const dns_name_t *reqsigner, |
226 | | const dns_acl_t *acl, dns_aclenv_t *env, |
227 | 0 | int *match, const dns_aclelement_t **matchelt) { |
228 | 0 | isc_result_t result = ISC_R_SUCCESS; |
229 | |
|
230 | 0 | REQUIRE(reqaddr != NULL); |
231 | 0 | REQUIRE(DNS_ACL_VALID(acl)); |
232 | |
|
233 | 0 | dns_acl_t *a = UNCONST(acl); /* for ISC_LIST_FOREACH */ |
234 | 0 | ISC_LIST_FOREACH(a->ports_and_transports, next, link) { |
235 | 0 | bool match_port = true; |
236 | 0 | bool match_transport = true; |
237 | 0 | result = ISC_R_FAILURE; |
238 | |
|
239 | 0 | if (next->port != 0) { |
240 | | /* Port is specified. */ |
241 | 0 | match_port = (local_port == next->port); |
242 | 0 | } |
243 | 0 | if (next->transports != 0) { |
244 | | /* Transport protocol is specified. */ |
245 | 0 | match_transport = ((transport & next->transports) == |
246 | 0 | transport && |
247 | 0 | next->encrypted == encrypted); |
248 | 0 | } |
249 | |
|
250 | 0 | if (match_port && match_transport) { |
251 | 0 | result = next->negative ? ISC_R_FAILURE : ISC_R_SUCCESS; |
252 | 0 | break; |
253 | 0 | } |
254 | 0 | } |
255 | |
|
256 | 0 | if (result != ISC_R_SUCCESS) { |
257 | 0 | return result; |
258 | 0 | } |
259 | | |
260 | 0 | return dns_acl_match(reqaddr, reqsigner, acl, env, match, matchelt); |
261 | 0 | } |
262 | | |
263 | | /* |
264 | | * Merge the contents of one ACL into another. Call dns_iptable_merge() |
265 | | * for the IP tables, then concatenate the element arrays. |
266 | | * |
267 | | * If pos is set to false, then the nested ACL is to be negated. This |
268 | | * means reverse the sense of each *positive* element or IP table node, |
269 | | * but leave negatives alone, so as to prevent a double-negative causing |
270 | | * an unexpected positive match in the parent ACL. |
271 | | */ |
272 | | isc_result_t |
273 | 0 | dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos) { |
274 | 0 | unsigned int nelem, i; |
275 | 0 | int max_node = 0, nodes; |
276 | | |
277 | | /* Resize the element array if needed. */ |
278 | 0 | if (dest->length + source->length > dest->alloc) { |
279 | 0 | size_t newalloc = dest->alloc + source->alloc; |
280 | 0 | if (newalloc < 4) { |
281 | 0 | newalloc = 4; |
282 | 0 | } |
283 | |
|
284 | 0 | dest->elements = isc_mem_creget(dest->mctx, dest->elements, |
285 | 0 | dest->alloc, newalloc, |
286 | 0 | sizeof(dest->elements[0])); |
287 | 0 | dest->alloc = newalloc; |
288 | 0 | } |
289 | | |
290 | | /* |
291 | | * Now copy in the new elements, increasing their node_num |
292 | | * values so as to keep the new ACL consistent. If we're |
293 | | * negating, then negate positive elements, but keep negative |
294 | | * elements the same for security reasons. |
295 | | */ |
296 | 0 | nelem = dest->length; |
297 | 0 | dest->length += source->length; |
298 | 0 | for (i = 0; i < source->length; i++) { |
299 | 0 | if (source->elements[i].node_num > max_node) { |
300 | 0 | max_node = source->elements[i].node_num; |
301 | 0 | } |
302 | | |
303 | | /* Copy type. */ |
304 | 0 | dest->elements[nelem + i].type = source->elements[i].type; |
305 | | |
306 | | /* Adjust node numbering. */ |
307 | 0 | dest->elements[nelem + i].node_num = |
308 | 0 | source->elements[i].node_num + dns_acl_node_count(dest); |
309 | | |
310 | | /* Duplicate nested acl. */ |
311 | 0 | if (source->elements[i].type == dns_aclelementtype_nestedacl && |
312 | 0 | source->elements[i].nestedacl != NULL) |
313 | 0 | { |
314 | 0 | dns_acl_attach(source->elements[i].nestedacl, |
315 | 0 | &dest->elements[nelem + i].nestedacl); |
316 | 0 | } |
317 | | |
318 | | /* Duplicate key name. */ |
319 | 0 | if (source->elements[i].type == dns_aclelementtype_keyname) { |
320 | 0 | dns_name_init(&dest->elements[nelem + i].keyname); |
321 | 0 | dns_name_dup(&source->elements[i].keyname, dest->mctx, |
322 | 0 | &dest->elements[nelem + i].keyname); |
323 | 0 | } |
324 | |
|
325 | | #if defined(HAVE_GEOIP2) |
326 | | /* Duplicate GeoIP data */ |
327 | | if (source->elements[i].type == dns_aclelementtype_geoip) { |
328 | | dest->elements[nelem + i].geoip_elem = |
329 | | source->elements[i].geoip_elem; |
330 | | } |
331 | | #endif /* if defined(HAVE_GEOIP2) */ |
332 | | |
333 | | /* reverse sense of positives if this is a negative acl */ |
334 | 0 | if (!pos && !source->elements[i].negative) { |
335 | 0 | dest->elements[nelem + i].negative = true; |
336 | 0 | } else { |
337 | 0 | dest->elements[nelem + i].negative = |
338 | 0 | source->elements[i].negative; |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | | /* |
343 | | * Merge the iptables. Make sure the destination ACL's |
344 | | * node_count value is set correctly afterward. |
345 | | */ |
346 | 0 | nodes = max_node + dns_acl_node_count(dest); |
347 | 0 | RETERR(dns_iptable_merge(dest->iptable, source->iptable, pos)); |
348 | 0 | if (nodes > dns_acl_node_count(dest)) { |
349 | 0 | dns_acl_node_count(dest) = nodes; |
350 | 0 | } |
351 | | |
352 | | /* |
353 | | * Merge ports and transports |
354 | | */ |
355 | 0 | dns_acl_merge_ports_transports(dest, source, pos); |
356 | |
|
357 | 0 | return ISC_R_SUCCESS; |
358 | 0 | } |
359 | | |
360 | | /* |
361 | | * Like dns_acl_match, but matches against the single ACL element 'e' |
362 | | * rather than a complete ACL, and returns true iff it matched. |
363 | | * |
364 | | * To determine whether the match was positive or negative, the |
365 | | * caller should examine e->negative. Since the element 'e' may be |
366 | | * a reference to a named ACL or a nested ACL, a matching element |
367 | | * returned through 'matchelt' is not necessarily 'e' itself. |
368 | | */ |
369 | | |
370 | | bool |
371 | | dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, |
372 | | const dns_aclelement_t *e, dns_aclenv_t *env, |
373 | 0 | const dns_aclelement_t **matchelt) { |
374 | 0 | dns_acl_t *inner = NULL; |
375 | 0 | int indirectmatch; |
376 | 0 | isc_result_t result; |
377 | |
|
378 | 0 | switch (e->type) { |
379 | 0 | case dns_aclelementtype_keyname: |
380 | 0 | if (reqsigner != NULL && dns_name_equal(reqsigner, &e->keyname)) |
381 | 0 | { |
382 | 0 | if (matchelt != NULL) { |
383 | 0 | *matchelt = e; |
384 | 0 | } |
385 | 0 | return true; |
386 | 0 | } else { |
387 | 0 | return false; |
388 | 0 | } |
389 | | |
390 | 0 | case dns_aclelementtype_nestedacl: |
391 | 0 | dns_acl_attach(e->nestedacl, &inner); |
392 | 0 | break; |
393 | | |
394 | 0 | case dns_aclelementtype_localhost: |
395 | 0 | if (env == NULL) { |
396 | 0 | return false; |
397 | 0 | } |
398 | 0 | rcu_read_lock(); |
399 | 0 | dns_acl_attach(rcu_dereference(env->localhost), &inner); |
400 | 0 | rcu_read_unlock(); |
401 | 0 | break; |
402 | | |
403 | 0 | case dns_aclelementtype_localnets: |
404 | 0 | if (env == NULL) { |
405 | 0 | return false; |
406 | 0 | } |
407 | 0 | rcu_read_lock(); |
408 | 0 | dns_acl_attach(rcu_dereference(env->localnets), &inner); |
409 | 0 | rcu_read_unlock(); |
410 | 0 | break; |
411 | | |
412 | | #if defined(HAVE_GEOIP2) |
413 | | case dns_aclelementtype_geoip: |
414 | | if (env == NULL || env->geoip == NULL) { |
415 | | return false; |
416 | | } |
417 | | return dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem); |
418 | | #endif /* if defined(HAVE_GEOIP2) */ |
419 | 0 | default: |
420 | 0 | UNREACHABLE(); |
421 | 0 | } |
422 | | |
423 | 0 | result = dns_acl_match(reqaddr, reqsigner, inner, env, &indirectmatch, |
424 | 0 | matchelt); |
425 | 0 | INSIST(result == ISC_R_SUCCESS); |
426 | |
|
427 | 0 | dns_acl_detach(&inner); |
428 | | |
429 | | /* |
430 | | * Treat negative matches in indirect ACLs as "no match". |
431 | | * That way, a negated indirect ACL will never become a |
432 | | * surprise positive match through double negation. |
433 | | * XXXDCL this should be documented. |
434 | | */ |
435 | 0 | if (indirectmatch > 0) { |
436 | 0 | if (matchelt != NULL) { |
437 | 0 | *matchelt = e; |
438 | 0 | } |
439 | 0 | return true; |
440 | 0 | } |
441 | | |
442 | | /* |
443 | | * A negative indirect match may have set *matchelt, but we don't |
444 | | * want it set when we return. |
445 | | */ |
446 | 0 | if (matchelt != NULL) { |
447 | 0 | *matchelt = NULL; |
448 | 0 | } |
449 | |
|
450 | 0 | return false; |
451 | 0 | } |
452 | | |
453 | | static void |
454 | 0 | dns__acl_destroy_port_transports(dns_acl_t *acl) { |
455 | 0 | ISC_LIST_FOREACH(acl->ports_and_transports, port_proto, link) { |
456 | 0 | ISC_LIST_DEQUEUE(acl->ports_and_transports, port_proto, link); |
457 | 0 | isc_mem_put(acl->mctx, port_proto, sizeof(*port_proto)); |
458 | 0 | } |
459 | 0 | } |
460 | | |
461 | | static void |
462 | 0 | dns__acl_destroy(dns_acl_t *dacl) { |
463 | 0 | INSIST(!ISC_LINK_LINKED(dacl, nextincache)); |
464 | |
|
465 | 0 | isc_refcount_destroy(&dacl->references); |
466 | 0 | dacl->magic = 0; |
467 | |
|
468 | 0 | for (size_t i = 0; i < dacl->length; i++) { |
469 | 0 | dns_aclelement_t *de = &dacl->elements[i]; |
470 | 0 | if (de->type == dns_aclelementtype_keyname) { |
471 | 0 | dns_name_free(&de->keyname, dacl->mctx); |
472 | 0 | } else if (de->type == dns_aclelementtype_nestedacl) { |
473 | 0 | dns_acl_detach(&de->nestedacl); |
474 | 0 | } |
475 | 0 | } |
476 | 0 | if (dacl->elements != NULL) { |
477 | 0 | isc_mem_cput(dacl->mctx, dacl->elements, dacl->alloc, |
478 | 0 | sizeof(dacl->elements[0])); |
479 | 0 | } |
480 | 0 | if (dacl->name != NULL) { |
481 | 0 | isc_mem_free(dacl->mctx, dacl->name); |
482 | 0 | } |
483 | 0 | if (dacl->iptable != NULL) { |
484 | 0 | dns_iptable_detach(&dacl->iptable); |
485 | 0 | } |
486 | |
|
487 | 0 | dns__acl_destroy_port_transports(dacl); |
488 | |
|
489 | 0 | isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl)); |
490 | 0 | } |
491 | | |
492 | | #if DNS_ACL_TRACE |
493 | | ISC_REFCOUNT_TRACE_IMPL(dns_acl, dns__acl_destroy); |
494 | | #else |
495 | 0 | ISC_REFCOUNT_IMPL(dns_acl, dns__acl_destroy); Unexecuted instantiation: dns_acl_ref Unexecuted instantiation: dns_acl_unref Unexecuted instantiation: dns_acl_detach |
496 | 0 | #endif |
497 | 0 |
|
498 | 0 | static isc_mutex_t insecure_prefix_lock; |
499 | 0 | static bool insecure_prefix_found; |
500 | 0 |
|
501 | 0 | void |
502 | 22 | dns__acl_initialize(void) { |
503 | 22 | isc_mutex_init(&insecure_prefix_lock); |
504 | 22 | } |
505 | | |
506 | | void |
507 | 0 | dns__acl_shutdown(void) { |
508 | 0 | isc_mutex_destroy(&insecure_prefix_lock); |
509 | 0 | } |
510 | | |
511 | | /* |
512 | | * Called via isc_radix_process() to find IP table nodes that are |
513 | | * insecure. |
514 | | */ |
515 | | static void |
516 | 0 | is_insecure(isc_prefix_t *prefix, void **data) { |
517 | | /* |
518 | | * If all nonexistent or negative then this node is secure. |
519 | | */ |
520 | 0 | if ((data[0] == NULL || !*(bool *)data[0]) && |
521 | 0 | (data[1] == NULL || !*(bool *)data[1])) |
522 | 0 | { |
523 | 0 | return; |
524 | 0 | } |
525 | | |
526 | | /* |
527 | | * If a loopback address found and the other family |
528 | | * entry doesn't exist or is negative, return. |
529 | | */ |
530 | 0 | if (prefix->bitlen == 32 && |
531 | 0 | htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK && |
532 | 0 | (data[1] == NULL || !*(bool *)data[1])) |
533 | 0 | { |
534 | 0 | return; |
535 | 0 | } |
536 | | |
537 | 0 | if (prefix->bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) && |
538 | 0 | (data[0] == NULL || !*(bool *)data[0])) |
539 | 0 | { |
540 | 0 | return; |
541 | 0 | } |
542 | | |
543 | | /* Non-negated, non-loopback */ |
544 | 0 | insecure_prefix_found = true; /* LOCKED */ |
545 | 0 | return; |
546 | 0 | } |
547 | | |
548 | | /* |
549 | | * Return true iff the acl 'a' is considered insecure, that is, |
550 | | * if it contains IP addresses other than those of the local host. |
551 | | * This is intended for applications such as printing warning |
552 | | * messages for suspect ACLs; it is not intended for making access |
553 | | * control decisions. We make no guarantee that an ACL for which |
554 | | * this function returns false is safe. |
555 | | */ |
556 | | bool |
557 | 0 | dns_acl_isinsecure(const dns_acl_t *a) { |
558 | 0 | unsigned int i; |
559 | 0 | bool insecure; |
560 | | |
561 | | /* |
562 | | * Walk radix tree to find out if there are any non-negated, |
563 | | * non-loopback prefixes. |
564 | | */ |
565 | 0 | LOCK(&insecure_prefix_lock); |
566 | 0 | insecure_prefix_found = false; |
567 | 0 | isc_radix_process(a->iptable->radix, is_insecure); |
568 | 0 | insecure = insecure_prefix_found; |
569 | 0 | UNLOCK(&insecure_prefix_lock); |
570 | 0 | if (insecure) { |
571 | 0 | return true; |
572 | 0 | } |
573 | | |
574 | | /* Now check non-radix elements */ |
575 | 0 | for (i = 0; i < a->length; i++) { |
576 | 0 | dns_aclelement_t *e = &a->elements[i]; |
577 | | |
578 | | /* A negated match can never be insecure. */ |
579 | 0 | if (e->negative) { |
580 | 0 | continue; |
581 | 0 | } |
582 | | |
583 | 0 | switch (e->type) { |
584 | 0 | case dns_aclelementtype_keyname: |
585 | 0 | case dns_aclelementtype_localhost: |
586 | 0 | continue; |
587 | | |
588 | 0 | case dns_aclelementtype_nestedacl: |
589 | 0 | if (dns_acl_isinsecure(e->nestedacl)) { |
590 | 0 | return true; |
591 | 0 | } |
592 | 0 | continue; |
593 | | |
594 | | #if defined(HAVE_GEOIP2) |
595 | | case dns_aclelementtype_geoip: |
596 | | #endif /* if defined(HAVE_GEOIP2) */ |
597 | 0 | case dns_aclelementtype_localnets: |
598 | 0 | return true; |
599 | | |
600 | 0 | default: |
601 | 0 | UNREACHABLE(); |
602 | 0 | } |
603 | 0 | } |
604 | | |
605 | | /* No insecure elements were found. */ |
606 | 0 | return false; |
607 | 0 | } |
608 | | |
609 | | /*% |
610 | | * Check whether an address/signer is allowed by a given acl/aclenv. |
611 | | */ |
612 | | bool |
613 | | dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl, |
614 | 0 | dns_aclenv_t *aclenv) { |
615 | 0 | int match; |
616 | 0 | isc_result_t result; |
617 | |
|
618 | 0 | if (acl == NULL) { |
619 | 0 | return true; |
620 | 0 | } |
621 | 0 | result = dns_acl_match(addr, signer, acl, aclenv, &match, NULL); |
622 | 0 | if (result == ISC_R_SUCCESS && match > 0) { |
623 | 0 | return true; |
624 | 0 | } |
625 | 0 | return false; |
626 | 0 | } |
627 | | |
628 | | /* |
629 | | * Initialize ACL environment, setting up localhost and localnets ACLs |
630 | | */ |
631 | | void |
632 | 2 | dns_aclenv_create(isc_mem_t *mctx, dns_aclenv_t **envp) { |
633 | 2 | dns_aclenv_t *env = isc_mem_get(mctx, sizeof(*env)); |
634 | 2 | *env = (dns_aclenv_t){ |
635 | 2 | .references = ISC_REFCOUNT_INITIALIZER(1), |
636 | 2 | .magic = DNS_ACLENV_MAGIC, |
637 | 2 | }; |
638 | | |
639 | 2 | isc_mem_attach(mctx, &env->mctx); |
640 | 2 | isc_refcount_init(&env->references, 1); |
641 | | |
642 | 2 | dns_acl_create(mctx, 0, &env->localhost); |
643 | 2 | dns_acl_create(mctx, 0, &env->localnets); |
644 | | |
645 | 2 | *envp = env; |
646 | 2 | } |
647 | | |
648 | | void |
649 | 0 | dns_aclenv_set(dns_aclenv_t *env, dns_acl_t *localhost, dns_acl_t *localnets) { |
650 | 0 | REQUIRE(VALID_ACLENV(env)); |
651 | 0 | REQUIRE(DNS_ACL_VALID(localhost)); |
652 | 0 | REQUIRE(DNS_ACL_VALID(localnets)); |
653 | |
|
654 | 0 | localhost = rcu_xchg_pointer(&env->localhost, dns_acl_ref(localhost)); |
655 | 0 | localnets = rcu_xchg_pointer(&env->localnets, dns_acl_ref(localnets)); |
656 | | |
657 | | /* |
658 | | * This function is called only during interface scanning, so blocking |
659 | | * a bit is acceptable. Wait until all ongoing attachments to old |
660 | | * 'localhost' and 'localnets' are finished before we can detach and |
661 | | * possibly destroy them. |
662 | | * |
663 | | * The problem here isn't the memory reclamation per se, but |
664 | | * the reference counting race - we need to wait for the |
665 | | * critical section to end before we decrement the value and |
666 | | * possibly destroy the acl objects. |
667 | | */ |
668 | 0 | synchronize_rcu(); |
669 | |
|
670 | 0 | dns_acl_detach(&localhost); |
671 | 0 | dns_acl_detach(&localnets); |
672 | 0 | } |
673 | | |
674 | | void |
675 | 0 | dns_aclenv_copy(dns_aclenv_t *target, dns_aclenv_t *source) { |
676 | 0 | REQUIRE(VALID_ACLENV(source)); |
677 | 0 | REQUIRE(VALID_ACLENV(target)); |
678 | |
|
679 | 0 | rcu_read_lock(); |
680 | | |
681 | | /* |
682 | | * We need to acquire the reference inside the critical section. |
683 | | */ |
684 | |
|
685 | 0 | dns_acl_t *localhost = dns_acl_ref(rcu_dereference(source->localhost)); |
686 | 0 | INSIST(DNS_ACL_VALID(localhost)); |
687 | |
|
688 | 0 | dns_acl_t *localnets = dns_acl_ref(rcu_dereference(source->localnets)); |
689 | 0 | INSIST(DNS_ACL_VALID(localnets)); |
690 | |
|
691 | 0 | rcu_read_unlock(); |
692 | |
|
693 | 0 | localhost = rcu_xchg_pointer(&target->localhost, localhost); |
694 | 0 | localnets = rcu_xchg_pointer(&target->localnets, localnets); |
695 | | |
696 | | /* |
697 | | * This function is called only during (re)configuration, so blocking |
698 | | * a bit is acceptable. |
699 | | * |
700 | | * See the comment above in dns_aclenv_set() for more detail. |
701 | | */ |
702 | 0 | synchronize_rcu(); |
703 | |
|
704 | 0 | target->match_mapped = source->match_mapped; |
705 | | #if defined(HAVE_GEOIP2) |
706 | | target->geoip = source->geoip; |
707 | | #endif /* if defined(HAVE_GEOIP2) */ |
708 | |
|
709 | 0 | dns_acl_detach(&localhost); |
710 | 0 | dns_acl_detach(&localnets); |
711 | 0 | } |
712 | | |
713 | | static void |
714 | 0 | dns__aclenv_destroy(dns_aclenv_t *aclenv) { |
715 | 0 | REQUIRE(VALID_ACLENV(aclenv)); |
716 | |
|
717 | 0 | aclenv->magic = 0; |
718 | | |
719 | | /* |
720 | | * The last reference to the aclenv has been detached, so nobody should |
721 | | * be reading from this aclenv. We can destroy the localhost and |
722 | | * localnet directly without swapping the pointers. |
723 | | */ |
724 | |
|
725 | 0 | dns_acl_detach(&aclenv->localhost); |
726 | 0 | dns_acl_detach(&aclenv->localnets); |
727 | |
|
728 | 0 | isc_mem_putanddetach(&aclenv->mctx, aclenv, sizeof(*aclenv)); |
729 | 0 | } |
730 | | |
731 | | #if DNS_ACL_TRACE |
732 | | ISC_REFCOUNT_TRACE_IMPL(dns_aclenv, dns__aclenv_destroy); |
733 | | #else |
734 | 0 | ISC_REFCOUNT_IMPL(dns_aclenv, dns__aclenv_destroy); Unexecuted instantiation: dns_aclenv_ref Unexecuted instantiation: dns_aclenv_unref Unexecuted instantiation: dns_aclenv_detach |
735 | 0 | #endif |
736 | 0 |
|
737 | 0 | void |
738 | 0 | dns_acl_add_port_transports(dns_acl_t *acl, const in_port_t port, |
739 | 0 | const uint32_t transports, const bool encrypted, |
740 | 0 | const bool negative) { |
741 | 0 | dns_acl_port_transports_t *port_proto; |
742 | 0 | REQUIRE(DNS_ACL_VALID(acl)); |
743 | 0 | REQUIRE(port != 0 || transports != 0); |
744 | |
|
745 | 0 | port_proto = isc_mem_get(acl->mctx, sizeof(*port_proto)); |
746 | 0 | *port_proto = (dns_acl_port_transports_t){ .port = port, |
747 | 0 | .transports = transports, |
748 | 0 | .encrypted = encrypted, |
749 | 0 | .negative = negative }; |
750 | |
|
751 | 0 | ISC_LINK_INIT(port_proto, link); |
752 | |
|
753 | 0 | ISC_LIST_APPEND(acl->ports_and_transports, port_proto, link); |
754 | 0 | acl->port_proto_entries++; |
755 | 0 | } |
756 | | |
757 | | void |
758 | 0 | dns_acl_merge_ports_transports(dns_acl_t *dest, dns_acl_t *source, bool pos) { |
759 | 0 | REQUIRE(DNS_ACL_VALID(dest)); |
760 | 0 | REQUIRE(DNS_ACL_VALID(source)); |
761 | |
|
762 | 0 | const bool negative = !pos; |
763 | | |
764 | | /* |
765 | | * Merge ports and transports |
766 | | */ |
767 | 0 | ISC_LIST_FOREACH(source->ports_and_transports, next, link) { |
768 | 0 | const bool next_positive = !next->negative; |
769 | 0 | bool add_negative; |
770 | | |
771 | | /* |
772 | | * Reverse sense of positives if this is a negative acl. The |
773 | | * logic is used (and, thus, enforced) by dns_acl_merge(), |
774 | | * from which dns_acl_merge_ports_transports() is called. |
775 | | */ |
776 | 0 | if (negative && next_positive) { |
777 | 0 | add_negative = true; |
778 | 0 | } else { |
779 | 0 | add_negative = next->negative; |
780 | 0 | } |
781 | |
|
782 | 0 | dns_acl_add_port_transports(dest, next->port, next->transports, |
783 | 0 | next->encrypted, add_negative); |
784 | 0 | } |
785 | 0 | } |