Coverage Report

Created: 2026-07-30 07:09

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.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
}