/src/freeradius-server/src/lib/util/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: c70875a6d29fa55196fca148a04e28bef48c74ce $ |
19 | | * |
20 | | * @file src/lib/util/decode.c |
21 | | * @brief Generic functions for decoding protocols. |
22 | | * |
23 | | * @copyright 2022 Network RADIUS SAS (legal@networkradius.com) |
24 | | */ |
25 | | #include <freeradius-devel/io/test_point.h> |
26 | | #include <freeradius-devel/util/proto.h> |
27 | | #include <freeradius-devel/util/decode.h> |
28 | | |
29 | | /** Decode an array of values from the network |
30 | | * |
31 | | * @param[in] ctx context to alloc new attributes in. |
32 | | * @param[out] out Where to write the decoded #fr_pair_t |
33 | | * @param[in] parent dictionary entry, must have parent->flags.array set |
34 | | * @param[in] data to parse. |
35 | | * @param[in] data_len of data to parse. |
36 | | * @param[in] decode_ctx passed to decode_value |
37 | | * @param[in] decode_value function to decode one value. |
38 | | * <0 on error - decode error, or OOM |
39 | | * data_len on success |
40 | | */ |
41 | | ssize_t fr_pair_array_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, |
42 | | uint8_t const *data, size_t data_len, |
43 | | void *decode_ctx, fr_pair_decode_value_t decode_value) |
44 | 17.8k | { |
45 | 17.8k | uint8_t const *p = data, *end = p + data_len; |
46 | 17.8k | ssize_t slen; |
47 | 17.8k | fr_pair_list_t list; |
48 | | |
49 | 17.8k | fr_pair_list_init(&list); |
50 | | |
51 | 17.8k | FR_PROTO_HEX_DUMP(data, data_len, "fr_pair_array_from_network"); |
52 | | |
53 | 17.8k | if (!fr_cond_assert_msg(parent->flags.array, |
54 | 17.8k | "%s: Internal sanity check failed, attribute \"%s\" does not have array bit set", |
55 | 17.8k | __FUNCTION__, parent->name)) return PAIR_DECODE_FATAL_ERROR; |
56 | | |
57 | | /* |
58 | | * Catch stupidities. |
59 | | */ |
60 | 17.8k | if (data_len == 0) return data_len; |
61 | | |
62 | 823k | while (p < end) { |
63 | 811k | slen = decode_value(ctx, &list, parent, p, (end - p), decode_ctx); |
64 | 811k | if (slen <= 0) { |
65 | 1.46k | fr_pair_list_free(&list); |
66 | 1.46k | return slen - (p - data); |
67 | 1.46k | } |
68 | | |
69 | 809k | p += slen; |
70 | 809k | } |
71 | | |
72 | 12.3k | fr_pair_list_append(out, &list); |
73 | 12.3k | return data_len; |
74 | 13.8k | } |
75 | | |
76 | | /** Create a "raw" pair from malformed network data |
77 | | * |
78 | | * @param[in] ctx context to alloc new attributes in. |
79 | | * @param[out] out Where to write the decoded #fr_pair_t |
80 | | * @param[in] da dictionary entry which it should be |
81 | | * @param[in] data to parse. |
82 | | * @param[in] data_len of data to parse. |
83 | | * <0 on error - decode error, or OOM |
84 | | * data_len on success |
85 | | */ |
86 | | ssize_t fr_pair_raw_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *da, |
87 | | uint8_t const *data, size_t data_len) |
88 | 168k | { |
89 | 168k | ssize_t slen; |
90 | 168k | fr_pair_t *vp; |
91 | 168k | fr_dict_attr_t *unknown; |
92 | 168k | fr_dict_attr_t const *child; |
93 | | |
94 | 168k | #if defined(STATIC_ANALYZER) || !defined(NDEBUG) |
95 | 168k | if (!da->parent) return -1; /* stupid static analyzers */ |
96 | 168k | #endif |
97 | | |
98 | 168k | FR_PROTO_TRACE("Creating Raw attribute %s", da->name); |
99 | 168k | FR_PROTO_HEX_DUMP(data, data_len, "fr_pair_raw_from_network"); |
100 | | |
101 | | /* |
102 | | * Build an unknown attr of the entire data. |
103 | | */ |
104 | 168k | unknown = fr_dict_attr_unknown_raw_afrom_da(ctx, da); |
105 | 168k | if (!unknown) return -1; |
106 | | |
107 | 160k | vp = fr_pair_afrom_da(ctx, unknown); /* makes a copy of 'unknown' */ |
108 | 160k | child = unknown; |
109 | 160k | fr_dict_attr_unknown_free(&child); /* const issues */ |
110 | 160k | if (!vp) return -1; |
111 | | |
112 | | /* |
113 | | * Don't bother getting data from the network if there's no data. |
114 | | */ |
115 | 160k | if (data_len > 0) { |
116 | 129k | slen = fr_value_box_from_network(vp, &vp->data, vp->da->type, vp->da, |
117 | 129k | &FR_DBUFF_TMP(data, data_len), data_len, true); |
118 | 129k | if (slen < 0) { |
119 | 21.0k | talloc_free(vp); |
120 | 21.0k | return slen; |
121 | 21.0k | } |
122 | 129k | } |
123 | | |
124 | | /* |
125 | | * Raw VPs are always tainted. |
126 | | */ |
127 | 139k | vp->vp_tainted = true; |
128 | 139k | fr_pair_append(out, vp); |
129 | | |
130 | 139k | return data_len; |
131 | 160k | } |
132 | | |
133 | | |
134 | | /** Decode a list of pairs from the network |
135 | | * |
136 | | * @param[in] ctx context to alloc new attributes in. |
137 | | * @param[out] out Where to write the decoded #fr_pair_t |
138 | | * @param[in] parent dictionary entry, must have parent->flags.array set |
139 | | * @param[in] data to parse. |
140 | | * @param[in] data_len of data to parse. |
141 | | * @param[in] decode_ctx passed to decode_tlv |
142 | | * @param[in] decode_tlv function to decode one attribute / option / tlv |
143 | | * @param[in] verify_tlvs simple function to see if the TLVs are even vaguely well-formed |
144 | | * @param[in] nested whether or not we create nested VPs. |
145 | | * <0 on error - decode error, or OOM |
146 | | * data_len on success |
147 | | * |
148 | | * The decode_tlv function should return an error if the option is |
149 | | * malformed. In that case, the entire list of pairs is thrown away, |
150 | | * and a "raw" attribute is created which contains the entire |
151 | | * data_len. |
152 | | * |
153 | | * If the value is malformed, then the decode_tlv function should call |
154 | | * fr_pair_raw_from_network() on the value, and return a positive value. |
155 | | */ |
156 | | ssize_t fr_pair_tlvs_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out, |
157 | | fr_dict_attr_t const *parent, |
158 | | uint8_t const *data, size_t const data_len, |
159 | | void *decode_ctx, fr_pair_decode_value_t decode_tlv, |
160 | | fr_pair_tlvs_verify_t verify_tlvs, |
161 | | bool nested) |
162 | 22.4k | { |
163 | 22.4k | uint8_t const *p, *end; |
164 | 22.4k | fr_pair_list_t tlvs, *list; |
165 | 22.4k | fr_pair_t *vp = NULL; |
166 | 22.4k | TALLOC_CTX *child_ctx; |
167 | | |
168 | 22.4k | FR_PROTO_HEX_DUMP(data, data_len, "fr_pair_tlvs_from_network"); |
169 | | |
170 | 22.4k | switch (parent->type) { |
171 | 15.8k | case FR_TYPE_TLV: |
172 | 22.4k | case FR_TYPE_VENDOR: |
173 | 22.4k | case FR_TYPE_STRUCT: |
174 | 22.4k | break; |
175 | | |
176 | 0 | default: |
177 | 0 | fr_strerror_printf("Internal sanity check failed, attribute \"%s\" has unexpected data type '%s'", |
178 | 0 | parent->name, fr_type_to_str(parent->type)); |
179 | 0 | return PAIR_DECODE_FATAL_ERROR; |
180 | 22.4k | } |
181 | | |
182 | | /* |
183 | | * Do a quick sanity check to see if the TLVs are at all OK. |
184 | | */ |
185 | 22.4k | if (verify_tlvs && !verify_tlvs(data, data_len)) return -1; |
186 | | |
187 | 19.3k | p = data; |
188 | 19.3k | end = data + data_len; |
189 | | |
190 | 19.3k | if (!nested) { |
191 | 14.4k | fr_pair_list_init(&tlvs); |
192 | 14.4k | list = &tlvs; |
193 | 14.4k | child_ctx = ctx; |
194 | 14.4k | } else { |
195 | 4.82k | vp = fr_pair_afrom_da(ctx, parent); |
196 | 4.82k | if (!vp) return PAIR_DECODE_OOM; |
197 | 4.82k | list = &vp->vp_group; |
198 | 4.82k | child_ctx = vp; |
199 | 4.82k | } |
200 | | |
201 | 75.3k | while (p < end) { |
202 | 63.4k | ssize_t slen; |
203 | | |
204 | 63.4k | slen = decode_tlv(child_ctx, list, parent, p, (end - p), decode_ctx); |
205 | 63.4k | if (slen <= 0) { |
206 | 7.42k | FR_PROTO_TRACE(" tlv decode failed at offset %zu - %s", (size_t) (p - data), fr_strerror()); |
207 | 7.42k | fr_pair_list_free(list); |
208 | 7.42k | talloc_free(vp); |
209 | | |
210 | | /* |
211 | | * Don't decode it as raw. We don't know how the TLVs are structured, so the |
212 | | * only da we have is the parent. The output has to in the parent with a child |
213 | | * da. So if we create a raw attribute here, then we have the raw attribute of |
214 | | * da==parent going into the parent, which is wrong. |
215 | | */ |
216 | 7.42k | return PAIR_DECODE_FATAL_ERROR; |
217 | 7.42k | } |
218 | | |
219 | 56.0k | p += slen; |
220 | 56.0k | } |
221 | | |
222 | 11.8k | if (!nested) { |
223 | 7.60k | fr_pair_list_append(out, &tlvs); |
224 | 7.60k | } else { |
225 | 4.28k | fr_pair_append(out, vp); |
226 | 4.28k | } |
227 | | |
228 | 11.8k | return data_len; |
229 | 19.3k | } |
230 | | |
231 | | |
232 | | /** Decode a DNS label or a list of DNS labels from the network |
233 | | * |
234 | | * @param[in] ctx context to alloc new attributes in. |
235 | | * @param[out] out Where to write the decoded #fr_pair_t |
236 | | * @param[in] parent dictionary entry, must have parent->flags.array set |
237 | | * @param[in] start of the DNS labels to decode |
238 | | * @param[in] data to parse. |
239 | | * @param[in] data_len of data to parse. |
240 | | * @param[in] lb struct to help with decoding packets. |
241 | | * @param[in] exact whether the labels should entirely fill the buffer. |
242 | | * @return |
243 | | * <0 on error - decode error, or OOM |
244 | | * data_len on success |
245 | | * |
246 | | * DNS labels exist in many protocols, and we also have src/lib/dns.c, so we might |
247 | | * as well put a common function here, too. |
248 | | * |
249 | | * This function assumes that the DNS label or labels take up all of the |
250 | | * input. If they do not, then the decoded DNS labels are freed, and |
251 | | * a raw attribute is returned instead. |
252 | | */ |
253 | | ssize_t fr_pair_dns_labels_from_network(TALLOC_CTX *ctx, fr_pair_list_t *out, |
254 | | fr_dict_attr_t const *parent, uint8_t const *start, |
255 | | uint8_t const *data, size_t const data_len, fr_dns_labels_t *lb, bool exact) |
256 | 48.7k | { |
257 | 48.7k | ssize_t slen; |
258 | 48.7k | size_t total, labels_len; |
259 | 48.7k | fr_pair_t *vp; |
260 | 48.7k | uint8_t const *next = data; |
261 | 48.7k | fr_pair_list_t tmp; |
262 | | |
263 | 48.7k | FR_PROTO_HEX_DUMP(data, data_len, "fr_pair_dns_labels_from_network"); |
264 | | |
265 | 48.7k | fr_pair_list_init(&tmp); |
266 | | |
267 | | /* |
268 | | * This function handles both single-valued and array |
269 | | * types. It's just easier that way. |
270 | | */ |
271 | 48.7k | if (!parent->flags.array) { |
272 | | /* |
273 | | * Decode starting at "NEXT", but allowing decodes from the start of the packet. |
274 | | */ |
275 | 38.5k | slen = fr_dns_label_uncompressed_length(start, data, data_len, &next, lb); |
276 | 38.5k | if (slen <= 0) { |
277 | 6.65k | FR_PROTO_TRACE("ERROR - uncompressed length failed"); |
278 | 6.65k | goto raw; |
279 | 6.65k | } |
280 | | |
281 | 31.8k | labels_len = next - data; /* decode only what we've found */ |
282 | 31.8k | } else { |
283 | | /* |
284 | | * Get the length of the entire set of labels, up |
285 | | * to (and including) the final 0x00. |
286 | | * |
287 | | * If any of the labels point outside of this |
288 | | * area, OR they are otherwise invalid, then that's an error. |
289 | | */ |
290 | 10.2k | slen = fr_dns_labels_network_verify(start, data, data_len, data, lb); |
291 | 10.2k | if (slen <= 0) { |
292 | 5.99k | FR_PROTO_TRACE("ERROR - network verify failed"); |
293 | 5.99k | goto raw; |
294 | 5.99k | } |
295 | | |
296 | 4.25k | labels_len = slen; |
297 | 4.25k | } |
298 | | |
299 | | /* |
300 | | * The labels MUST fill the entire buffer. |
301 | | */ |
302 | 36.0k | if (exact && (labels_len != data_len)) { |
303 | 807 | FR_PROTO_TRACE("ERROR - labels_len %zu != data_len %zu", labels_len, data_len); |
304 | 13.4k | raw: |
305 | 13.4k | return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
306 | 807 | } |
307 | | |
308 | | /* |
309 | | * Loop over the input buffer, decoding the labels one by |
310 | | * one. |
311 | | * |
312 | | * @todo - put the labels into a child cursor, and then |
313 | | * merge them only if it succeeds. That doesn't seem to |
314 | | * work for some reason, and I don't have time to debug |
315 | | * it right now. So... let's leave it. |
316 | | */ |
317 | 72.6k | for (total = 0; total < labels_len; total += slen) { |
318 | 37.3k | vp = fr_pair_afrom_da(ctx, parent); |
319 | 37.3k | if (!vp) return PAIR_DECODE_OOM; |
320 | | |
321 | | /* |
322 | | * Having verified the input above, this next |
323 | | * function should never fail unless there's a |
324 | | * bug in the code. |
325 | | */ |
326 | 37.3k | slen = fr_dns_label_to_value_box(vp, &vp->data, data, labels_len, data + total, true, lb); |
327 | 37.3k | if (slen <= 0) { |
328 | 0 | FR_PROTO_TRACE("ERROR - failed decoding DNS label at with %zu error %zd", total, slen); |
329 | 0 | talloc_free(vp); |
330 | 0 | fr_pair_list_free(&tmp); |
331 | 0 | goto raw; |
332 | 0 | } |
333 | | |
334 | 37.3k | fr_pair_append(&tmp, vp); |
335 | 37.3k | } |
336 | | |
337 | 35.2k | fr_pair_list_append(out, &tmp); |
338 | 35.2k | return labels_len; |
339 | 35.2k | } |
340 | | |
341 | | /** Generic decode value. |
342 | | * |
343 | | */ |
344 | | ssize_t fr_pair_decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, |
345 | | uint8_t const *data, size_t const data_len, UNUSED void *decode_ctx) |
346 | 24.5k | { |
347 | 24.5k | fr_pair_t *vp; |
348 | 24.5k | ssize_t slen; |
349 | | |
350 | 24.5k | if (!fr_type_is_leaf(parent->type)) { |
351 | 0 | FR_PROTO_TRACE("Cannot use generic decoder for data type %s", fr_type_to_str(parent->type)); |
352 | 0 | fr_strerror_printf("Cannot decode data type %s", fr_type_to_str(parent->type)); |
353 | 0 | return -1; |
354 | 0 | } |
355 | | |
356 | 24.5k | vp = fr_pair_afrom_da(ctx, parent); |
357 | 24.5k | if (!vp) return PAIR_DECODE_OOM; |
358 | | |
359 | | /* |
360 | | * No protocol-specific data types here. |
361 | | * |
362 | | * If we can't decode this field, then the entire |
363 | | * structure is treated as a raw blob. |
364 | | */ |
365 | 24.5k | slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, |
366 | 24.5k | &FR_DBUFF_TMP(data, data_len), data_len, true); |
367 | 24.5k | if (slen <= 0) { |
368 | 0 | FR_PROTO_TRACE("failed decoding child VP %s", vp->da->name); |
369 | 0 | talloc_free(vp); |
370 | 0 | return fr_pair_raw_from_network(ctx, out, parent, data, data_len); |
371 | 0 | } |
372 | | |
373 | 24.5k | fr_pair_append(out, vp); |
374 | | |
375 | 24.5k | return slen; |
376 | 24.5k | } |