Coverage Report

Created: 2026-09-14 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}