/src/freeradius-server/src/protocols/dns/base.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 9c2f827a54c3e52ee73d2fe242f9f440f897a826 $ |
19 | | * |
20 | | * @file protocols/dns/base.c |
21 | | * @brief Functions to send/receive dns packets. |
22 | | * |
23 | | * @copyright 2008 The FreeRADIUS server project |
24 | | * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) |
25 | | */ |
26 | | RCSID("$Id: 9c2f827a54c3e52ee73d2fe242f9f440f897a826 $") |
27 | | |
28 | | #include "dns.h" |
29 | | #include "attrs.h" |
30 | | #include <freeradius-devel/protocol/dns/rfc1034.h> |
31 | | |
32 | | static uint32_t instance_count = 0; |
33 | | static bool instantiated = false; |
34 | | |
35 | | typedef struct { |
36 | | uint16_t code; |
37 | | uint16_t length; |
38 | | } dns_option_t; |
39 | | |
40 | | fr_dict_t const *dict_dns; |
41 | | |
42 | | static _Thread_local fr_dns_labels_t fr_dns_labels; |
43 | | static _Thread_local fr_dns_block_t fr_dns_blocks[256]; |
44 | | static _Thread_local uint8_t fr_dns_marker[65536]; |
45 | | |
46 | | extern fr_dict_autoload_t dns_dict[]; |
47 | | fr_dict_autoload_t dns_dict[] = { |
48 | | { .out = &dict_dns, .proto = "dns" }, |
49 | | DICT_AUTOLOAD_TERMINATOR |
50 | | }; |
51 | | |
52 | | fr_dict_attr_t const *attr_dns_packet; |
53 | | fr_dict_attr_t const *attr_dns_question; |
54 | | fr_dict_attr_t const *attr_dns_rr; |
55 | | fr_dict_attr_t const *attr_dns_ns; |
56 | | fr_dict_attr_t const *attr_dns_ar; |
57 | | |
58 | | extern fr_dict_attr_autoload_t dns_dict_attr[]; |
59 | | fr_dict_attr_autoload_t dns_dict_attr[] = { |
60 | | { .out = &attr_dns_packet, .name = "Header", .type = FR_TYPE_STRUCT, .dict = &dict_dns }, |
61 | | { .out = &attr_dns_question, .name = "Question", .type = FR_TYPE_STRUCT, .dict = &dict_dns }, |
62 | | { .out = &attr_dns_rr, .name = "Resource-Record", .type = FR_TYPE_STRUCT, .dict = &dict_dns }, |
63 | | { .out = &attr_dns_ns, .name = "Name-Server", .type = FR_TYPE_STRUCT, .dict = &dict_dns }, |
64 | | { .out = &attr_dns_ar, .name = "Additional-Record", .type = FR_TYPE_STRUCT, .dict = &dict_dns }, |
65 | | DICT_AUTOLOAD_TERMINATOR |
66 | | }; |
67 | | |
68 | | char const *fr_dns_packet_names[FR_DNS_CODE_MAX] = { |
69 | | [FR_DNS_QUERY] = "Query", |
70 | | [FR_DNS_INVERSE_QUERY] = "Inverse-Query", |
71 | | [FR_DNS_STATUS] = "Status", |
72 | | [FR_DNS_NOTIFY] = "Notify", |
73 | | [FR_DNS_UPDATE] = "Update", |
74 | | [FR_DNS_STATEFUL_OPERATION] = "Stateful-Operation", |
75 | | }; |
76 | | |
77 | | FR_DICT_ATTR_FLAG_FUNC(fr_dns_attr_flags_t, dns_label) |
78 | | FR_DICT_ATTR_FLAG_FUNC(fr_dns_attr_flags_t, dns_label_uncompressed) |
79 | | |
80 | | static fr_dict_flag_parser_t const dns_flags[] = { |
81 | | { L("dns_label"), { .func = dict_flag_dns_label } }, |
82 | | { L("dns_label_uncompressed"), { .func = dict_flag_dns_label_uncompressed } } |
83 | | }; |
84 | | |
85 | 2.54k | #define DECODE_FAIL(_reason) if (reason) *reason = FR_DNS_DECODE_FAIL_ ## _reason |
86 | | |
87 | | static bool fr_dns_tlv_ok(uint8_t const *p, uint8_t const *end, fr_dns_decode_fail_t *reason) |
88 | 2.05k | { |
89 | 2.05k | uint16_t len; |
90 | | |
91 | 8.44k | while (p < end) { |
92 | 6.40k | if ((p + 4) > end) { |
93 | 10 | DECODE_FAIL(MISSING_TLV_HEADER); |
94 | 10 | return false; |
95 | 10 | } |
96 | | |
97 | 6.39k | len = fr_nbo_to_uint16(p + 2); |
98 | 6.39k | if ((p + 4 + len) > end) { |
99 | 2 | DECODE_FAIL(TLV_OVERFLOWS_RR); |
100 | 2 | return false; |
101 | 2 | } |
102 | | |
103 | 6.39k | p += 4 + len; |
104 | 6.39k | } |
105 | | |
106 | 2.03k | return true; |
107 | 2.05k | } |
108 | | |
109 | | bool fr_dns_packet_ok(uint8_t const *packet, size_t packet_len, bool query, fr_dns_decode_fail_t *reason) |
110 | 2.54k | { |
111 | 2.54k | uint8_t const *p, *end; |
112 | 2.54k | int qdcount, count, expected; |
113 | 2.54k | uint8_t opcode; |
114 | | |
115 | 2.54k | if (packet_len <= DNS_HDR_LEN) { |
116 | 9 | DECODE_FAIL(MIN_LENGTH_PACKET); |
117 | 9 | return false; |
118 | 9 | } |
119 | | |
120 | 2.53k | if (packet_len > 65535) { |
121 | 0 | DECODE_FAIL(MAX_LENGTH_PACKET); |
122 | 0 | return false; |
123 | 0 | } |
124 | | |
125 | | /* |
126 | | * query=0, response=1 |
127 | | */ |
128 | 2.53k | if (((packet[2] & 0x80) == 0) != query) { |
129 | 868 | DECODE_FAIL(UNEXPECTED); |
130 | 868 | return false; |
131 | 868 | } |
132 | | |
133 | | /* |
134 | | * @todo - the truncation rules mean that the various counts below are wrong, and the caller |
135 | | * should retry over TCP. This is really an indication to us, that we need to fully implement |
136 | | * the truncation checks. |
137 | | */ |
138 | 1.66k | if ((packet[2] & 0x02) != 0) { |
139 | 4 | DECODE_FAIL(TRUNCATED); |
140 | 4 | return false; |
141 | 4 | } |
142 | 1.66k | qdcount = fr_nbo_to_uint16(packet + 4); |
143 | | |
144 | 1.66k | opcode = (packet[2] >> 3) & 0x0f; |
145 | 1.66k | if ((opcode >= FR_DNS_CODE_MAX) || !fr_dns_packet_names[opcode]) { |
146 | 4 | DECODE_FAIL(UNKNOWN_OPCODE); |
147 | 4 | return false; |
148 | 4 | } |
149 | | |
150 | | /* |
151 | | * RFC 2136 (DNS update) defines the four "count" fields to have different meanings: |
152 | | * |
153 | | * ZOCOUNT The number of RRs in the Zone Section. |
154 | | * PRCOUNT The number of RRs in the Prerequisite Section. |
155 | | * UPCOUNT The number of RRs in the Update Section. |
156 | | * ADCOUNT The number of RRs in the Additional Data Section. |
157 | | * |
158 | | * @todo - we can likely do more validation checks on input packets. |
159 | | */ |
160 | 1.65k | if (query && (opcode != FR_OPCODE_VALUE_UPDATE)) { |
161 | | /* |
162 | | * There should be at least one query, and no |
163 | | * replies in the query. |
164 | | * |
165 | | * @todo - unless it's an IQUERY, in which case |
166 | | * there should be no questions, and at least one |
167 | | * answer. |
168 | | */ |
169 | 287 | if (!qdcount) { |
170 | 2 | DECODE_FAIL(NO_QUESTIONS); |
171 | 2 | return false; |
172 | 2 | } |
173 | | |
174 | 285 | if (fr_nbo_to_uint16(packet + 6) != 0) { |
175 | 22 | DECODE_FAIL(ANSWERS_IN_QUESTION); |
176 | 22 | return false; |
177 | 22 | } |
178 | | |
179 | 263 | if (fr_nbo_to_uint16(packet + 8) != 0) { |
180 | 18 | DECODE_FAIL(NS_IN_QUESTION); |
181 | 18 | return false; |
182 | 18 | } |
183 | | // additional records can exist! |
184 | | |
185 | 1.37k | } else { |
186 | | /* |
187 | | * Replies _usually_ copy the query. But not |
188 | | * always And replies can have zero or more answers. |
189 | | */ |
190 | 1.37k | } |
191 | | |
192 | 1.61k | expected = fr_nbo_to_uint16(packet + 4) + fr_nbo_to_uint16(packet + 6) + fr_nbo_to_uint16(packet + 8) + fr_nbo_to_uint16(packet + 10); |
193 | 1.61k | count = 0; |
194 | | |
195 | 1.61k | p = packet + DNS_HDR_LEN; |
196 | 1.61k | end = packet + packet_len; |
197 | | |
198 | | /* |
199 | | * We track valid label targets in a simple array (up to |
200 | | * 2^14 bits of compressed pointer). |
201 | | * |
202 | | * Note that some labels might appear in the RRDATA |
203 | | * field, and we don't verify those here. However, this |
204 | | * function will verify the most common packets. As a |
205 | | * result, any issues with overflow, etc. are more |
206 | | * difficult to exploit. |
207 | | */ |
208 | 1.61k | memset(fr_dns_marker, 0, packet_len < (1 << 14) ? packet_len : (1 << 14)); |
209 | | |
210 | | /* |
211 | | * Check for wildly fake packets, by making rough |
212 | | * estimations. This way we don't actually have to walk |
213 | | * the packet. |
214 | | */ |
215 | 1.61k | if (p + (qdcount * 5) > end) { |
216 | 30 | DECODE_FAIL(TOO_MANY_RRS); |
217 | 30 | return false; |
218 | 30 | } |
219 | 1.58k | p += (qdcount * 5); |
220 | | |
221 | 1.58k | if ((p + ((expected - qdcount) * (1 + 8 + 2))) > end) { |
222 | 38 | DECODE_FAIL(TOO_MANY_RRS); |
223 | 38 | return false; |
224 | 38 | } |
225 | | |
226 | | /* |
227 | | * The counts are at least vaguely OK, let's walk over the whole packet. |
228 | | */ |
229 | 1.54k | p = packet + DNS_HDR_LEN; |
230 | | |
231 | | /* |
232 | | * Check that lengths of RRs match. |
233 | | */ |
234 | 52.1k | while (p < end) { |
235 | 50.8k | uint16_t len = 0; |
236 | 50.8k | uint8_t const *start = p; |
237 | 50.8k | bool is_opt = false; |
238 | | |
239 | | /* |
240 | | * Simple DNS label decoder |
241 | | * |
242 | | * @todo - move this to src/lib/util/dns.c, |
243 | | * perhaps as fr_dns_label_verify(), and then |
244 | | * have it also return a pointer to the next |
245 | | * label? fr_dns_label_uncompressed_length() |
246 | | * does similar but slightly different things. |
247 | | */ |
248 | 58.1k | while (p < end) { |
249 | | /* |
250 | | * 0x00 is "end of label" |
251 | | */ |
252 | 58.1k | if (!*p) { |
253 | 50.2k | p++; |
254 | 50.2k | break; |
255 | 50.2k | } |
256 | | |
257 | | /* |
258 | | * 2 octets of 14-bit pointer, which must |
259 | | * be at least somewhat sane. |
260 | | */ |
261 | 7.92k | if (*p >= 0xc0) { |
262 | 523 | ptrdiff_t offset; |
263 | | |
264 | 523 | if ((p + 2) > end) { |
265 | 4 | DECODE_FAIL(POINTER_OVERFLOWS_PACKET); |
266 | 4 | return false; |
267 | 4 | } |
268 | | |
269 | 519 | offset = p[1]; |
270 | 519 | offset += ((*p & ~0xc0) << 8); |
271 | | |
272 | | /* |
273 | | * Can't point to the header. |
274 | | */ |
275 | 519 | if (offset < 12) { |
276 | 4 | DECODE_FAIL(POINTER_TO_HEADER); |
277 | 4 | return false; |
278 | 4 | } |
279 | | |
280 | | /* |
281 | | * Can't point to the current label. |
282 | | */ |
283 | 515 | if (offset >= (start - packet)) { |
284 | 26 | DECODE_FAIL(POINTER_LOOPS); |
285 | 26 | return false; |
286 | 26 | } |
287 | | |
288 | 489 | if (!fr_dns_marker[offset]) { |
289 | 3 | DECODE_FAIL(POINTER_TO_NON_LABEL); |
290 | 3 | return false; |
291 | 3 | } |
292 | | |
293 | | /* |
294 | | * A compressed pointer is the end of the current label. |
295 | | */ |
296 | 486 | p += 2; |
297 | 486 | break; |
298 | 489 | } |
299 | | |
300 | | /* |
301 | | * 0b01 and 0b10 are forbidden |
302 | | */ |
303 | 7.40k | if (*p > 63) { |
304 | 13 | DECODE_FAIL(INVALID_POINTER); |
305 | 13 | return false; |
306 | 13 | } |
307 | | |
308 | | /* |
309 | | * It must be a length byte, which doesn't cause overflow. |
310 | | */ |
311 | 7.38k | if ((p + *p + 1) > end) { |
312 | 13 | DECODE_FAIL(LABEL_OVERFLOWS_PACKET); |
313 | 13 | return false; |
314 | 13 | } |
315 | | |
316 | | /* |
317 | | * Total length of labels can't be too high. |
318 | | */ |
319 | 7.37k | len += *p; |
320 | 7.37k | if (len >= 256) { |
321 | 1 | DECODE_FAIL(LABEL_TOO_LONG); |
322 | 1 | return false; |
323 | 1 | } |
324 | | |
325 | | /* |
326 | | * Remember that this is where we have a |
327 | | * label. |
328 | | */ |
329 | 7.37k | fr_dns_marker[p - packet] = 1; |
330 | | |
331 | | /* |
332 | | * Go to the next label. |
333 | | */ |
334 | 7.37k | p += *p + 1; |
335 | 7.37k | } |
336 | | |
337 | 50.7k | if (qdcount) { |
338 | | /* |
339 | | * qtype + qclass |
340 | | */ |
341 | 44.0k | if ((p + 4) > end) { |
342 | 5 | DECODE_FAIL(MISSING_QD_HEADER); |
343 | 5 | return false; |
344 | 5 | } |
345 | | |
346 | 44.0k | p += 4; |
347 | 44.0k | qdcount--; |
348 | 44.0k | goto next; |
349 | 44.0k | } |
350 | | |
351 | | /* |
352 | | * type (2) + class (2) + TTL (4) |
353 | | * |
354 | | * These are overloaded for the OPT RR |
355 | | * and possibly others, but the basic |
356 | | * idea is the same. |
357 | | */ |
358 | 6.74k | if ((p + 8) > end) { |
359 | 14 | DECODE_FAIL(MISSING_RR_HEADER); |
360 | 14 | return false; |
361 | 14 | } |
362 | 6.72k | is_opt = (p[0] == 0) && (p[1] == 41); |
363 | 6.72k | p += 8; |
364 | | |
365 | | /* |
366 | | * rr_len |
367 | | */ |
368 | 6.72k | if ((p + 2) > end) { |
369 | 35 | DECODE_FAIL(MISSING_RR_LEN); |
370 | 35 | return false; |
371 | 35 | } |
372 | | |
373 | | /* |
374 | | * @todo - RFC2136 allows RDLENGTH=0 for many cases. |
375 | | */ |
376 | 6.69k | len = fr_nbo_to_uint16(p); |
377 | 6.69k | if (!is_opt && (len == 0)) { |
378 | 5 | DECODE_FAIL(ZERO_RR_LEN); |
379 | 5 | return false; |
380 | 5 | } |
381 | | |
382 | 6.68k | p += 2; |
383 | 6.68k | if ((p + len) > end) { |
384 | 27 | DECODE_FAIL(RR_OVERFLOWS_PACKET); |
385 | 27 | return false; |
386 | 27 | } |
387 | | |
388 | | /* |
389 | | * Verify the TLVs, too. |
390 | | */ |
391 | 6.66k | if (is_opt && !fr_dns_tlv_ok(p, p + len, reason)) { |
392 | 12 | return false; |
393 | 12 | } |
394 | | |
395 | 6.64k | p += len; |
396 | | |
397 | 50.6k | next: |
398 | 50.6k | count++; |
399 | | |
400 | 50.6k | if (count > expected) { |
401 | 2 | DECODE_FAIL(TOO_MANY_RRS); |
402 | 2 | return false; |
403 | 2 | } |
404 | 50.6k | } |
405 | | |
406 | 1.38k | if (count != expected) { |
407 | 14 | DECODE_FAIL(TOO_FEW_RRS); |
408 | 14 | return false; |
409 | 14 | } |
410 | | |
411 | | /* |
412 | | * @todo - save fr_dns_marker[] data, so that it can be used by fr_dns_labels_get(). This helps |
413 | | * to reduce redundant work. |
414 | | */ |
415 | | |
416 | 1.37k | DECODE_FAIL(NONE); |
417 | 1.37k | return true; |
418 | 1.38k | } |
419 | | |
420 | | fr_dns_labels_t *fr_dns_labels_get(uint8_t const *packet, size_t packet_len, bool init_mark) |
421 | 1.37k | { |
422 | 1.37k | fr_dns_labels_t *lb = &fr_dns_labels; |
423 | | |
424 | 1.37k | lb->max = 256; |
425 | 1.37k | lb->mark = fr_dns_marker; |
426 | 1.37k | lb->blocks = fr_dns_blocks; |
427 | | |
428 | 1.37k | lb->start = packet; |
429 | 1.37k | lb->end = packet + packet_len; |
430 | | |
431 | 1.37k | lb->num = 1; |
432 | 1.37k | lb->blocks[0].start = DNS_HDR_LEN; |
433 | 1.37k | lb->blocks[0].end = DNS_HDR_LEN; |
434 | | |
435 | 1.37k | if (init_mark) { |
436 | 1.37k | fr_assert(packet_len <= 65535); |
437 | 1.37k | memset(lb->mark, 0, packet_len); |
438 | 1.37k | } |
439 | | |
440 | 1.37k | return lb; |
441 | 1.37k | } |
442 | | |
443 | | /** Resolve/cache attributes in the DNS dictionary |
444 | | * |
445 | | * @return |
446 | | * - 0 on success. |
447 | | * - -1 on failure. |
448 | | */ |
449 | | int fr_dns_global_init(void) |
450 | 4 | { |
451 | 4 | if (instance_count > 0) { |
452 | 2 | instance_count++; |
453 | 2 | return 0; |
454 | 2 | } |
455 | | |
456 | 2 | instance_count++; |
457 | | |
458 | 2 | if (fr_dict_autoload(dns_dict) < 0) { |
459 | 0 | fail: |
460 | 0 | instance_count--; |
461 | 0 | return -1; |
462 | 0 | } |
463 | 2 | if (fr_dict_attr_autoload(dns_dict_attr) < 0) { |
464 | 0 | fr_dict_autofree(dns_dict); |
465 | 0 | goto fail; |
466 | 0 | } |
467 | | |
468 | 2 | instantiated = true; |
469 | 2 | return 0; |
470 | 2 | } |
471 | | |
472 | | void fr_dns_global_free(void) |
473 | 4 | { |
474 | 4 | if (!instantiated) return; |
475 | | |
476 | 4 | fr_assert(instance_count > 0); |
477 | | |
478 | 4 | if (--instance_count > 0) return; |
479 | | |
480 | 2 | fr_dict_autofree(dns_dict); |
481 | 2 | instantiated = false; |
482 | 2 | } |
483 | | |
484 | | static bool attr_valid(fr_dict_attr_t *da) |
485 | 174 | { |
486 | 174 | if (da->flags.array) { |
487 | 0 | fr_strerror_const("The 'array' flag cannot be used with DNS"); |
488 | 0 | return false; |
489 | 0 | } |
490 | | |
491 | 174 | if (da->type == FR_TYPE_ATTR) { |
492 | 0 | fr_strerror_const("The 'attribute' data type cannot be used with DNS"); |
493 | 0 | return false; |
494 | 0 | } |
495 | | |
496 | 174 | if (fr_dns_flag_dns_label_any(da)) { |
497 | 18 | if (da->type != FR_TYPE_STRING) { |
498 | 0 | fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'"); |
499 | 0 | return false; |
500 | 0 | } |
501 | 18 | da->flags.is_known_width = true; /* Lie so we don't trip up the main validation checks */ |
502 | 18 | } |
503 | | |
504 | 174 | switch (da->type) { |
505 | 6 | case FR_TYPE_IP: |
506 | 6 | da->flags.is_known_width = true; |
507 | 6 | break; |
508 | | |
509 | 168 | default: |
510 | 168 | break; |
511 | 174 | } |
512 | | |
513 | 174 | return true; |
514 | 174 | } |
515 | | |
516 | | fr_dict_protocol_t libfreeradius_dns_dict_protocol = { |
517 | | .name = "dns", |
518 | | .default_type_size = 2, |
519 | | .default_type_length = 2, |
520 | | .attr = { |
521 | | .flags = { |
522 | | .table = dns_flags, |
523 | | .table_len = NUM_ELEMENTS(dns_flags), |
524 | | .len = sizeof(fr_dns_attr_flags_t) |
525 | | }, |
526 | | .valid = attr_valid |
527 | | }, |
528 | | |
529 | | .init = fr_dns_global_init, |
530 | | .free = fr_dns_global_free, |
531 | | }; |