/src/fwupd/libfwupdplugin/fu-cbor-common.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2026 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 3.14M | #define G_LOG_DOMAIN "FuCbor" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-cbor-common.h" |
12 | | #include "fu-cbor-item-private.h" |
13 | | #include "fu-common.h" |
14 | | #include "fu-input-stream.h" |
15 | | |
16 | | typedef struct { |
17 | | guint max_depth; |
18 | | guint max_items; |
19 | | guint max_length; |
20 | | FuInputStream *stream; /* no ref */ |
21 | | gsize offset; |
22 | | } FuCborParseHelper; |
23 | | |
24 | | static FuCborItem * |
25 | | fu_cbor_parse_item(FuCborParseHelper *helper, guint current_depth, GError **error); |
26 | | |
27 | | static FuCborItem * |
28 | | fu_cbor_parse_map(FuCborParseHelper *helper, guint64 len, guint current_depth, GError **error) |
29 | 235k | { |
30 | 235k | g_autoptr(FuCborItem) item = fu_cbor_item_new_map(); |
31 | | |
32 | | /* sanity check */ |
33 | 235k | if (helper->max_depth > 0 && current_depth > helper->max_depth) { |
34 | 332 | g_set_error(error, |
35 | 332 | FWUPD_ERROR, |
36 | 332 | FWUPD_ERROR_INVALID_DATA, |
37 | 332 | "reached max depth of %u", |
38 | 332 | current_depth); |
39 | 332 | return NULL; |
40 | 332 | } |
41 | 235k | if (helper->max_items > 0 && len > helper->max_items) { |
42 | 853 | g_set_error(error, |
43 | 853 | FWUPD_ERROR, |
44 | 853 | FWUPD_ERROR_INVALID_DATA, |
45 | 853 | "too many items (%u of maximum %u)", |
46 | 853 | (guint)len, |
47 | 853 | helper->max_items); |
48 | 853 | return NULL; |
49 | 853 | } |
50 | | |
51 | 234k | g_debug("map has %u items", (guint)len); |
52 | 780k | for (guint64 i = 0; i < len; i++) { |
53 | 568k | g_autoptr(FuCborItem) item_key = NULL; |
54 | 568k | g_autoptr(FuCborItem) item_val = NULL; |
55 | 568k | item_key = fu_cbor_parse_item(helper, current_depth, error); |
56 | 568k | if (item_key == NULL) |
57 | 10.4k | return NULL; |
58 | 557k | item_val = fu_cbor_parse_item(helper, current_depth, error); |
59 | 557k | if (item_val == NULL) |
60 | 11.6k | return NULL; |
61 | 545k | if (!fu_cbor_item_map_append(item, item_key, item_val, error)) |
62 | 0 | return NULL; |
63 | 545k | } |
64 | | |
65 | | /* success */ |
66 | 212k | return g_steal_pointer(&item); |
67 | 234k | } |
68 | | |
69 | | static FuCborItem * |
70 | | fu_cbor_parse_array(FuCborParseHelper *helper, guint64 len, guint current_depth, GError **error) |
71 | 52.1k | { |
72 | 52.1k | g_autoptr(FuCborItem) item = fu_cbor_item_new_array(); |
73 | | |
74 | | /* sanity check */ |
75 | 52.1k | if (helper->max_depth > 0 && current_depth > helper->max_depth) { |
76 | 287 | g_set_error(error, |
77 | 287 | FWUPD_ERROR, |
78 | 287 | FWUPD_ERROR_INVALID_DATA, |
79 | 287 | "reached max depth of %u", |
80 | 287 | current_depth); |
81 | 287 | return NULL; |
82 | 287 | } |
83 | 51.8k | if (helper->max_items > 0 && len > helper->max_items) { |
84 | 847 | g_set_error(error, |
85 | 847 | FWUPD_ERROR, |
86 | 847 | FWUPD_ERROR_INVALID_DATA, |
87 | 847 | "too many items (%u of maximum %u)", |
88 | 847 | (guint)len, |
89 | 847 | helper->max_items); |
90 | 847 | return NULL; |
91 | 847 | } |
92 | | |
93 | 51.0k | g_debug("array has %u items", (guint)len); |
94 | 151k | for (guint64 i = 0; i < len; i++) { |
95 | 114k | g_autoptr(FuCborItem) item_tmp = NULL; |
96 | 114k | item_tmp = fu_cbor_parse_item(helper, current_depth, error); |
97 | 114k | if (item_tmp == NULL) |
98 | 13.9k | return NULL; |
99 | 100k | if (!fu_cbor_item_array_append(item, item_tmp, error)) |
100 | 0 | return NULL; |
101 | 100k | } |
102 | | |
103 | | /* success */ |
104 | 37.0k | return g_steal_pointer(&item); |
105 | 51.0k | } |
106 | | |
107 | | static FuCborItem * |
108 | | fu_cbor_parse_item(FuCborParseHelper *helper, guint current_depth, GError **error) |
109 | 1.35M | { |
110 | 1.35M | FuCborTag tag; |
111 | 1.35M | guint64 len = 0; |
112 | 1.35M | guint8 len_short; |
113 | 1.35M | guint8 value8 = 0; |
114 | | |
115 | 1.35M | if (!fu_input_stream_read_u8(helper->stream, helper->offset, &value8, error)) |
116 | 2.78k | return NULL; |
117 | | |
118 | 1.34M | if (!fu_size_checked_inc(&helper->offset, 1, error)) { |
119 | 0 | g_prefix_error_literal(error, "CBOR tag offset overflow: "); |
120 | 0 | return NULL; |
121 | 0 | } |
122 | | |
123 | 1.34M | tag = (value8 & 0b11100000) >> 5; |
124 | 1.34M | g_debug("tag: %u [%s] @0x%x", tag, fu_cbor_tag_to_string(tag), (guint)helper->offset); |
125 | | |
126 | | /* process length */ |
127 | 1.34M | len_short = (guint)(value8 & 0b11111); |
128 | 1.34M | g_debug("len-short: %u", len_short); |
129 | 1.34M | if (len_short <= FU_CBOR_LEN_SHORT_MAX) { |
130 | 1.16M | len = len_short; |
131 | 1.16M | } else if (len_short == FU_CBOR_LEN_EXT8) { |
132 | 127k | if (!fu_input_stream_read_u8(helper->stream, helper->offset, &value8, error)) |
133 | 554 | return NULL; |
134 | 126k | len = value8; |
135 | | |
136 | 126k | if (!fu_size_checked_inc(&helper->offset, 1, error)) { |
137 | 0 | g_prefix_error_literal(error, "CBOR length8 offset overflow: "); |
138 | 0 | return NULL; |
139 | 0 | } |
140 | 126k | } else if (len_short == FU_CBOR_LEN_EXT16) { |
141 | 15.0k | guint16 value16 = 0; |
142 | 15.0k | if (!fu_input_stream_read_u16(helper->stream, |
143 | 15.0k | helper->offset, |
144 | 15.0k | &value16, |
145 | 15.0k | G_BIG_ENDIAN, |
146 | 15.0k | error)) |
147 | 284 | return NULL; |
148 | 14.7k | len = value16; |
149 | | |
150 | 14.7k | if (!fu_size_checked_inc(&helper->offset, 2, error)) { |
151 | 0 | g_prefix_error_literal(error, "CBOR length16 offset overflow: "); |
152 | 0 | return NULL; |
153 | 0 | } |
154 | 44.9k | } else if (len_short == FU_CBOR_LEN_EXT32) { |
155 | 9.81k | guint32 value32 = 0; |
156 | 9.81k | if (!fu_input_stream_read_u32(helper->stream, |
157 | 9.81k | helper->offset, |
158 | 9.81k | &value32, |
159 | 9.81k | G_BIG_ENDIAN, |
160 | 9.81k | error)) |
161 | 414 | return NULL; |
162 | 9.39k | len = value32; |
163 | | |
164 | 9.39k | if (!fu_size_checked_inc(&helper->offset, 4, error)) { |
165 | 0 | g_prefix_error_literal(error, "CBOR length32 offset overflow: "); |
166 | 0 | return NULL; |
167 | 0 | } |
168 | 35.1k | } else if (len_short == FU_CBOR_LEN_EXT64) { |
169 | 33.8k | guint64 value64 = 0; |
170 | 33.8k | if (!fu_input_stream_read_u64(helper->stream, |
171 | 33.8k | helper->offset, |
172 | 33.8k | &value64, |
173 | 33.8k | G_BIG_ENDIAN, |
174 | 33.8k | error)) |
175 | 304 | return NULL; |
176 | 33.5k | if (value64 > G_MAXINT64) { |
177 | 563 | g_set_error_literal(error, |
178 | 563 | FWUPD_ERROR, |
179 | 563 | FWUPD_ERROR_INVALID_DATA, |
180 | 563 | "lengths larger than s64:MAX are not supported"); |
181 | 563 | return NULL; |
182 | 563 | } |
183 | 33.0k | len = value64; |
184 | | |
185 | 33.0k | if (!fu_size_checked_inc(&helper->offset, 8, error)) { |
186 | 0 | g_prefix_error_literal(error, "CBOR length64 offset overflow: "); |
187 | 0 | return NULL; |
188 | 0 | } |
189 | 33.0k | } else if (len_short == FU_CBOR_LEN_INDEFINITE) { |
190 | 590 | g_set_error_literal(error, |
191 | 590 | FWUPD_ERROR, |
192 | 590 | FWUPD_ERROR_INVALID_DATA, |
193 | 590 | "indefinite-length encoding is not supported"); |
194 | 590 | return NULL; |
195 | 684 | } else { |
196 | 684 | g_set_error(error, |
197 | 684 | FWUPD_ERROR, |
198 | 684 | FWUPD_ERROR_INVALID_DATA, |
199 | 684 | "short count %u is invalid", |
200 | 684 | len_short); |
201 | 684 | return NULL; |
202 | 684 | } |
203 | 1.34M | if (len != len_short) |
204 | 165k | g_debug("len: %" G_GUINT64_FORMAT, len); |
205 | | |
206 | | /* process tags */ |
207 | 1.34M | if (tag == FU_CBOR_TAG_POS_INT) |
208 | 798k | return fu_cbor_item_new_integer(len); |
209 | 547k | if (tag == FU_CBOR_TAG_NEG_INT) |
210 | 162k | return fu_cbor_item_new_integer(-1 - (gint64)len); |
211 | 385k | if (tag == FU_CBOR_TAG_STRING) { |
212 | 20.8k | g_autofree gchar *str = NULL; |
213 | 20.8k | if (helper->max_length > 0 && len > helper->max_length) { |
214 | 870 | g_set_error(error, |
215 | 870 | FWUPD_ERROR, |
216 | 870 | FWUPD_ERROR_INVALID_DATA, |
217 | 870 | "string too long (%u of maximum %u)", |
218 | 870 | (guint)len, |
219 | 870 | helper->max_length); |
220 | 870 | return NULL; |
221 | 870 | } |
222 | 19.9k | str = fu_input_stream_read_string(helper->stream, helper->offset, len, error); |
223 | 19.9k | if (str == NULL) |
224 | 7.30k | return NULL; |
225 | 12.6k | if (!fu_size_checked_inc(&helper->offset, len, error)) |
226 | 0 | return NULL; |
227 | 12.6k | return fu_cbor_item_new_string_steal(g_steal_pointer(&str)); |
228 | 12.6k | } |
229 | 364k | if (tag == FU_CBOR_TAG_BYTES) { |
230 | 52.9k | g_autoptr(GBytes) blob = NULL; |
231 | 52.9k | if (helper->max_length > 0 && len > helper->max_length) { |
232 | 819 | g_set_error(error, |
233 | 819 | FWUPD_ERROR, |
234 | 819 | FWUPD_ERROR_INVALID_DATA, |
235 | 819 | "bytes too long (%u of maximum %u)", |
236 | 819 | (guint)len, |
237 | 819 | helper->max_length); |
238 | 819 | return NULL; |
239 | 819 | } |
240 | 52.1k | blob = fu_input_stream_read_bytes(helper->stream, helper->offset, len, NULL, error); |
241 | 52.1k | if (blob == NULL) |
242 | 532 | return NULL; |
243 | 51.6k | if (!fu_size_checked_inc(&helper->offset, len, error)) |
244 | 0 | return NULL; |
245 | 51.6k | return fu_cbor_item_new_bytes(blob); |
246 | 51.6k | } |
247 | 311k | if (tag == FU_CBOR_TAG_SPECIAL) { |
248 | 23.2k | if (len == FU_CBOR_SPECIAL_VALUE_TRUE) |
249 | 5.54k | return fu_cbor_item_new_boolean(TRUE); |
250 | 17.7k | if (len == FU_CBOR_SPECIAL_VALUE_FALSE) |
251 | 8.24k | return fu_cbor_item_new_boolean(FALSE); |
252 | 9.50k | if (len == FU_CBOR_SPECIAL_VALUE_NULL) |
253 | 4.08k | return fu_cbor_item_new_string(NULL); |
254 | 5.41k | g_set_error(error, |
255 | 5.41k | FWUPD_ERROR, |
256 | 5.41k | FWUPD_ERROR_INVALID_DATA, |
257 | 5.41k | "special value %u [%s] is not handled", |
258 | 5.41k | (guint)len, |
259 | 5.41k | fu_cbor_special_value_to_string(len)); |
260 | 5.41k | return NULL; |
261 | 9.50k | } |
262 | 288k | if (tag == FU_CBOR_TAG_MAP) |
263 | 235k | return fu_cbor_parse_map(helper, len, current_depth + 1, error); |
264 | 52.8k | if (tag == FU_CBOR_TAG_ARRAY) |
265 | 52.1k | return fu_cbor_parse_array(helper, len, current_depth + 1, error); |
266 | | |
267 | | /* unknown */ |
268 | 673 | g_set_error(error, |
269 | 673 | FWUPD_ERROR, |
270 | 673 | FWUPD_ERROR_INVALID_DATA, |
271 | 673 | "tag %u [%s] is not handled", |
272 | 673 | tag, |
273 | 673 | fu_cbor_tag_to_string(tag)); |
274 | 673 | return NULL; |
275 | 52.8k | } |
276 | | |
277 | | /** |
278 | | * fu_cbor_parse: (skip): |
279 | | * @stream: a #FuInputStream |
280 | | * @offset: (inout) (nullable): stream position |
281 | | * @max_depth: maximum depth, or 0 for no limit |
282 | | * @max_items: maximum number of items, or 0 for no limit |
283 | | * @max_length: maximum length of strings and byte arrays, or 0 for no limit |
284 | | * @error: (nullable): optional return location for an error |
285 | | * |
286 | | * Parses a buffer into a CBOR map or array. |
287 | | * |
288 | | * Returns: (transfer full): root item, or %NULL on error |
289 | | * |
290 | | * Since: 2.1.2 |
291 | | **/ |
292 | | FuCborItem * |
293 | | fu_cbor_parse(FuInputStream *stream, |
294 | | gsize *offset, |
295 | | guint max_depth, |
296 | | guint max_items, |
297 | | guint max_length, |
298 | | GError **error) |
299 | 111k | { |
300 | 111k | g_autoptr(FuCborItem) item = NULL; |
301 | 111k | FuCborParseHelper helper = { |
302 | 111k | .stream = stream, |
303 | 111k | .max_depth = max_depth, |
304 | 111k | .max_items = max_items, |
305 | 111k | .max_length = max_length, |
306 | 111k | }; |
307 | | |
308 | 111k | g_return_val_if_fail(FU_IS_INPUT_STREAM(stream), NULL); |
309 | 111k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
310 | | |
311 | 111k | if (offset != NULL) |
312 | 111k | helper.offset = *offset; |
313 | 111k | item = fu_cbor_parse_item(&helper, 0, error); |
314 | 111k | if (item == NULL) { |
315 | 24.1k | g_prefix_error(error, "CBOR parsing failed @0x%x: ", (guint)helper.offset); |
316 | 24.1k | return NULL; |
317 | 24.1k | } |
318 | 87.7k | if (fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_MAP && |
319 | 5.87k | fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_ARRAY) { |
320 | 4.47k | g_set_error(error, |
321 | 4.47k | FWUPD_ERROR, |
322 | 4.47k | FWUPD_ERROR_INVALID_DATA, |
323 | 4.47k | "CBOR data must start with array or map, got %s", |
324 | 4.47k | fu_cbor_item_kind_to_string(fu_cbor_item_get_kind(item))); |
325 | 4.47k | return NULL; |
326 | 4.47k | } |
327 | | |
328 | | /* success */ |
329 | 83.3k | if (offset != NULL) |
330 | 83.3k | *offset = helper.offset; |
331 | 83.3k | return g_steal_pointer(&item); |
332 | 87.7k | } |