/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.19M | #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 | 239k | { |
30 | 239k | g_autoptr(FuCborItem) item = fu_cbor_item_new_map(); |
31 | | |
32 | | /* sanity check */ |
33 | 239k | if (helper->max_depth > 0 && current_depth > helper->max_depth) { |
34 | 346 | g_set_error(error, |
35 | 346 | FWUPD_ERROR, |
36 | 346 | FWUPD_ERROR_INVALID_DATA, |
37 | 346 | "reached max depth of %u", |
38 | 346 | current_depth); |
39 | 346 | return NULL; |
40 | 346 | } |
41 | 239k | if (helper->max_items > 0 && len > helper->max_items) { |
42 | 692 | g_set_error(error, |
43 | 692 | FWUPD_ERROR, |
44 | 692 | FWUPD_ERROR_INVALID_DATA, |
45 | 692 | "too many items (%u of maximum %u)", |
46 | 692 | (guint)len, |
47 | 692 | helper->max_items); |
48 | 692 | return NULL; |
49 | 692 | } |
50 | | |
51 | 238k | g_debug("map has %u items", (guint)len); |
52 | 789k | for (guint64 i = 0; i < len; i++) { |
53 | 575k | g_autoptr(FuCborItem) item_key = NULL; |
54 | 575k | g_autoptr(FuCborItem) item_val = NULL; |
55 | 575k | item_key = fu_cbor_parse_item(helper, current_depth, error); |
56 | 575k | if (item_key == NULL) |
57 | 10.6k | return NULL; |
58 | 564k | item_val = fu_cbor_parse_item(helper, current_depth, error); |
59 | 564k | if (item_val == NULL) |
60 | 13.3k | return NULL; |
61 | 551k | if (!fu_cbor_item_map_append(item, item_key, item_val, error)) |
62 | 0 | return NULL; |
63 | 551k | } |
64 | | |
65 | | /* success */ |
66 | 214k | return g_steal_pointer(&item); |
67 | 238k | } |
68 | | |
69 | | static FuCborItem * |
70 | | fu_cbor_parse_array(FuCborParseHelper *helper, guint64 len, guint current_depth, GError **error) |
71 | 51.4k | { |
72 | 51.4k | g_autoptr(FuCborItem) item = fu_cbor_item_new_array(); |
73 | | |
74 | | /* sanity check */ |
75 | 51.4k | if (helper->max_depth > 0 && current_depth > helper->max_depth) { |
76 | 286 | g_set_error(error, |
77 | 286 | FWUPD_ERROR, |
78 | 286 | FWUPD_ERROR_INVALID_DATA, |
79 | 286 | "reached max depth of %u", |
80 | 286 | current_depth); |
81 | 286 | return NULL; |
82 | 286 | } |
83 | 51.1k | 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 | 50.2k | g_debug("array has %u items", (guint)len); |
94 | 149k | for (guint64 i = 0; i < len; i++) { |
95 | 112k | g_autoptr(FuCborItem) item_tmp = NULL; |
96 | 112k | item_tmp = fu_cbor_parse_item(helper, current_depth, error); |
97 | 112k | if (item_tmp == NULL) |
98 | 12.6k | return NULL; |
99 | 99.3k | if (!fu_cbor_item_array_append(item, item_tmp, error)) |
100 | 0 | return NULL; |
101 | 99.3k | } |
102 | | |
103 | | /* success */ |
104 | 37.6k | return g_steal_pointer(&item); |
105 | 50.2k | } |
106 | | |
107 | | static FuCborItem * |
108 | | fu_cbor_parse_item(FuCborParseHelper *helper, guint current_depth, GError **error) |
109 | 1.37M | { |
110 | 1.37M | FuCborTag tag; |
111 | 1.37M | guint64 len = 0; |
112 | 1.37M | guint8 len_short; |
113 | 1.37M | guint8 value8 = 0; |
114 | | |
115 | 1.37M | if (!fu_input_stream_read_u8(helper->stream, helper->offset, &value8, error)) |
116 | 2.46k | return NULL; |
117 | | |
118 | 1.36M | 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.36M | tag = (value8 & 0b11100000) >> 5; |
124 | 1.36M | g_debug("tag: %u [%s] @0x%x", tag, fu_cbor_tag_to_string(tag), (guint)helper->offset); |
125 | | |
126 | | /* process length */ |
127 | 1.36M | len_short = (guint)(value8 & 0b11111); |
128 | 1.36M | g_debug("len-short: %u", len_short); |
129 | 1.36M | if (len_short <= FU_CBOR_LEN_SHORT_MAX) { |
130 | 1.17M | len = len_short; |
131 | 1.17M | } else if (len_short == FU_CBOR_LEN_EXT8) { |
132 | 130k | if (!fu_input_stream_read_u8(helper->stream, helper->offset, &value8, error)) |
133 | 451 | return NULL; |
134 | 129k | len = value8; |
135 | | |
136 | 129k | 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 | 129k | } else if (len_short == FU_CBOR_LEN_EXT16) { |
141 | 15.2k | guint16 value16 = 0; |
142 | 15.2k | if (!fu_input_stream_read_u16(helper->stream, |
143 | 15.2k | helper->offset, |
144 | 15.2k | &value16, |
145 | 15.2k | G_BIG_ENDIAN, |
146 | 15.2k | error)) |
147 | 324 | return NULL; |
148 | 14.8k | len = value16; |
149 | | |
150 | 14.8k | 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 | 43.1k | } else if (len_short == FU_CBOR_LEN_EXT32) { |
155 | 9.87k | guint32 value32 = 0; |
156 | 9.87k | if (!fu_input_stream_read_u32(helper->stream, |
157 | 9.87k | helper->offset, |
158 | 9.87k | &value32, |
159 | 9.87k | G_BIG_ENDIAN, |
160 | 9.87k | error)) |
161 | 436 | return NULL; |
162 | 9.43k | len = value32; |
163 | | |
164 | 9.43k | 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 | 33.2k | } else if (len_short == FU_CBOR_LEN_EXT64) { |
169 | 32.0k | guint64 value64 = 0; |
170 | 32.0k | if (!fu_input_stream_read_u64(helper->stream, |
171 | 32.0k | helper->offset, |
172 | 32.0k | &value64, |
173 | 32.0k | G_BIG_ENDIAN, |
174 | 32.0k | error)) |
175 | 314 | return NULL; |
176 | 31.7k | if (value64 > G_MAXINT64) { |
177 | 825 | g_set_error_literal(error, |
178 | 825 | FWUPD_ERROR, |
179 | 825 | FWUPD_ERROR_INVALID_DATA, |
180 | 825 | "lengths larger than s64:MAX are not supported"); |
181 | 825 | return NULL; |
182 | 825 | } |
183 | 30.9k | len = value64; |
184 | | |
185 | 30.9k | 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 | 30.9k | } else if (len_short == FU_CBOR_LEN_INDEFINITE) { |
190 | 538 | g_set_error_literal(error, |
191 | 538 | FWUPD_ERROR, |
192 | 538 | FWUPD_ERROR_INVALID_DATA, |
193 | 538 | "indefinite-length encoding is not supported"); |
194 | 538 | return NULL; |
195 | 627 | } else { |
196 | 627 | g_set_error(error, |
197 | 627 | FWUPD_ERROR, |
198 | 627 | FWUPD_ERROR_INVALID_DATA, |
199 | 627 | "short count %u is invalid", |
200 | 627 | len_short); |
201 | 627 | return NULL; |
202 | 627 | } |
203 | 1.36M | if (len != len_short) |
204 | 166k | g_debug("len: %" G_GUINT64_FORMAT, len); |
205 | | |
206 | | /* process tags */ |
207 | 1.36M | if (tag == FU_CBOR_TAG_POS_INT) |
208 | 807k | return fu_cbor_item_new_integer(len); |
209 | 556k | if (tag == FU_CBOR_TAG_NEG_INT) |
210 | 164k | return fu_cbor_item_new_integer(-1 - (gint64)len); |
211 | 391k | if (tag == FU_CBOR_TAG_STRING) { |
212 | 21.9k | g_autofree gchar *str = NULL; |
213 | 21.9k | if (helper->max_length > 0 && len > helper->max_length) { |
214 | 739 | g_set_error(error, |
215 | 739 | FWUPD_ERROR, |
216 | 739 | FWUPD_ERROR_INVALID_DATA, |
217 | 739 | "string too long (%u of maximum %u)", |
218 | 739 | (guint)len, |
219 | 739 | helper->max_length); |
220 | 739 | return NULL; |
221 | 739 | } |
222 | 21.2k | str = fu_input_stream_read_string(helper->stream, helper->offset, len, error); |
223 | 21.2k | if (str == NULL) |
224 | 7.57k | return NULL; |
225 | 13.6k | if (!fu_size_checked_inc(&helper->offset, len, error)) |
226 | 0 | return NULL; |
227 | 13.6k | return fu_cbor_item_new_string_steal(g_steal_pointer(&str)); |
228 | 13.6k | } |
229 | 369k | if (tag == FU_CBOR_TAG_BYTES) { |
230 | 54.4k | g_autoptr(GBytes) blob = NULL; |
231 | 54.4k | if (helper->max_length > 0 && len > helper->max_length) { |
232 | 848 | g_set_error(error, |
233 | 848 | FWUPD_ERROR, |
234 | 848 | FWUPD_ERROR_INVALID_DATA, |
235 | 848 | "bytes too long (%u of maximum %u)", |
236 | 848 | (guint)len, |
237 | 848 | helper->max_length); |
238 | 848 | return NULL; |
239 | 848 | } |
240 | 53.6k | blob = fu_input_stream_read_bytes(helper->stream, helper->offset, len, NULL, error); |
241 | 53.6k | if (blob == NULL) |
242 | 489 | return NULL; |
243 | 53.1k | if (!fu_size_checked_inc(&helper->offset, len, error)) |
244 | 0 | return NULL; |
245 | 53.1k | return fu_cbor_item_new_bytes(blob); |
246 | 53.1k | } |
247 | 315k | if (tag == FU_CBOR_TAG_SPECIAL) { |
248 | 23.6k | if (len == FU_CBOR_SPECIAL_VALUE_TRUE) |
249 | 7.12k | return fu_cbor_item_new_boolean(TRUE); |
250 | 16.5k | if (len == FU_CBOR_SPECIAL_VALUE_FALSE) |
251 | 6.27k | return fu_cbor_item_new_boolean(FALSE); |
252 | 10.2k | if (len == FU_CBOR_SPECIAL_VALUE_NULL) |
253 | 4.05k | return fu_cbor_item_new_string(NULL); |
254 | 6.23k | g_set_error(error, |
255 | 6.23k | FWUPD_ERROR, |
256 | 6.23k | FWUPD_ERROR_INVALID_DATA, |
257 | 6.23k | "special value %u [%s] is not handled", |
258 | 6.23k | (guint)len, |
259 | 6.23k | fu_cbor_special_value_to_string(len)); |
260 | 6.23k | return NULL; |
261 | 10.2k | } |
262 | 291k | if (tag == FU_CBOR_TAG_MAP) |
263 | 239k | return fu_cbor_parse_map(helper, len, current_depth + 1, error); |
264 | 51.9k | if (tag == FU_CBOR_TAG_ARRAY) |
265 | 51.4k | return fu_cbor_parse_array(helper, len, current_depth + 1, error); |
266 | | |
267 | | /* unknown */ |
268 | 519 | g_set_error(error, |
269 | 519 | FWUPD_ERROR, |
270 | 519 | FWUPD_ERROR_INVALID_DATA, |
271 | 519 | "tag %u [%s] is not handled", |
272 | 519 | tag, |
273 | 519 | fu_cbor_tag_to_string(tag)); |
274 | 519 | return NULL; |
275 | 51.9k | } |
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 | 118k | { |
300 | 118k | g_autoptr(FuCborItem) item = NULL; |
301 | 118k | FuCborParseHelper helper = { |
302 | 118k | .stream = stream, |
303 | 118k | .max_depth = max_depth, |
304 | 118k | .max_items = max_items, |
305 | 118k | .max_length = max_length, |
306 | 118k | }; |
307 | | |
308 | 118k | g_return_val_if_fail(FU_IS_INPUT_STREAM(stream), NULL); |
309 | 118k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
310 | | |
311 | 118k | if (offset != NULL) |
312 | 118k | helper.offset = *offset; |
313 | 118k | item = fu_cbor_parse_item(&helper, 0, error); |
314 | 118k | if (item == NULL) { |
315 | 24.5k | g_prefix_error(error, "CBOR parsing failed @0x%x: ", (guint)helper.offset); |
316 | 24.5k | return NULL; |
317 | 24.5k | } |
318 | 93.7k | if (fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_MAP && |
319 | 7.53k | fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_ARRAY) { |
320 | 4.64k | g_set_error(error, |
321 | 4.64k | FWUPD_ERROR, |
322 | 4.64k | FWUPD_ERROR_INVALID_DATA, |
323 | 4.64k | "CBOR data must start with array or map, got %s", |
324 | 4.64k | fu_cbor_item_kind_to_string(fu_cbor_item_get_kind(item))); |
325 | 4.64k | return NULL; |
326 | 4.64k | } |
327 | | |
328 | | /* success */ |
329 | 89.0k | if (offset != NULL) |
330 | 89.0k | *offset = helper.offset; |
331 | 89.0k | return g_steal_pointer(&item); |
332 | 93.7k | } |