Coverage Report

Created: 2025-12-14 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-efi-volume.c
Line
Count
Source
1
/*
2
 * Copyright 2021 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
16.6k
#define G_LOG_DOMAIN "FuEfiVolume"
8
9
#include "config.h"
10
11
#include "fu-byte-array.h"
12
#include "fu-bytes.h"
13
#include "fu-chunk-array.h"
14
#include "fu-common.h"
15
#include "fu-efi-common.h"
16
#include "fu-efi-filesystem.h"
17
#include "fu-efi-ftw-store.h"
18
#include "fu-efi-struct.h"
19
#include "fu-efi-volume.h"
20
#include "fu-efi-vss2-variable-store.h"
21
#include "fu-input-stream.h"
22
#include "fu-partial-input-stream.h"
23
#include "fu-sum.h"
24
25
/**
26
 * FuEfiVolume:
27
 *
28
 * A UEFI file volume.
29
 *
30
 * See also: [class@FuFirmware]
31
 */
32
33
typedef struct {
34
  guint16 attrs;
35
} FuEfiVolumePrivate;
36
37
67.4k
G_DEFINE_TYPE_WITH_PRIVATE(FuEfiVolume, fu_efi_volume, FU_TYPE_FIRMWARE)
38
67.4k
#define GET_PRIVATE(o) (fu_efi_volume_get_instance_private(o))
39
40
static void
41
fu_efi_volume_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
42
0
{
43
0
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
44
0
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
45
0
  fu_xmlb_builder_insert_kx(bn, "attrs", priv->attrs);
46
0
  if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) {
47
0
    fu_xmlb_builder_insert_kv(bn,
48
0
            "name",
49
0
            fu_efi_guid_to_name(fu_firmware_get_id(firmware)));
50
0
  }
51
0
}
52
53
static gboolean
54
fu_efi_volume_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
55
95.9k
{
56
95.9k
  return fu_struct_efi_volume_validate_stream(stream, offset, error);
57
95.9k
}
58
59
static gboolean
60
fu_efi_volume_parse_nvram_evsa(FuEfiVolume *self,
61
             GInputStream *stream,
62
             gsize offset,
63
             FuFirmwareParseFlags flags,
64
             GError **error)
65
2.05k
{
66
2.05k
  gsize streamsz = 0;
67
2.05k
  guint found_cnt = 0;
68
2.05k
  gsize offset_last = offset;
69
70
2.05k
  if (!fu_input_stream_size(stream, &streamsz, error))
71
0
    return FALSE;
72
35.8k
  while (offset < streamsz) {
73
33.7k
    g_autoptr(FuFirmware) img = NULL;
74
33.7k
    g_autoptr(GError) error_local = NULL;
75
76
    /* try to find a NVRAM store */
77
33.7k
    img = fu_firmware_new_from_gtypes(stream,
78
33.7k
              offset,
79
33.7k
              flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
80
33.7k
              &error_local,
81
33.7k
              FU_TYPE_EFI_VSS2_VARIABLE_STORE,
82
33.7k
              FU_TYPE_EFI_FTW_STORE,
83
33.7k
              G_TYPE_INVALID);
84
33.7k
    if (img == NULL) {
85
33.2k
      if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA)) {
86
1.19k
        g_debug("ignoring EFI NVRAM @0x%x: %s",
87
1.19k
          (guint)offset,
88
1.19k
          error_local->message);
89
1.19k
      }
90
33.2k
      offset += 0x1000;
91
33.2k
      continue;
92
33.2k
    }
93
94
    /* sanity check */
95
516
    if (fu_firmware_get_size(img) == 0) {
96
2
      g_set_error_literal(error,
97
2
              FWUPD_ERROR,
98
2
              FWUPD_ERROR_INTERNAL,
99
2
              "NVRAM store entry has zero size");
100
2
      return FALSE;
101
2
    }
102
103
    /* preserve the exact padding between EVSA stores */
104
514
    if (offset != offset_last) {
105
57
      g_autoptr(GBytes) blob = g_bytes_new(NULL, 0);
106
57
      g_autoptr(GBytes) blob_padded =
107
57
          fu_bytes_pad(blob, offset - offset_last, 0xFF);
108
57
      g_autoptr(FuFirmware) img_padded = fu_firmware_new_from_bytes(blob_padded);
109
57
      if (!fu_firmware_add_image(FU_FIRMWARE(self), img_padded, error))
110
0
        return FALSE;
111
57
    }
112
113
    /* we found something */
114
514
    fu_firmware_set_offset(img, offset);
115
514
    if (!fu_firmware_add_image(FU_FIRMWARE(self), img, error))
116
0
      return FALSE;
117
514
    offset += fu_firmware_get_size(img);
118
514
    offset = fu_common_align_up(offset, FU_FIRMWARE_ALIGNMENT_4K);
119
514
    found_cnt += 1;
120
121
    /* the last thing we found */
122
514
    offset_last = offset;
123
514
  }
124
125
  /* we found nothing */
126
2.05k
  if (found_cnt == 0) {
127
1.55k
    g_set_error_literal(error,
128
1.55k
            FWUPD_ERROR,
129
1.55k
            FWUPD_ERROR_INTERNAL,
130
1.55k
            "no NVRAM stores found");
131
1.55k
    return FALSE;
132
1.55k
  }
133
134
  /* success */
135
498
  return TRUE;
136
2.05k
}
137
138
static gboolean
139
fu_efi_volume_parse(FuFirmware *firmware,
140
        GInputStream *stream,
141
        FuFirmwareParseFlags flags,
142
        GError **error)
143
10.1k
{
144
10.1k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
145
10.1k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
146
10.1k
  gsize blockmap_sz = 0;
147
10.1k
  gsize offset = 0;
148
10.1k
  gsize streamsz = 0;
149
10.1k
  guint16 hdr_length = 0;
150
10.1k
  guint32 attrs = 0;
151
10.1k
  guint64 fv_length = 0;
152
10.1k
  guint8 alignment;
153
10.1k
  g_autofree gchar *guid_str = NULL;
154
10.1k
  g_autoptr(FuStructEfiVolume) st_hdr = NULL;
155
10.1k
  g_autoptr(GInputStream) partial_stream = NULL;
156
157
  /* parse */
158
10.1k
  st_hdr = fu_struct_efi_volume_parse_stream(stream, 0x0, error);
159
10.1k
  if (st_hdr == NULL)
160
0
    return FALSE;
161
162
  /* guid */
163
10.1k
  guid_str = fwupd_guid_to_string(fu_struct_efi_volume_get_guid(st_hdr),
164
10.1k
          FWUPD_GUID_FLAG_MIXED_ENDIAN);
165
10.1k
  g_debug("volume GUID: %s [%s]", guid_str, fu_efi_guid_to_name(guid_str));
166
167
  /* length */
168
10.1k
  if (!fu_input_stream_size(stream, &streamsz, error))
169
0
    return FALSE;
170
10.1k
  fv_length = fu_struct_efi_volume_get_length(st_hdr);
171
10.1k
  if (fv_length == 0x0) {
172
33
    g_set_error_literal(error,
173
33
            FWUPD_ERROR,
174
33
            FWUPD_ERROR_INTERNAL,
175
33
            "invalid volume length");
176
33
    return FALSE;
177
33
  }
178
10.1k
  if (fv_length > fu_firmware_get_size_max(firmware)) {
179
2.00k
    g_set_error(error,
180
2.00k
          FWUPD_ERROR,
181
2.00k
          FWUPD_ERROR_INTERNAL,
182
2.00k
          "volume length larger than max size: 0x%x > 0x%x",
183
2.00k
          (guint)fv_length,
184
2.00k
          (guint)fu_firmware_get_size_max(firmware));
185
2.00k
    return FALSE;
186
2.00k
  }
187
8.15k
  attrs = fu_struct_efi_volume_get_attrs(st_hdr);
188
8.15k
  alignment = (attrs & 0x00ff0000) >> 16;
189
8.15k
  if (alignment > FU_FIRMWARE_ALIGNMENT_2G) {
190
63
    g_set_error(error,
191
63
          FWUPD_ERROR,
192
63
          FWUPD_ERROR_NOT_FOUND,
193
63
          "0x%x invalid, maximum is 0x%x",
194
63
          (guint)alignment,
195
63
          (guint)FU_FIRMWARE_ALIGNMENT_2G);
196
63
    return FALSE;
197
63
  }
198
8.09k
  fu_firmware_set_alignment(firmware, alignment);
199
8.09k
  priv->attrs = attrs & 0xffff;
200
8.09k
  hdr_length = fu_struct_efi_volume_get_hdr_len(st_hdr);
201
8.09k
  if (hdr_length < st_hdr->buf->len || hdr_length > fv_length || hdr_length > streamsz ||
202
7.89k
      hdr_length % 2 != 0) {
203
212
    g_set_error(error,
204
212
          FWUPD_ERROR,
205
212
          FWUPD_ERROR_INTERNAL,
206
212
          "invalid volume header length 0x%x",
207
212
          (guint)hdr_length);
208
212
    return FALSE;
209
212
  }
210
211
  /* verify checksum */
212
7.87k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
213
7.87k
    guint16 checksum_verify;
214
7.87k
    g_autoptr(GBytes) blob_hdr = NULL;
215
216
7.87k
    blob_hdr = fu_input_stream_read_bytes(stream, 0x0, hdr_length, NULL, error);
217
7.87k
    if (blob_hdr == NULL)
218
0
      return FALSE;
219
7.87k
    checksum_verify = fu_sum16w_bytes(blob_hdr, G_LITTLE_ENDIAN);
220
7.87k
    if (checksum_verify != 0) {
221
370
      g_set_error(error,
222
370
            FWUPD_ERROR,
223
370
            FWUPD_ERROR_INVALID_FILE,
224
370
            "checksum invalid, got %02x, expected %02x",
225
370
            checksum_verify,
226
370
            fu_struct_efi_volume_get_checksum(st_hdr));
227
370
      return FALSE;
228
370
    }
229
7.87k
  }
230
231
  /* extended header items */
232
7.50k
  if (fu_struct_efi_volume_get_ext_hdr(st_hdr) != 0) {
233
346
    g_autoptr(FuStructEfiVolumeExtHeader) st_ext_hdr = NULL;
234
346
    goffset offset_ext = fu_struct_efi_volume_get_ext_hdr(st_hdr);
235
346
    st_ext_hdr =
236
346
        fu_struct_efi_volume_ext_header_parse_stream(stream, offset_ext, error);
237
346
    if (st_ext_hdr == NULL)
238
60
      return FALSE;
239
286
    offset_ext += fu_struct_efi_volume_ext_header_get_size(st_ext_hdr);
240
2.18k
    do {
241
2.18k
      g_autoptr(FuStructEfiVolumeExtEntry) st_ext_entry = NULL;
242
2.18k
      st_ext_entry =
243
2.18k
          fu_struct_efi_volume_ext_entry_parse_stream(stream, offset_ext, error);
244
2.18k
      if (st_ext_entry == NULL)
245
139
        return FALSE;
246
2.04k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0x0) {
247
13
        g_set_error_literal(error,
248
13
                FWUPD_ERROR,
249
13
                FWUPD_ERROR_INVALID_DATA,
250
13
                "EFI_VOLUME_EXT_ENTRY invalid size");
251
13
        return FALSE;
252
13
      }
253
2.03k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0xFFFF)
254
14
        break;
255
2.02k
      offset_ext += fu_struct_efi_volume_ext_entry_get_size(st_ext_entry);
256
2.02k
    } while ((gsize)offset_ext < fv_length);
257
286
  }
258
259
  /* add image */
260
7.29k
  partial_stream =
261
7.29k
      fu_partial_input_stream_new(stream, hdr_length, fv_length - hdr_length, error);
262
7.29k
  if (partial_stream == NULL) {
263
60
    g_prefix_error_literal(error, "failed to cut EFI volume: ");
264
60
    return FALSE;
265
60
  }
266
7.23k
  fu_firmware_set_id(firmware, guid_str);
267
7.23k
  fu_firmware_set_size(firmware, fv_length);
268
269
  /* parse, which might cascade and do something like FFS2 */
270
7.23k
  if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS2) == 0 ||
271
5.60k
      g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS3) == 0) {
272
1.63k
    g_autoptr(FuFirmware) img = fu_efi_filesystem_new();
273
1.63k
    fu_firmware_set_alignment(img, fu_firmware_get_alignment(firmware));
274
1.63k
    if (!fu_firmware_parse_stream(img,
275
1.63k
                partial_stream,
276
1.63k
                0x0,
277
1.63k
                flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
278
1.63k
                error))
279
1.31k
      return FALSE;
280
324
    if (!fu_firmware_add_image(firmware, img, error))
281
0
      return FALSE;
282
5.60k
  } else if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA) == 0 ||
283
3.54k
       g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA2) == 0) {
284
2.05k
    g_autoptr(GError) error_local = NULL;
285
2.05k
    if (!fu_efi_volume_parse_nvram_evsa(self,
286
2.05k
                stream,
287
2.05k
                hdr_length,
288
2.05k
                flags,
289
2.05k
                &error_local)) {
290
1.55k
      g_debug("ignoring %s [%s] EFI FV: %s",
291
1.55k
        guid_str,
292
1.55k
        fu_efi_guid_to_name(guid_str),
293
1.55k
        error_local->message);
294
1.55k
      if (!fu_firmware_set_stream(firmware, partial_stream, error))
295
0
        return FALSE;
296
1.55k
    }
297
3.54k
  } else {
298
3.54k
    g_warning("no idea how to parse %s [%s] EFI volume",
299
3.54k
        guid_str,
300
3.54k
        fu_efi_guid_to_name(guid_str));
301
3.54k
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
302
0
      return FALSE;
303
3.54k
  }
304
305
  /* skip the blockmap */
306
5.92k
  offset += st_hdr->buf->len;
307
645k
  while (offset < streamsz) {
308
645k
    guint32 num_blocks;
309
645k
    guint32 length;
310
645k
    g_autoptr(FuStructEfiVolumeBlockMap) st_blk = NULL;
311
645k
    st_blk = fu_struct_efi_volume_block_map_parse_stream(stream, offset, error);
312
645k
    if (st_blk == NULL)
313
587
      return FALSE;
314
644k
    num_blocks = fu_struct_efi_volume_block_map_get_num_blocks(st_blk);
315
644k
    length = fu_struct_efi_volume_block_map_get_length(st_blk);
316
644k
    offset += st_blk->buf->len;
317
644k
    if (num_blocks == 0x0 && length == 0x0)
318
4.91k
      break;
319
639k
    blockmap_sz += (gsize)num_blocks * (gsize)length;
320
639k
  }
321
5.33k
  if (blockmap_sz < (gsize)fv_length) {
322
126
    g_set_error_literal(error,
323
126
            FWUPD_ERROR,
324
126
            FWUPD_ERROR_INTERNAL,
325
126
            "blocks allocated is less than volume length");
326
126
    return FALSE;
327
126
  }
328
329
  /* success */
330
5.21k
  return TRUE;
331
5.33k
}
332
333
static GByteArray *
334
fu_efi_volume_write(FuFirmware *firmware, GError **error)
335
3.56k
{
336
3.56k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
337
3.56k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
338
3.56k
  g_autoptr(FuStructEfiVolume) st_vol = fu_struct_efi_volume_new();
339
3.56k
  g_autoptr(FuStructEfiVolumeBlockMap) st_blk = fu_struct_efi_volume_block_map_new();
340
3.56k
  fwupd_guid_t guid = {0x0};
341
3.56k
  guint32 hdr_length = 0x48;
342
3.56k
  guint64 fv_length;
343
3.56k
  g_autoptr(FuChunkArray) chunks = NULL;
344
3.56k
  g_autoptr(GBytes) img_blob = NULL;
345
3.56k
  g_autoptr(GPtrArray) images = NULL;
346
347
  /* sanity check */
348
3.56k
  if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) {
349
55
    g_set_error(error,
350
55
          FWUPD_ERROR,
351
55
          FWUPD_ERROR_INVALID_FILE,
352
55
          "alignment invalid, got 0x%02x",
353
55
          fu_firmware_get_alignment(firmware));
354
55
    return NULL;
355
55
  }
356
357
  /* GUID */
358
3.51k
  if (fu_firmware_get_id(firmware) == NULL) {
359
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no GUID set for FV");
360
0
    return NULL;
361
0
  }
362
3.51k
  if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
363
3.51k
            &guid,
364
3.51k
            FWUPD_GUID_FLAG_MIXED_ENDIAN,
365
3.51k
            error))
366
0
    return NULL;
367
368
  /* length */
369
3.51k
  images = fu_firmware_get_images(firmware);
370
3.51k
  if (images->len == 0) {
371
2.83k
    img_blob = fu_firmware_get_bytes_with_patches(firmware, error);
372
2.83k
    if (img_blob == NULL) {
373
30
      g_prefix_error_literal(error, "no EFI FV payload: ");
374
30
      return NULL;
375
30
    }
376
2.83k
  } else {
377
673
    g_autoptr(GByteArray) buf_tmp = g_byte_array_new();
378
1.36k
    for (guint i = 0; i < images->len; i++) {
379
740
      FuFirmware *img = g_ptr_array_index(images, i);
380
740
      g_autoptr(GBytes) img_blob_tmp = NULL;
381
382
740
      img_blob_tmp = fu_firmware_write(img, error);
383
740
      if (img_blob_tmp == NULL) {
384
44
        g_prefix_error_literal(error, "no EFI FV child payload: ");
385
44
        return NULL;
386
44
      }
387
696
      fu_byte_array_append_bytes(buf_tmp, img_blob_tmp);
388
696
    }
389
629
    img_blob =
390
629
        g_byte_array_free_to_bytes(g_steal_pointer(&buf_tmp)); /* nocheck:blocked */
391
629
  }
392
393
  /* pack */
394
3.43k
  fu_struct_efi_volume_set_guid(st_vol, &guid);
395
3.43k
  fv_length = fu_common_align_up(hdr_length + g_bytes_get_size(img_blob),
396
3.43k
               fu_firmware_get_alignment(firmware));
397
398
  /* we want a minimum size of volume */
399
3.43k
  if (fu_firmware_get_size(firmware) > fv_length) {
400
117
    g_debug("padding FV from 0x%x to 0x%x",
401
117
      (guint)fv_length,
402
117
      (guint)fu_firmware_get_size(firmware));
403
117
    fv_length = fu_firmware_get_size(firmware);
404
117
  }
405
406
3.43k
  fu_struct_efi_volume_set_length(st_vol, fv_length);
407
3.43k
  fu_struct_efi_volume_set_attrs(st_vol,
408
3.43k
               priv->attrs |
409
3.43k
             ((guint32)fu_firmware_get_alignment(firmware) << 16));
410
3.43k
  fu_struct_efi_volume_set_hdr_len(st_vol, hdr_length);
411
412
  /* blockmap */
413
3.43k
  chunks = fu_chunk_array_new_virtual(fv_length,
414
3.43k
              FU_CHUNK_ADDR_OFFSET_NONE,
415
3.43k
              FU_CHUNK_PAGESZ_NONE,
416
3.43k
              0x1000);
417
3.43k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, fu_chunk_array_length(chunks));
418
3.43k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x1000);
419
3.43k
  fu_byte_array_append_array(st_vol->buf, st_blk->buf);
420
3.43k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, 0x0);
421
3.43k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x0);
422
3.43k
  fu_byte_array_append_array(st_vol->buf, st_blk->buf);
423
424
  /* fix up checksum */
425
3.43k
  fu_struct_efi_volume_set_checksum(
426
3.43k
      st_vol,
427
3.43k
      0x10000 - fu_sum16w(st_vol->buf->data, st_vol->buf->len, G_LITTLE_ENDIAN));
428
429
  /* pad contents to alignment */
430
3.43k
  fu_byte_array_append_bytes(st_vol->buf, img_blob);
431
3.43k
  fu_byte_array_set_size(st_vol->buf, fv_length, 0xFF);
432
433
  /* success */
434
3.43k
  return g_steal_pointer(&st_vol->buf);
435
3.51k
}
436
437
static void
438
fu_efi_volume_init(FuEfiVolume *self)
439
15.9k
{
440
15.9k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
441
15.9k
  priv->attrs = 0xfeff;
442
15.9k
#ifdef HAVE_FUZZER
443
15.9k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 0x100000); /* 1MB */
444
15.9k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 10);
445
#else
446
  fu_firmware_set_size_max(FU_FIRMWARE(self), 0x10000000); /* 256MB */
447
  fu_firmware_set_images_max(FU_FIRMWARE(self), 1000);
448
#endif
449
15.9k
  g_type_ensure(FU_TYPE_EFI_FILESYSTEM);
450
15.9k
  g_type_ensure(FU_TYPE_EFI_VSS2_VARIABLE_STORE);
451
15.9k
  g_type_ensure(FU_TYPE_EFI_FTW_STORE);
452
15.9k
}
453
454
static void
455
fu_efi_volume_class_init(FuEfiVolumeClass *klass)
456
3
{
457
3
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
458
3
  firmware_class->validate = fu_efi_volume_validate;
459
3
  firmware_class->parse = fu_efi_volume_parse;
460
3
  firmware_class->write = fu_efi_volume_write;
461
3
  firmware_class->export = fu_efi_volume_export;
462
3
}
463
464
/**
465
 * fu_efi_volume_new:
466
 *
467
 * Creates a new #FuFirmware
468
 *
469
 * Since: 2.0.0
470
 **/
471
FuFirmware *
472
fu_efi_volume_new(void)
473
12.6k
{
474
12.6k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_VOLUME, NULL));
475
12.6k
}