/src/freeradius-server/src/protocols/dns/decode.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: 92a7088f3c23773e4746d8e63ef57de0795f5d51 $ |
19 | | * |
20 | | * @file protocols/dns/decode.c |
21 | | * @brief Functions to decode DNS packets. |
22 | | * |
23 | | * @author Alan DeKok (aland@freeradius.org) |
24 | | * |
25 | | * @copyright 2021 The FreeRADIUS server project |
26 | | * @copyright 2021 NetworkRADIUS SARL (legal@networkradius.com) |
27 | | */ |
28 | | #include <freeradius-devel/io/test_point.h> |
29 | | #include <freeradius-devel/util/dns.h> |
30 | | #include <freeradius-devel/util/proto.h> |
31 | | #include <freeradius-devel/util/struct.h> |
32 | | |
33 | | #include "dns.h" |
34 | | #include "attrs.h" |
35 | | |
36 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
37 | | fr_dict_attr_t const *parent, |
38 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
39 | | |
40 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
41 | | fr_dict_attr_t const *parent, |
42 | | uint8_t const *data, size_t const data_len, void *decode_ctx); |
43 | | |
44 | | static ssize_t decode_tlv_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
45 | | fr_dict_attr_t const *parent, |
46 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
47 | 691 | { |
48 | 691 | return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true); |
49 | 691 | } |
50 | | |
51 | | |
52 | | static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out, |
53 | | fr_dict_attr_t const *parent, |
54 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
55 | 165k | { |
56 | 165k | if ((parent->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(parent)) { |
57 | 49.1k | fr_dns_ctx_t *packet_ctx = decode_ctx; |
58 | | |
59 | 49.1k | return fr_pair_dns_labels_from_network(ctx, out, parent, packet_ctx->packet, data, data_len, packet_ctx->lb, false); |
60 | 49.1k | } |
61 | | |
62 | 116k | return decode_value(ctx, out, parent, data, data_len, decode_ctx); |
63 | 165k | } |
64 | | |
65 | | |
66 | | static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, |
67 | | fr_dict_attr_t const *parent, |
68 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
69 | 123k | { |
70 | 123k | ssize_t slen; |
71 | 123k | fr_pair_t *vp; |
72 | | |
73 | 123k | FR_PROTO_HEX_DUMP(data, data_len, "decode_value"); |
74 | | |
75 | 123k | switch (parent->type) { |
76 | | /* |
77 | | * Address MAY be shorter than 16 bytes. |
78 | | */ |
79 | 0 | case FR_TYPE_IPV6_PREFIX: |
80 | 0 | if (data_len == 0) { |
81 | 3.92k | raw: |
82 | 3.92k | return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
83 | 0 | } |
84 | | |
85 | 0 | vp = fr_pair_afrom_da(ctx, parent); |
86 | 0 | if (!vp) return PAIR_DECODE_OOM; |
87 | 0 | PAIR_ALLOCED(vp); |
88 | | |
89 | | /* |
90 | | * Check values of prefix length, data lengths, etc. |
91 | | */ |
92 | 0 | if (fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent, |
93 | 0 | data[0], data + 1, data_len - 1, |
94 | 0 | (parent->parent->type == FR_TYPE_STRUCT), true) < 0) { |
95 | 0 | talloc_free(vp); |
96 | 0 | goto raw; |
97 | 0 | } |
98 | 0 | break; |
99 | | |
100 | | /* |
101 | | * A bool is encoded as an empty option if it's |
102 | | * true. A bool is omitted entirely if it's |
103 | | * false. |
104 | | */ |
105 | 657 | case FR_TYPE_BOOL: |
106 | 657 | if (data_len != 0) goto raw; |
107 | 365 | vp = fr_pair_afrom_da(ctx, parent); |
108 | 365 | if (!vp) return PAIR_DECODE_OOM; |
109 | 365 | PAIR_ALLOCED(vp); |
110 | | |
111 | 365 | vp->vp_bool = true; |
112 | 365 | break; |
113 | | |
114 | 3.09k | case FR_TYPE_STRUCT: |
115 | 3.09k | slen = fr_struct_from_network(ctx, out, parent, data, data_len, |
116 | 3.09k | decode_ctx, decode_value_trampoline, NULL); |
117 | 3.09k | if (slen < 0) return slen; |
118 | 3.02k | return data_len; |
119 | | |
120 | 0 | case FR_TYPE_GROUP: |
121 | 0 | return PAIR_DECODE_FATAL_ERROR; /* not supported */ |
122 | | |
123 | 119k | default: |
124 | 119k | vp = fr_pair_afrom_da(ctx, parent); |
125 | 119k | if (!vp) return PAIR_DECODE_OOM; |
126 | 119k | PAIR_ALLOCED(vp); |
127 | | |
128 | 119k | if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
129 | 119k | &FR_DBUFF_TMP(data, data_len), data_len, true) < 0) { |
130 | 3.63k | FR_PROTO_TRACE("failed decoding?"); |
131 | 3.63k | talloc_free(vp); |
132 | 3.63k | goto raw; |
133 | 3.63k | } |
134 | 116k | break; |
135 | 123k | } |
136 | | |
137 | 116k | vp->vp_tainted = true; |
138 | 116k | fr_pair_append(out, vp); |
139 | 116k | return data_len; |
140 | 123k | } |
141 | | |
142 | | |
143 | 11.4k | #define DNS_GET_OPTION_NUM(_x) fr_nbo_to_uint16(_x) |
144 | 11.4k | #define DNS_GET_OPTION_LEN(_x) fr_nbo_to_uint16((_x) + 2) |
145 | | |
146 | | static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out, |
147 | | fr_dict_attr_t const *parent, |
148 | | uint8_t const *data, size_t const data_len, void *decode_ctx) |
149 | 11.4k | { |
150 | 11.4k | unsigned int option; |
151 | 11.4k | size_t len; |
152 | 11.4k | ssize_t slen; |
153 | 11.4k | fr_dict_attr_t const *da; |
154 | 11.4k | fr_dns_ctx_t *packet_ctx = decode_ctx; |
155 | | |
156 | | #ifdef STATIC_ANALYZER |
157 | | if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR; |
158 | | #endif |
159 | | |
160 | | /* |
161 | | * Must have at least an option header. |
162 | | */ |
163 | 11.4k | if (data_len < 4) { |
164 | 7 | fr_strerror_printf("%s: Insufficient data", __FUNCTION__); |
165 | 7 | return -(data_len); |
166 | 7 | } |
167 | | |
168 | 11.4k | option = DNS_GET_OPTION_NUM(data); |
169 | 11.4k | len = DNS_GET_OPTION_LEN(data); |
170 | 11.4k | if (len > (data_len - 4)) { |
171 | 26 | fr_strerror_printf("%s: Option overflows input. " |
172 | 26 | "Optional length must be less than %zu bytes, got %zu bytes", |
173 | 26 | __FUNCTION__, data_len - 4, len); |
174 | 26 | return PAIR_DECODE_FATAL_ERROR; |
175 | 26 | } |
176 | | |
177 | 11.4k | FR_PROTO_HEX_DUMP(data, len + 4, "decode_option"); |
178 | | |
179 | 11.4k | da = fr_dict_attr_child_by_num(parent, option); |
180 | 11.4k | if (!da) { |
181 | 4.10k | da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option); |
182 | 4.10k | if (!da) return PAIR_DECODE_FATAL_ERROR; |
183 | 4.10k | } |
184 | 11.4k | FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name); |
185 | | |
186 | 11.4k | if ((da->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(da)) { |
187 | 0 | slen = fr_pair_dns_labels_from_network(ctx, out, da, packet_ctx->packet, data + 4, len, packet_ctx->lb, true); |
188 | |
|
189 | 11.4k | } else { |
190 | 11.4k | slen = decode_value(ctx, out, da, data + 4, len, decode_ctx); |
191 | 11.4k | } |
192 | 11.4k | fr_dict_attr_unknown_free(&da); |
193 | | |
194 | 11.4k | if (slen < 0) return slen; |
195 | | |
196 | 11.3k | return len + 4; |
197 | 11.4k | } |
198 | | |
199 | | static ssize_t decode_record(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *attr, |
200 | | uint8_t const *rr, uint8_t const *end, |
201 | | fr_dns_ctx_t *packet_ctx, uint8_t const *counter) |
202 | 4.21k | { |
203 | 4.21k | unsigned int i, count; |
204 | 4.21k | uint8_t const *p = rr; |
205 | | |
206 | | /* |
207 | | * The header has a count of how many records we need to decode. |
208 | | */ |
209 | 4.21k | count = fr_nbo_to_uint16(counter); |
210 | 4.21k | FR_PROTO_TRACE("Decoding %u of %s", count, attr->name); |
211 | | |
212 | | /* coverity[tainted_data] */ |
213 | 52.3k | for (i = 0; (i < count) && (p < end); i++) { |
214 | 48.1k | ssize_t slen; |
215 | | |
216 | 48.1k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - %s %u/%u", attr->name, i, count); |
217 | | |
218 | 48.1k | slen = fr_struct_from_network(ctx, out, attr, p, end - p, |
219 | 48.1k | packet_ctx, decode_value_trampoline, decode_tlv_trampoline); |
220 | 48.1k | if (slen < 0) return slen; |
221 | 48.1k | if (!slen) break; |
222 | | |
223 | 48.1k | fr_assert(slen <= (end - p)); |
224 | | |
225 | 48.1k | p += slen; |
226 | 48.1k | } |
227 | | |
228 | 4.19k | return p - rr; |
229 | 4.21k | } |
230 | | |
231 | | /** Decode a DNS packet |
232 | | * |
233 | | */ |
234 | | ssize_t fr_dns_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packet, size_t packet_len, fr_dns_ctx_t *packet_ctx) |
235 | 1.05k | { |
236 | 1.05k | ssize_t slen; |
237 | 1.05k | uint8_t const *p, *end; |
238 | | |
239 | 1.05k | if (packet_len < DNS_HDR_LEN) return 0; |
240 | | |
241 | | /* |
242 | | * @todo - synthesize Packet-Type from the various fields. |
243 | | */ |
244 | | |
245 | 1.05k | FR_PROTO_HEX_DUMP(packet, packet_len, "fr_dns_decode"); |
246 | | |
247 | | /* |
248 | | * Decode the header. |
249 | | */ |
250 | 1.05k | slen = fr_struct_from_network(ctx, out, attr_dns_packet, packet, DNS_HDR_LEN, |
251 | 1.05k | packet_ctx, decode_value_trampoline, NULL); /* no TLVs in the header */ |
252 | 1.05k | if (slen < 0) { |
253 | 0 | fr_strerror_printf("Failed decoding DNS header - %s", fr_strerror()); |
254 | 0 | return slen; |
255 | 0 | } |
256 | 1.05k | fr_assert(slen == DNS_HDR_LEN); |
257 | | |
258 | 1.05k | p = packet + DNS_HDR_LEN; |
259 | 1.05k | end = packet + packet_len; |
260 | 1.05k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after header"); |
261 | | |
262 | 1.05k | slen = decode_record(ctx, out, attr_dns_question, p, end, packet_ctx, packet + 4); |
263 | 1.05k | if (slen < 0) { |
264 | 0 | fr_strerror_printf("Failed decoding questions - %s", fr_strerror()); |
265 | | /* coverity[return_overflow] */ |
266 | 0 | return slen - (p - packet); |
267 | 0 | } |
268 | 1.05k | p += slen; |
269 | 1.05k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of questions", slen); |
270 | | |
271 | 1.05k | slen = decode_record(ctx, out, attr_dns_rr, p, end, packet_ctx, packet + 6); |
272 | 1.05k | if (slen < 0) { |
273 | 2 | fr_strerror_printf("Failed decoding RRs - %s", fr_strerror()); |
274 | | /* coverity[return_overflow] */ |
275 | 2 | return slen - (p - packet); |
276 | 2 | } |
277 | 1.05k | p += slen; |
278 | 1.05k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of RRs", slen); |
279 | | |
280 | 1.05k | slen = decode_record(ctx, out, attr_dns_ns, p, end, packet_ctx, packet + 8); |
281 | 1.05k | if (slen < 0) { |
282 | 8 | fr_strerror_printf("Failed decoding NS - %s", fr_strerror()); |
283 | | /* coverity[return_overflow] */ |
284 | 8 | return slen - (p - packet); |
285 | 8 | } |
286 | 1.04k | p += slen; |
287 | 1.04k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of NS", slen); |
288 | | |
289 | 1.04k | slen = decode_record(ctx, out, attr_dns_ar, p, end, packet_ctx, packet + 10); |
290 | 1.04k | if (slen < 0) { |
291 | 8 | fr_strerror_printf("Failed decoding additional records - %s", fr_strerror()); |
292 | | /* coverity[return_overflow] */ |
293 | 8 | return slen - (p - packet); |
294 | 8 | } |
295 | 1.03k | FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of additional records", slen); |
296 | | |
297 | | // p += slen; |
298 | | |
299 | 1.03k | return packet_len; |
300 | 1.04k | } |
301 | | |
302 | | /** Decode DNS RR |
303 | | * |
304 | | * @param[in] ctx context to alloc new attributes in. |
305 | | * @param[in,out] out Where to write the decoded options. |
306 | | * @param[in] parent to lookup attributes in. |
307 | | * @param[in] data to parse. |
308 | | * @param[in] data_len of data to parse. |
309 | | * @param[in] decode_ctx Unused. |
310 | | */ |
311 | | static ssize_t decode_rr(TALLOC_CTX *ctx, fr_pair_list_t *out, UNUSED fr_dict_attr_t const *parent, |
312 | | uint8_t const *data, size_t data_len, void *decode_ctx) |
313 | 0 | { |
314 | 0 | ssize_t slen; |
315 | 0 | fr_dns_ctx_t *packet_ctx = (fr_dns_ctx_t *) decode_ctx; |
316 | |
|
317 | 0 | FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len); |
318 | |
|
319 | 0 | if (data_len == 0) return 0; |
320 | | |
321 | | /* |
322 | | * This function is only used for testing, so update decode_ctx |
323 | | */ |
324 | 0 | packet_ctx->packet = data; |
325 | 0 | packet_ctx->packet_len = data_len; |
326 | |
|
327 | 0 | FR_PROTO_HEX_DUMP(data, data_len, NULL); |
328 | | |
329 | | /* |
330 | | * There should be at least room for the RR header |
331 | | */ |
332 | 0 | if (data_len < 9) { |
333 | 0 | fr_strerror_printf("%s: Insufficient data", __FUNCTION__); |
334 | 0 | return -1; |
335 | 0 | } |
336 | | |
337 | 0 | slen = fr_struct_from_network(ctx, out, attr_dns_rr, data, data_len, |
338 | 0 | decode_ctx, decode_value_trampoline, decode_tlv_trampoline); |
339 | 0 | if (slen < 0) return slen; |
340 | | |
341 | 0 | FR_PROTO_TRACE("decoding option complete, returning %zd byte(s)", slen); |
342 | 0 | return slen; |
343 | 0 | } |
344 | | |
345 | | /* |
346 | | * Test points |
347 | | */ |
348 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
349 | | UNUSED fr_dict_attr_t const *root_da) |
350 | 2.90k | { |
351 | 2.90k | fr_dns_ctx_t *test_ctx; |
352 | | |
353 | 2.90k | test_ctx = talloc_zero(ctx, fr_dns_ctx_t); |
354 | | |
355 | 2.90k | test_ctx->tmp_ctx = talloc(test_ctx, uint8_t); |
356 | 2.90k | *out = test_ctx; |
357 | | |
358 | 2.90k | return 0; |
359 | 2.90k | } |
360 | | |
361 | | fr_table_num_ordered_t fr_dns_reason_fail_table[] = { |
362 | | { L("none"), FR_DNS_DECODE_FAIL_NONE }, |
363 | | { L("unknown opcode"), FR_DNS_DECODE_FAIL_UNKNOWN_OPCODE }, |
364 | | { L("packet is smaller than DNS header"), FR_DNS_DECODE_FAIL_MIN_LENGTH_PACKET }, |
365 | | { L("packet is larger than 65535"), FR_DNS_DECODE_FAIL_MAX_LENGTH_PACKET }, |
366 | | { L("expected query / answer, got answer / query"), FR_DNS_DECODE_FAIL_UNEXPECTED }, |
367 | | { L("no 'questions' in query packet"), FR_DNS_DECODE_FAIL_NO_QUESTIONS }, |
368 | | { L("unexpected answers in query packet"), FR_DNS_DECODE_FAIL_ANSWERS_IN_QUESTION }, |
369 | | { L("unexpected NS records in query packet"), FR_DNS_DECODE_FAIL_NS_IN_QUESTION }, |
370 | | { L("invalid label for resource record"), FR_DNS_DECODE_FAIL_INVALID_RR_LABEL }, |
371 | | { L("missing resource record header"), FR_DNS_DECODE_FAIL_MISSING_RR_HEADER }, |
372 | | { L("missing resource record length field"), FR_DNS_DECODE_FAIL_MISSING_RR_LEN }, |
373 | | { L("resource record length field is zero"), FR_DNS_DECODE_FAIL_ZERO_RR_LEN }, |
374 | | { L("resource record length overflows the packet"), FR_DNS_DECODE_FAIL_RR_OVERFLOWS_PACKET }, |
375 | | { L("more resource records than indicated in header"), FR_DNS_DECODE_FAIL_TOO_MANY_RRS }, |
376 | | { L("fewer resource records than indicated in header"), FR_DNS_DECODE_FAIL_TOO_FEW_RRS }, |
377 | | { L("pointer overflows packet"), FR_DNS_DECODE_FAIL_POINTER_OVERFLOWS_PACKET }, |
378 | | { L("pointer points to packet header"), FR_DNS_DECODE_FAIL_POINTER_TO_HEADER }, |
379 | | { L("pointer does not point to a label"), FR_DNS_DECODE_FAIL_POINTER_TO_NON_LABEL }, |
380 | | { L("pointer creates a loop"), FR_DNS_DECODE_FAIL_POINTER_LOOPS }, |
381 | | { L("invalid pointer"), FR_DNS_DECODE_FAIL_INVALID_POINTER }, |
382 | | { L("label overflows the packet"), FR_DNS_DECODE_FAIL_LABEL_OVERFLOWS_PACKET }, |
383 | | { L("too many characters in label"), FR_DNS_DECODE_FAIL_LABEL_TOO_LONG }, |
384 | | { L("query record header is missing"), FR_DNS_DECODE_FAIL_MISSING_QD_HEADER }, |
385 | | { L("missing TLV header in OPT RR"), FR_DNS_DECODE_FAIL_MISSING_TLV_HEADER }, |
386 | | { L("TLV overflows enclosing RR"), FR_DNS_DECODE_FAIL_TLV_OVERFLOWS_RR }, |
387 | | { L("TC bit indicates truncation"), FR_DNS_DECODE_FAIL_TRUNCATED }, |
388 | | }; |
389 | | size_t fr_dns_reason_fail_table_len = NUM_ELEMENTS(fr_dns_reason_fail_table); |
390 | | |
391 | | static ssize_t decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, void *proto_ctx) |
392 | 1.38k | { |
393 | 1.38k | fr_dns_ctx_t *packet_ctx = proto_ctx; |
394 | 1.38k | fr_dns_decode_fail_t reason; |
395 | | |
396 | 1.38k | if (data_len > 65535) return -1; /* packet is too big */ |
397 | | |
398 | | /* |
399 | | * Allow queries or answers |
400 | | */ |
401 | 1.37k | if (!fr_dns_packet_ok(data, data_len, true, &reason)) { |
402 | 1.05k | if (reason != FR_DNS_DECODE_FAIL_UNEXPECTED) goto fail; |
403 | | |
404 | 930 | if (!fr_dns_packet_ok(data, data_len, false, &reason)) { |
405 | 317 | fail: |
406 | 317 | fr_strerror_printf("DNS packet malformed - %s", |
407 | 317 | fr_table_str_by_value(fr_dns_reason_fail_table, reason, "<INVALID>")); |
408 | 317 | return -1; |
409 | 197 | } |
410 | 930 | } |
411 | | |
412 | 1.05k | packet_ctx->packet = data; |
413 | 1.05k | packet_ctx->packet_len = data_len; |
414 | 1.05k | packet_ctx->lb = fr_dns_labels_get(data, data_len, true); |
415 | 1.05k | fr_assert(packet_ctx->lb != NULL); |
416 | | |
417 | 1.05k | return fr_dns_decode(ctx, out, data, data_len, packet_ctx); |
418 | 1.37k | } |
419 | | |
420 | | /* |
421 | | * Test points |
422 | | */ |
423 | | extern fr_test_point_pair_decode_t dns_tp_decode_pair; |
424 | | fr_test_point_pair_decode_t dns_tp_decode_pair = { |
425 | | .test_ctx = decode_test_ctx, |
426 | | .func = decode_rr |
427 | | }; |
428 | | |
429 | | extern fr_test_point_proto_decode_t dns_tp_decode_proto; |
430 | | fr_test_point_proto_decode_t dns_tp_decode_proto = { |
431 | | .test_ctx = decode_test_ctx, |
432 | | .func = decode_proto |
433 | | }; |