Coverage Report

Created: 2026-01-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupd/fwupd-common.c
Line
Count
Source
1
/*
2
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "fwupd-common-private.h"
10
#include "fwupd-error.h"
11
12
#ifdef HAVE_GIO_UNIX
13
#include <errno.h>
14
#include <fcntl.h>
15
#include <glib/gstdio.h>
16
#include <unistd.h>
17
#endif
18
19
#ifdef HAVE_MEMFD_CREATE
20
#include <sys/mman.h>
21
#endif
22
23
#include <string.h>
24
25
/**
26
 * fwupd_checksum_guess_kind:
27
 * @checksum: (nullable): a checksum
28
 *
29
 * Guesses the checksum kind based on the length of the hash.
30
 *
31
 * Returns: a checksum type, e.g. %G_CHECKSUM_SHA1
32
 *
33
 * Since: 0.9.3
34
 **/
35
GChecksumType
36
fwupd_checksum_guess_kind(const gchar *checksum)
37
0
{
38
0
  guint len;
39
0
  g_return_val_if_fail(checksum != NULL, G_CHECKSUM_SHA1);
40
0
  len = strlen(checksum);
41
0
  if (len == 32)
42
0
    return G_CHECKSUM_MD5;
43
0
  if (len == 40)
44
0
    return G_CHECKSUM_SHA1;
45
0
  if (len == 64)
46
0
    return G_CHECKSUM_SHA256;
47
0
  if (len == 96)
48
0
    return G_CHECKSUM_SHA384;
49
0
  if (len == 128)
50
0
    return G_CHECKSUM_SHA512;
51
0
  return G_CHECKSUM_SHA1;
52
0
}
53
54
/**
55
 * fwupd_checksum_type_to_string_display:
56
 * @checksum_type: a #GChecksumType, e.g. %G_CHECKSUM_SHA1
57
 *
58
 * Formats a checksum type for display.
59
 *
60
 * Returns: text, or %NULL for invalid
61
 *
62
 * Since: 1.9.6
63
 **/
64
const gchar *
65
fwupd_checksum_type_to_string_display(GChecksumType checksum_type)
66
0
{
67
0
  if (checksum_type == G_CHECKSUM_MD5)
68
0
    return "MD5";
69
0
  if (checksum_type == G_CHECKSUM_SHA1)
70
0
    return "SHA1";
71
0
  if (checksum_type == G_CHECKSUM_SHA256)
72
0
    return "SHA256";
73
0
  if (checksum_type == G_CHECKSUM_SHA384)
74
0
    return "SHA384";
75
0
  if (checksum_type == G_CHECKSUM_SHA512)
76
0
    return "SHA512";
77
0
  return NULL;
78
0
}
79
80
/**
81
 * fwupd_checksum_format_for_display:
82
 * @checksum: (nullable): a checksum
83
 *
84
 * Formats a checksum for display.
85
 *
86
 * Returns: text, or %NULL for invalid
87
 *
88
 * Since: 0.9.3
89
 **/
90
gchar *
91
fwupd_checksum_format_for_display(const gchar *checksum)
92
0
{
93
0
  GChecksumType kind = fwupd_checksum_guess_kind(checksum);
94
0
  return g_strdup_printf("%s(%s)", fwupd_checksum_type_to_string_display(kind), checksum);
95
0
}
96
97
/**
98
 * fwupd_checksum_get_by_kind:
99
 * @checksums: (element-type utf8): checksums
100
 * @kind: a checksum type, e.g. %G_CHECKSUM_SHA512
101
 *
102
 * Gets a specific checksum kind.
103
 *
104
 * Returns: a checksum from the array, or %NULL if not found
105
 *
106
 * Since: 0.9.4
107
 **/
108
const gchar *
109
fwupd_checksum_get_by_kind(GPtrArray *checksums, GChecksumType kind)
110
0
{
111
0
  g_return_val_if_fail(checksums != NULL, NULL);
112
0
  for (guint i = 0; i < checksums->len; i++) {
113
0
    const gchar *checksum = g_ptr_array_index(checksums, i);
114
0
    if (fwupd_checksum_guess_kind(checksum) == kind)
115
0
      return checksum;
116
0
  }
117
0
  return NULL;
118
0
}
119
120
/**
121
 * fwupd_checksum_get_best:
122
 * @checksums: (element-type utf8): checksums
123
 *
124
 * Gets a the best possible checksum kind.
125
 *
126
 * Returns: a checksum from the array, or %NULL if nothing was suitable
127
 *
128
 * Since: 0.9.4
129
 **/
130
const gchar *
131
fwupd_checksum_get_best(GPtrArray *checksums)
132
0
{
133
0
  GChecksumType checksum_types[] = {G_CHECKSUM_SHA512,
134
0
            G_CHECKSUM_SHA256,
135
0
            G_CHECKSUM_SHA384,
136
0
            G_CHECKSUM_SHA1,
137
0
            0};
138
0
  g_return_val_if_fail(checksums != NULL, NULL);
139
0
  for (guint i = 0; checksum_types[i] != 0; i++) {
140
0
    for (guint j = 0; j < checksums->len; j++) {
141
0
      const gchar *checksum = g_ptr_array_index(checksums, j);
142
0
      if (fwupd_checksum_guess_kind(checksum) == checksum_types[i])
143
0
        return checksum;
144
0
    }
145
0
  }
146
0
  return NULL;
147
0
}
148
149
#define FWUPD_GUID_NAMESPACE_DEFAULT   "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
150
#define FWUPD_GUID_NAMESPACE_MICROSOFT "70ffd812-4c7f-4c7d-0000-000000000000"
151
152
typedef struct __attribute__((packed)) { /* nocheck:blocked */
153
  guint32 a;
154
  guint16 b;
155
  guint16 c;
156
  guint16 d;
157
  guint8 e[6];
158
} fwupd_guid_native_t;
159
160
/**
161
 * fwupd_guid_to_string:
162
 * @guid: a #fwupd_guid_t to read
163
 * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN
164
 *
165
 * Returns a text GUID of mixed or BE endian for a packed buffer.
166
 *
167
 * Returns: a new GUID string
168
 *
169
 * Since: 1.2.5
170
 **/
171
gchar *
172
fwupd_guid_to_string(const fwupd_guid_t *guid, FwupdGuidFlags flags)
173
66.3k
{
174
66.3k
  fwupd_guid_native_t gnat;
175
176
66.3k
  g_return_val_if_fail(guid != NULL, NULL);
177
178
  /* copy to avoid issues with aligning */
179
66.3k
  memcpy(&gnat, guid, sizeof(gnat)); /* nocheck:blocked */
180
181
  /* mixed is bizaar, but specified as the DCE encoding */
182
66.3k
  if (flags & FWUPD_GUID_FLAG_MIXED_ENDIAN) {
183
54.1k
    return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
184
54.1k
               (guint)GUINT32_FROM_LE(gnat.a), /* nocheck:blocked */
185
54.1k
               (guint)GUINT16_FROM_LE(gnat.b), /* nocheck:blocked */
186
54.1k
               (guint)GUINT16_FROM_LE(gnat.c), /* nocheck:blocked */
187
54.1k
               (guint)GUINT16_FROM_BE(gnat.d), /* nocheck:blocked */
188
54.1k
               gnat.e[0],
189
54.1k
               gnat.e[1],
190
54.1k
               gnat.e[2],
191
54.1k
               gnat.e[3],
192
54.1k
               gnat.e[4],
193
54.1k
               gnat.e[5]);
194
54.1k
  }
195
12.1k
  return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
196
12.1k
             (guint)GUINT32_FROM_BE(gnat.a), /* nocheck:blocked */
197
12.1k
             (guint)GUINT16_FROM_BE(gnat.b), /* nocheck:blocked */
198
12.1k
             (guint)GUINT16_FROM_BE(gnat.c), /* nocheck:blocked */
199
12.1k
             (guint)GUINT16_FROM_BE(gnat.d), /* nocheck:blocked */
200
12.1k
             gnat.e[0],
201
12.1k
             gnat.e[1],
202
12.1k
             gnat.e[2],
203
12.1k
             gnat.e[3],
204
12.1k
             gnat.e[4],
205
12.1k
             gnat.e[5]);
206
66.3k
}
207
208
/**
209
 * fwupd_guid_from_string:
210
 * @guidstr: (not nullable): a GUID, e.g. `00112233-4455-6677-8899-aabbccddeeff`
211
 * @guid: (nullable): a #fwupd_guid_t, or NULL to just check the GUID
212
 * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN
213
 * @error: (nullable): optional return location for an error
214
 *
215
 * Converts a string GUID into its binary encoding. All string GUIDs are
216
 * formatted as big endian but on-disk can be encoded in different ways.
217
 *
218
 * Returns: %TRUE for success
219
 *
220
 * Since: 1.2.5
221
 **/
222
gboolean
223
fwupd_guid_from_string(const gchar *guidstr,
224
           fwupd_guid_t *guid,
225
           FwupdGuidFlags flags,
226
           GError **error)
227
21.0k
{
228
21.0k
  fwupd_guid_native_t gu = {0x0};
229
21.0k
  gboolean mixed_endian = flags & FWUPD_GUID_FLAG_MIXED_ENDIAN;
230
21.0k
  guint64 tmp;
231
21.0k
  g_auto(GStrv) split = NULL;
232
233
21.0k
  g_return_val_if_fail(guidstr != NULL, FALSE);
234
21.0k
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
235
236
  /* split into sections */
237
21.0k
  if (strlen(guidstr) != 36) {
238
6.87k
    g_set_error_literal(error,
239
6.87k
            FWUPD_ERROR,
240
6.87k
            FWUPD_ERROR_INVALID_DATA,
241
6.87k
            "GUID is not valid format");
242
6.87k
    return FALSE;
243
6.87k
  }
244
14.1k
  split = g_strsplit(guidstr, "-", 5);
245
14.1k
  if (g_strv_length(split) != 5) {
246
384
    g_set_error_literal(error,
247
384
            FWUPD_ERROR,
248
384
            FWUPD_ERROR_INVALID_DATA,
249
384
            "GUID is not valid format, no dashes");
250
384
    return FALSE;
251
384
  }
252
13.7k
  if (strlen(split[0]) != 8 || strlen(split[1]) != 4 || strlen(split[2]) != 4 ||
253
13.1k
      strlen(split[3]) != 4 || strlen(split[4]) != 12) {
254
911
    g_set_error_literal(error,
255
911
            FWUPD_ERROR,
256
911
            FWUPD_ERROR_INVALID_DATA,
257
911
            "GUID is not valid format, not GUID");
258
911
    return FALSE;
259
911
  }
260
261
  /* parse */
262
12.8k
  if (!g_ascii_string_to_unsigned(split[0], 16, 0, 0xffffffff, &tmp, error))
263
122
    return FALSE;
264
12.7k
  gu.a = mixed_endian ? GUINT32_TO_LE(tmp) : GUINT32_TO_BE(tmp); /* nocheck:blocked */
265
12.7k
  if (!g_ascii_string_to_unsigned(split[1], 16, 0, 0xffff, &tmp, error))
266
93
    return FALSE;
267
12.6k
  gu.b = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); /* nocheck:blocked */
268
12.6k
  if (!g_ascii_string_to_unsigned(split[2], 16, 0, 0xffff, &tmp, error))
269
119
    return FALSE;
270
12.5k
  gu.c = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); /* nocheck:blocked */
271
12.5k
  if (!g_ascii_string_to_unsigned(split[3], 16, 0, 0xffff, &tmp, error))
272
43
    return FALSE;
273
12.5k
  gu.d = GUINT16_TO_BE(tmp); /* nocheck:blocked */
274
86.0k
  for (guint i = 0; i < 6; i++) {
275
73.9k
    gchar buffer[3] = {0x0};
276
73.9k
    memcpy(buffer, split[4] + (i * 2), 2); /* nocheck:blocked */
277
73.9k
    if (!g_ascii_string_to_unsigned(buffer, 16, 0, 0xff, &tmp, error))
278
385
      return FALSE;
279
73.5k
    gu.e[i] = tmp;
280
73.5k
  }
281
12.1k
  if (guid != NULL)
282
12.1k
    memcpy(guid, &gu, sizeof(gu)); /* nocheck:blocked */
283
284
  /* success */
285
12.1k
  return TRUE;
286
12.5k
}
287
288
/**
289
 * fwupd_guid_hash_data:
290
 * @data: data to hash
291
 * @datasz: length of @data
292
 * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT
293
 *
294
 * Returns a GUID for some data. This uses a hash and so even small
295
 * differences in the @data will produce radically different return values.
296
 *
297
 * The implementation is taken from RFC4122, Section 4.1.3; specifically
298
 * using a type-5 SHA-1 hash.
299
 *
300
 * Returns: a new GUID, or %NULL for internal error
301
 *
302
 * Since: 1.2.5
303
 **/
304
gchar *
305
fwupd_guid_hash_data(const guint8 *data, gsize datasz, FwupdGuidFlags flags)
306
0
{
307
0
  gsize digestlen = 20;
308
0
  guint8 hash[20] = {0};
309
0
  fwupd_guid_t uu_new;
310
0
  g_autoptr(GChecksum) csum = NULL;
311
0
  const fwupd_guid_t uu_default = {0x6b,
312
0
           0xa7,
313
0
           0xb8,
314
0
           0x10,
315
0
           0x9d,
316
0
           0xad,
317
0
           0x11,
318
0
           0xd1,
319
0
           0x80,
320
0
           0xb4,
321
0
           0x00,
322
0
           0xc0,
323
0
           0x4f,
324
0
           0xd4,
325
0
           0x30,
326
0
           0xc8};
327
0
  const fwupd_guid_t uu_microso = {0x70, 0xff, 0xd8, 0x12, 0x4c, 0x7f, 0x4c, 0x7d};
328
0
  const fwupd_guid_t *uu_namespace = &uu_default;
329
330
0
  g_return_val_if_fail(data != NULL, NULL);
331
0
  g_return_val_if_fail(datasz != 0, NULL);
332
333
  /* old MS GUID */
334
0
  if (flags & FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT)
335
0
    uu_namespace = &uu_microso;
336
337
  /* hash the namespace and then the string */
338
0
  csum = g_checksum_new(G_CHECKSUM_SHA1);
339
0
  g_checksum_update(csum, (guchar *)uu_namespace, sizeof(*uu_namespace));
340
0
  g_checksum_update(csum, (guchar *)data, (gssize)datasz);
341
0
  g_checksum_get_digest(csum, hash, &digestlen);
342
343
  /* copy most parts of the hash 1:1 */
344
0
  memcpy(uu_new, hash, sizeof(uu_new)); /* nocheck:blocked */
345
346
  /* set specific bits according to Section 4.1.3 */
347
0
  uu_new[6] = (guint8)((uu_new[6] & 0x0f) | (5 << 4));
348
0
  uu_new[8] = (guint8)((uu_new[8] & 0x3f) | 0x80);
349
0
  return fwupd_guid_to_string((const fwupd_guid_t *)&uu_new, flags);
350
0
}
351
352
/**
353
 * fwupd_device_id_is_valid:
354
 * @device_id: string to check, e.g. `d3fae86d95e5d56626129d00e332c4b8dac95442`
355
 *
356
 * Checks the string is a valid non-partial device ID. It is important to note
357
 * that the wildcard ID of `*` is not considered a valid ID in this function and
358
 * the client must check for this manually if this should be allowed.
359
 *
360
 * Returns: %TRUE if @guid was a fwupd device ID, %FALSE otherwise
361
 *
362
 * Since: 1.4.1
363
 **/
364
gboolean
365
fwupd_device_id_is_valid(const gchar *device_id)
366
0
{
367
0
  if (device_id == NULL)
368
0
    return FALSE;
369
0
  if (strlen(device_id) != 40)
370
0
    return FALSE;
371
0
  for (guint i = 0; device_id[i] != '\0'; i++) {
372
0
    gchar tmp = device_id[i];
373
    /* isalnum isn't case specific */
374
0
    if ((tmp < 'a' || tmp > 'f') && (tmp < '0' || tmp > '9'))
375
0
      return FALSE;
376
0
  }
377
0
  return TRUE;
378
0
}
379
380
/**
381
 * fwupd_guid_is_valid:
382
 * @guid: string to check, e.g. `00112233-4455-6677-8899-aabbccddeeff`
383
 *
384
 * Checks the string is a valid GUID.
385
 *
386
 * Returns: %TRUE if @guid was a valid GUID, %FALSE otherwise
387
 *
388
 * Since: 1.2.5
389
 **/
390
gboolean
391
fwupd_guid_is_valid(const gchar *guid)
392
0
{
393
0
  const gchar zeroguid[] = {"00000000-0000-0000-0000-000000000000"};
394
395
  /* sanity check */
396
0
  if (guid == NULL)
397
0
    return FALSE;
398
399
  /* check for dashes and hexdigits in the right place */
400
0
  for (guint i = 0; i < sizeof(zeroguid) - 1; i++) {
401
0
    if (guid[i] == '\0')
402
0
      return FALSE;
403
0
    if (zeroguid[i] == '-') {
404
0
      if (guid[i] != '-')
405
0
        return FALSE;
406
0
      continue;
407
0
    }
408
0
    if (!g_ascii_isxdigit(guid[i]))
409
0
      return FALSE;
410
0
  }
411
412
  /* longer than required */
413
0
  if (guid[sizeof(zeroguid) - 1] != '\0')
414
0
    return FALSE;
415
416
  /* not valid */
417
0
  return g_strcmp0(guid, zeroguid) != 0;
418
0
}
419
420
/**
421
 * fwupd_guid_hash_string:
422
 * @str: (nullable): a source string to use as a key
423
 *
424
 * Returns a GUID for a given string. This uses a hash and so even small
425
 * differences in the @str will produce radically different return values.
426
 *
427
 * The default implementation is taken from RFC4122, Section 4.1.3; specifically
428
 * using a type-5 SHA-1 hash with a DNS namespace.
429
 * The same result can be obtained with this simple python program:
430
 *
431
 *    #!/usr/bin/python
432
 *    import uuid
433
 *    print uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
434
 *
435
 * Returns: a new GUID, or %NULL if the string was invalid
436
 *
437
 * Since: 1.2.5
438
 **/
439
gchar *
440
fwupd_guid_hash_string(const gchar *str)
441
0
{
442
0
  if (str == NULL || str[0] == '\0')
443
0
    return NULL;
444
0
  return fwupd_guid_hash_data((const guint8 *)str, strlen(str), FWUPD_GUID_FLAG_NONE);
445
0
}
446
447
/**
448
 * fwupd_hash_kv_to_variant: (skip):
449
 **/
450
GVariant *
451
fwupd_hash_kv_to_variant(GHashTable *hash)
452
0
{
453
0
  GVariantBuilder builder;
454
0
  g_autoptr(GList) keys = g_hash_table_get_keys(hash);
455
0
  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
456
0
  for (GList *l = keys; l != NULL; l = l->next) {
457
0
    const gchar *key = l->data;
458
0
    const gchar *value = g_hash_table_lookup(hash, key);
459
0
    g_variant_builder_add(&builder, "{ss}", key, value);
460
0
  }
461
0
  return g_variant_builder_end(&builder);
462
0
}
463
464
/**
465
 * fwupd_variant_to_hash_kv: (skip):
466
 **/
467
GHashTable *
468
fwupd_variant_to_hash_kv(GVariant *dict)
469
0
{
470
0
  GHashTable *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
471
0
  GVariantIter iter;
472
0
  const gchar *key;
473
0
  const gchar *value;
474
0
  g_variant_iter_init(&iter, dict);
475
0
  while (g_variant_iter_loop(&iter, "{&s&s}", &key, &value))
476
0
    g_hash_table_insert(hash, g_strdup(key), g_strdup(value));
477
0
  return hash;
478
0
}
479
480
#ifdef HAVE_GIO_UNIX
481
/**
482
 * fwupd_unix_input_stream_from_bytes: (skip):
483
 **/
484
GUnixInputStream *
485
fwupd_unix_input_stream_from_bytes(GBytes *bytes, GError **error)
486
{
487
  gint fd;
488
  gssize rc;
489
#ifndef HAVE_MEMFD_CREATE
490
  gchar tmp_file[] = "/tmp/fwupd.XXXXXX";
491
#endif
492
493
#ifdef HAVE_MEMFD_CREATE
494
  fd = memfd_create("fwupd", MFD_CLOEXEC);
495
#else
496
  /* emulate in-memory file by an unlinked temporary file */
497
  fd = g_mkstemp(tmp_file);
498
  if (fd != -1) {
499
    rc = g_unlink(tmp_file);
500
    if (rc != 0) {
501
      if (!g_close(fd, error)) {
502
        g_prefix_error_literal(error, "failed to close temporary file: ");
503
        return NULL;
504
      }
505
      g_set_error_literal(error,
506
              FWUPD_ERROR,
507
              FWUPD_ERROR_INVALID_FILE,
508
              "failed to unlink temporary file");
509
      return NULL;
510
    }
511
  }
512
#endif
513
514
  if (fd < 0) {
515
    g_set_error_literal(error,
516
            FWUPD_ERROR,
517
            FWUPD_ERROR_INVALID_FILE,
518
            "failed to create memfd");
519
    return NULL;
520
  }
521
  rc = write(fd, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes));
522
  if (rc < 0) {
523
    g_set_error(error,
524
          FWUPD_ERROR,
525
          FWUPD_ERROR_INVALID_FILE,
526
          "failed to write %" G_GSSIZE_FORMAT,
527
          rc);
528
    return NULL;
529
  }
530
  if (lseek(fd, 0, SEEK_SET) < 0) {
531
    g_set_error(error,
532
          FWUPD_ERROR,
533
          FWUPD_ERROR_INVALID_FILE,
534
          "failed to seek: %s",
535
          fwupd_strerror(errno));
536
    return NULL;
537
  }
538
  return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(fd, TRUE));
539
}
540
541
/**
542
 * fwupd_unix_input_stream_from_fn: (skip):
543
 **/
544
GUnixInputStream *
545
fwupd_unix_input_stream_from_fn(const gchar *fn, GError **error)
546
{
547
  gint fd = open(fn, O_RDONLY);
548
  if (fd < 0) {
549
    g_set_error(error,
550
          FWUPD_ERROR,
551
          FWUPD_ERROR_INVALID_FILE,
552
          "failed to open %s: %s",
553
          fn,
554
          fwupd_strerror(errno));
555
    return NULL;
556
  }
557
  return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(fd, TRUE));
558
}
559
560
/**
561
 * fwupd_unix_output_stream_from_fn: (skip):
562
 **/
563
GUnixOutputStream *
564
fwupd_unix_output_stream_from_fn(const gchar *fn, GError **error)
565
{
566
  gint fd = g_open(fn, O_RDWR | O_CREAT, S_IRWXU);
567
  if (fd < 0) {
568
    g_set_error(error,
569
          FWUPD_ERROR,
570
          FWUPD_ERROR_INVALID_FILE,
571
          "failed to open %s: %s",
572
          fn,
573
          fwupd_strerror(errno));
574
    return NULL;
575
  }
576
  return G_UNIX_OUTPUT_STREAM(g_unix_output_stream_new(fd, TRUE));
577
}
578
#endif