Coverage Report

Created: 2026-04-28 06:49

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
23.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
99.9k
G_DEFINE_TYPE_WITH_PRIVATE(FuEfiVolume, fu_efi_volume, FU_TYPE_FIRMWARE)
38
99.9k
#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
34.5k
{
56
34.5k
  return fu_struct_efi_volume_validate_stream(stream, offset, error);
57
34.5k
}
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
6.20k
{
66
6.20k
  gsize streamsz = 0;
67
6.20k
  guint found_cnt = 0;
68
6.20k
  gsize offset_last = offset;
69
70
6.20k
  if (!fu_input_stream_size(stream, &streamsz, error))
71
0
    return FALSE;
72
137k
  while (offset < streamsz) {
73
131k
    g_autoptr(FuFirmware) img = NULL;
74
131k
    g_autoptr(GError) error_local = NULL;
75
76
    /* try to find a NVRAM store */
77
131k
    img = fu_firmware_new_from_gtypes(stream,
78
131k
              offset,
79
131k
              flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
80
131k
              &error_local,
81
131k
              FU_TYPE_EFI_VSS2_VARIABLE_STORE,
82
131k
              FU_TYPE_EFI_FTW_STORE,
83
131k
              G_TYPE_INVALID);
84
131k
    if (img == NULL) {
85
130k
      if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA)) {
86
6.07k
        g_debug("ignoring EFI NVRAM @0x%x: %s",
87
6.07k
          (guint)offset,
88
6.07k
          error_local->message);
89
6.07k
      }
90
130k
      if (!fu_size_checked_inc(&offset, 4 * FU_KB, error)) {
91
0
        g_prefix_error_literal(error, "NVRAM scan offset overflow: ");
92
0
        return FALSE;
93
0
      }
94
130k
      continue;
95
130k
    }
96
97
    /* sanity check */
98
1.37k
    if (fu_firmware_get_size(img) == 0) {
99
14
      g_set_error_literal(error,
100
14
              FWUPD_ERROR,
101
14
              FWUPD_ERROR_INTERNAL,
102
14
              "NVRAM store entry has zero size");
103
14
      return FALSE;
104
14
    }
105
106
    /* preserve the exact padding between EVSA stores */
107
1.35k
    if (offset != offset_last) {
108
527
      g_autoptr(GBytes) blob = g_bytes_new(NULL, 0);
109
527
      g_autoptr(GBytes) blob_padded =
110
527
          fu_bytes_pad(blob, offset - offset_last, 0xFF);
111
527
      g_autoptr(FuFirmware) img_padded = fu_firmware_new_from_bytes(blob_padded);
112
527
      if (!fu_firmware_add_image(FU_FIRMWARE(self), img_padded, error))
113
0
        return FALSE;
114
527
    }
115
116
    /* we found something */
117
1.35k
    fu_firmware_set_offset(img, offset);
118
1.35k
    if (!fu_firmware_add_image(FU_FIRMWARE(self), img, error))
119
0
      return FALSE;
120
1.35k
    if (!fu_size_checked_inc(&offset, fu_firmware_get_size(img), error))
121
0
      return FALSE;
122
1.35k
    offset = fu_common_align_up(offset, FU_FIRMWARE_ALIGNMENT_4K);
123
1.35k
    found_cnt += 1;
124
125
    /* the last thing we found */
126
1.35k
    offset_last = offset;
127
1.35k
  }
128
129
  /* we found nothing */
130
6.19k
  if (found_cnt == 0) {
131
4.84k
    g_set_error_literal(error,
132
4.84k
            FWUPD_ERROR,
133
4.84k
            FWUPD_ERROR_INTERNAL,
134
4.84k
            "no NVRAM stores found");
135
4.84k
    return FALSE;
136
4.84k
  }
137
138
  /* success */
139
1.34k
  return TRUE;
140
6.19k
}
141
142
static gboolean
143
fu_efi_volume_parse(FuFirmware *firmware,
144
        GInputStream *stream,
145
        FuFirmwareParseFlags flags,
146
        GError **error)
147
12.6k
{
148
12.6k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
149
12.6k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
150
12.6k
  gsize blockmap_sz = 0;
151
12.6k
  gsize offset = 0;
152
12.6k
  gsize streamsz = 0;
153
12.6k
  guint16 hdr_length = 0;
154
12.6k
  guint32 attrs = 0;
155
12.6k
  guint64 fv_length = 0;
156
12.6k
  guint8 alignment;
157
12.6k
  g_autofree gchar *guid_str = NULL;
158
12.6k
  g_autoptr(FuStructEfiVolume) st_hdr = NULL;
159
12.6k
  g_autoptr(GInputStream) partial_stream = NULL;
160
161
  /* parse */
162
12.6k
  st_hdr = fu_struct_efi_volume_parse_stream(stream, 0x0, error);
163
12.6k
  if (st_hdr == NULL)
164
0
    return FALSE;
165
166
  /* guid */
167
12.6k
  guid_str = fwupd_guid_to_string(fu_struct_efi_volume_get_guid(st_hdr),
168
12.6k
          FWUPD_GUID_FLAG_MIXED_ENDIAN);
169
12.6k
  g_debug("volume GUID: %s [%s]", guid_str, fu_efi_guid_to_name(guid_str));
170
171
  /* length */
172
12.6k
  if (!fu_input_stream_size(stream, &streamsz, error))
173
0
    return FALSE;
174
12.6k
  fv_length = fu_struct_efi_volume_get_length(st_hdr);
175
12.6k
  if (fv_length == 0x0) {
176
24
    g_set_error_literal(error,
177
24
            FWUPD_ERROR,
178
24
            FWUPD_ERROR_INTERNAL,
179
24
            "invalid volume length");
180
24
    return FALSE;
181
24
  }
182
12.6k
  if (fv_length > fu_firmware_get_size_max(firmware)) {
183
3.02k
    g_set_error(error,
184
3.02k
          FWUPD_ERROR,
185
3.02k
          FWUPD_ERROR_INTERNAL,
186
3.02k
          "volume length larger than max size: 0x%x > 0x%x",
187
3.02k
          (guint)fv_length,
188
3.02k
          (guint)fu_firmware_get_size_max(firmware));
189
3.02k
    return FALSE;
190
3.02k
  }
191
9.63k
  attrs = fu_struct_efi_volume_get_attrs(st_hdr);
192
9.63k
  alignment = (attrs & 0x00ff0000) >> 16;
193
9.63k
  if (alignment > FU_FIRMWARE_ALIGNMENT_2G) {
194
56
    g_set_error(error,
195
56
          FWUPD_ERROR,
196
56
          FWUPD_ERROR_NOT_FOUND,
197
56
          "0x%x invalid, maximum is 0x%x",
198
56
          (guint)alignment,
199
56
          (guint)FU_FIRMWARE_ALIGNMENT_2G);
200
56
    return FALSE;
201
56
  }
202
9.57k
  fu_firmware_set_alignment(firmware, alignment);
203
9.57k
  priv->attrs = attrs & 0xffff;
204
9.57k
  hdr_length = fu_struct_efi_volume_get_hdr_len(st_hdr);
205
9.57k
  if (hdr_length < st_hdr->buf->len || hdr_length > fv_length || hdr_length > streamsz ||
206
9.32k
      hdr_length % 2 != 0) {
207
264
    g_set_error(error,
208
264
          FWUPD_ERROR,
209
264
          FWUPD_ERROR_INTERNAL,
210
264
          "invalid volume header length 0x%x",
211
264
          (guint)hdr_length);
212
264
    return FALSE;
213
264
  }
214
215
  /* verify checksum */
216
9.31k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
217
0
    guint16 checksum_verify;
218
0
    g_autoptr(GBytes) blob_hdr = NULL;
219
220
0
    blob_hdr = fu_input_stream_read_bytes(stream, 0x0, hdr_length, NULL, error);
221
0
    if (blob_hdr == NULL)
222
0
      return FALSE;
223
0
    checksum_verify = fu_sum16w_bytes(blob_hdr, G_LITTLE_ENDIAN);
224
0
    if (checksum_verify != 0) {
225
0
      g_set_error(error,
226
0
            FWUPD_ERROR,
227
0
            FWUPD_ERROR_INVALID_FILE,
228
0
            "checksum invalid, got %02x, expected %02x",
229
0
            checksum_verify,
230
0
            fu_struct_efi_volume_get_checksum(st_hdr));
231
0
      return FALSE;
232
0
    }
233
0
  }
234
235
  /* extended header items */
236
9.31k
  if (fu_struct_efi_volume_get_ext_hdr(st_hdr) != 0) {
237
753
    g_autoptr(FuStructEfiVolumeExtHeader) st_ext_hdr = NULL;
238
753
    gsize offset_ext = fu_struct_efi_volume_get_ext_hdr(st_hdr);
239
753
    st_ext_hdr =
240
753
        fu_struct_efi_volume_ext_header_parse_stream(stream, offset_ext, error);
241
753
    if (st_ext_hdr == NULL)
242
108
      return FALSE;
243
645
    if (!fu_size_checked_inc(&offset_ext,
244
645
           fu_struct_efi_volume_ext_header_get_size(st_ext_hdr),
245
645
           error))
246
0
      return FALSE;
247
4.65k
    do {
248
4.65k
      g_autoptr(FuStructEfiVolumeExtEntry) st_ext_entry = NULL;
249
4.65k
      st_ext_entry =
250
4.65k
          fu_struct_efi_volume_ext_entry_parse_stream(stream, offset_ext, error);
251
4.65k
      if (st_ext_entry == NULL)
252
185
        return FALSE;
253
4.46k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0x0) {
254
54
        g_set_error_literal(error,
255
54
                FWUPD_ERROR,
256
54
                FWUPD_ERROR_INVALID_DATA,
257
54
                "EFI_VOLUME_EXT_ENTRY invalid size");
258
54
        return FALSE;
259
54
      }
260
4.41k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0xFFFF)
261
30
        break;
262
4.38k
      if (!fu_size_checked_inc(
263
4.38k
        &offset_ext,
264
4.38k
        fu_struct_efi_volume_ext_entry_get_size(st_ext_entry),
265
4.38k
        error))
266
0
        return FALSE;
267
4.38k
    } while (offset_ext < fv_length);
268
645
  }
269
270
  /* add image */
271
8.96k
  partial_stream =
272
8.96k
      fu_partial_input_stream_new(stream, hdr_length, fv_length - hdr_length, error);
273
8.96k
  if (partial_stream == NULL) {
274
143
    g_prefix_error_literal(error, "failed to cut EFI volume: ");
275
143
    return FALSE;
276
143
  }
277
8.82k
  fu_firmware_set_id(firmware, guid_str);
278
8.82k
  fu_firmware_set_size(firmware, fv_length);
279
280
  /* parse, which might cascade and do something like FFS2 */
281
8.82k
  if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS2) == 0 ||
282
7.58k
      g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS3) == 0) {
283
1.24k
    g_autoptr(FuFirmware) img = fu_efi_filesystem_new();
284
1.24k
    fu_firmware_set_alignment(img, fu_firmware_get_alignment(firmware));
285
1.24k
    if (!fu_firmware_parse_stream(img,
286
1.24k
                partial_stream,
287
1.24k
                0x0,
288
1.24k
                flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
289
1.24k
                error))
290
1.09k
      return FALSE;
291
150
    if (!fu_firmware_add_image(firmware, img, error))
292
0
      return FALSE;
293
7.58k
  } else if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA) == 0 ||
294
6.20k
       g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA2) == 0) {
295
6.20k
    g_autoptr(GError) error_local = NULL;
296
6.20k
    if (!fu_efi_volume_parse_nvram_evsa(self,
297
6.20k
                stream,
298
6.20k
                hdr_length,
299
6.20k
                flags,
300
6.20k
                &error_local)) {
301
4.85k
      g_debug("ignoring %s [%s] EFI FV: %s",
302
4.85k
        guid_str,
303
4.85k
        fu_efi_guid_to_name(guid_str),
304
4.85k
        error_local->message);
305
4.85k
      if (!fu_firmware_set_stream(firmware, partial_stream, error))
306
0
        return FALSE;
307
4.85k
    }
308
6.20k
  } else {
309
#ifndef HAVE_FUZZER
310
    g_warning("no idea how to parse %s [%s] EFI volume",
311
        guid_str,
312
        fu_efi_guid_to_name(guid_str));
313
#endif
314
1.37k
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
315
0
      return FALSE;
316
1.37k
  }
317
318
  /* skip the blockmap */
319
7.73k
  if (!fu_size_checked_inc(&offset, st_hdr->buf->len, error)) {
320
0
    g_prefix_error_literal(error, "block map offset overflow: ");
321
0
    return FALSE;
322
0
  }
323
1.15M
  while (offset < streamsz) {
324
1.14M
    guint32 num_blocks;
325
1.14M
    guint32 length;
326
1.14M
    g_autoptr(FuStructEfiVolumeBlockMap) st_blk = NULL;
327
1.14M
    st_blk = fu_struct_efi_volume_block_map_parse_stream(stream, offset, error);
328
1.14M
    if (st_blk == NULL)
329
2.08k
      return FALSE;
330
1.14M
    num_blocks = fu_struct_efi_volume_block_map_get_num_blocks(st_blk);
331
1.14M
    length = fu_struct_efi_volume_block_map_get_length(st_blk);
332
1.14M
    if (!fu_size_checked_inc(&offset, st_blk->buf->len, error)) {
333
0
      g_prefix_error_literal(error, "block map entry offset overflow: ");
334
0
      return FALSE;
335
0
    }
336
1.14M
    if (num_blocks == 0x0 && length == 0x0)
337
4.37k
      break;
338
1.14M
    blockmap_sz += (gsize)num_blocks * (gsize)length;
339
1.14M
  }
340
5.64k
  if (blockmap_sz < (gsize)fv_length) {
341
311
    g_set_error_literal(error,
342
311
            FWUPD_ERROR,
343
311
            FWUPD_ERROR_INTERNAL,
344
311
            "blocks allocated is less than volume length");
345
311
    return FALSE;
346
311
  }
347
348
  /* success */
349
5.33k
  return TRUE;
350
5.64k
}
351
352
static GByteArray *
353
fu_efi_volume_write(FuFirmware *firmware, GError **error)
354
3.48k
{
355
3.48k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
356
3.48k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
357
3.48k
  g_autoptr(FuStructEfiVolume) st_vol = fu_struct_efi_volume_new();
358
3.48k
  g_autoptr(FuStructEfiVolumeBlockMap) st_blk = fu_struct_efi_volume_block_map_new();
359
3.48k
  fwupd_guid_t guid = {0x0};
360
3.48k
  guint32 hdr_length = 0x48;
361
3.48k
  guint64 fv_length;
362
3.48k
  g_autoptr(FuChunkArray) chunks = NULL;
363
3.48k
  g_autoptr(GBytes) img_blob = NULL;
364
3.48k
  g_autoptr(GPtrArray) images = NULL;
365
366
  /* sanity check */
367
3.48k
  if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) {
368
12
    g_set_error(error,
369
12
          FWUPD_ERROR,
370
12
          FWUPD_ERROR_INVALID_FILE,
371
12
          "alignment invalid, got 0x%02x",
372
12
          fu_firmware_get_alignment(firmware));
373
12
    return NULL;
374
12
  }
375
376
  /* GUID */
377
3.47k
  if (fu_firmware_get_id(firmware) == NULL) {
378
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no GUID set for FV");
379
0
    return NULL;
380
0
  }
381
3.47k
  if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
382
3.47k
            &guid,
383
3.47k
            FWUPD_GUID_FLAG_MIXED_ENDIAN,
384
3.47k
            error))
385
0
    return NULL;
386
387
  /* length */
388
3.47k
  images = fu_firmware_get_images(firmware);
389
3.47k
  if (images->len == 0) {
390
2.45k
    img_blob = fu_firmware_get_bytes_with_patches(firmware, error);
391
2.45k
    if (img_blob == NULL) {
392
54
      g_prefix_error_literal(error, "no EFI FV payload: ");
393
54
      return NULL;
394
54
    }
395
2.45k
  } else {
396
1.02k
    g_autoptr(GByteArray) buf_tmp = g_byte_array_new();
397
2.35k
    for (guint i = 0; i < images->len; i++) {
398
1.48k
      FuFirmware *img = g_ptr_array_index(images, i);
399
1.48k
      g_autoptr(GBytes) img_blob_tmp = NULL;
400
401
1.48k
      img_blob_tmp = fu_firmware_write(img, error);
402
1.48k
      if (img_blob_tmp == NULL) {
403
155
        g_prefix_error_literal(error, "no EFI FV child payload: ");
404
155
        return NULL;
405
155
      }
406
1.32k
      fu_byte_array_append_bytes(buf_tmp, img_blob_tmp);
407
1.32k
    }
408
871
    img_blob =
409
871
        g_byte_array_free_to_bytes(g_steal_pointer(&buf_tmp)); /* nocheck:blocked */
410
871
  }
411
412
  /* pack */
413
3.26k
  fu_struct_efi_volume_set_guid(st_vol, &guid);
414
3.26k
  fv_length = fu_common_align_up(hdr_length + g_bytes_get_size(img_blob),
415
3.26k
               fu_firmware_get_alignment(firmware));
416
417
  /* we want a minimum size of volume */
418
3.26k
  if (fu_firmware_get_size(firmware) > fv_length) {
419
50
    g_debug("padding FV from 0x%x to 0x%x",
420
50
      (guint)fv_length,
421
50
      (guint)fu_firmware_get_size(firmware));
422
50
    fv_length = fu_firmware_get_size(firmware);
423
50
  }
424
425
3.26k
  fu_struct_efi_volume_set_length(st_vol, fv_length);
426
3.26k
  fu_struct_efi_volume_set_attrs(st_vol,
427
3.26k
               priv->attrs |
428
3.26k
             ((guint32)fu_firmware_get_alignment(firmware) << 16));
429
3.26k
  fu_struct_efi_volume_set_hdr_len(st_vol, hdr_length);
430
431
  /* blockmap */
432
3.26k
  chunks = fu_chunk_array_new_virtual(fv_length,
433
3.26k
              FU_CHUNK_ADDR_OFFSET_NONE,
434
3.26k
              FU_CHUNK_PAGESZ_NONE,
435
3.26k
              4 * FU_KB);
436
3.26k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, fu_chunk_array_length(chunks));
437
3.26k
  fu_struct_efi_volume_block_map_set_length(st_blk, 4 * FU_KB);
438
3.26k
  fu_byte_array_append_array(st_vol->buf, st_blk->buf);
439
3.26k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, 0x0);
440
3.26k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x0);
441
3.26k
  fu_byte_array_append_array(st_vol->buf, st_blk->buf);
442
443
  /* fix up checksum */
444
3.26k
  fu_struct_efi_volume_set_checksum(
445
3.26k
      st_vol,
446
3.26k
      0x10000 - fu_sum16w(st_vol->buf->data, st_vol->buf->len, G_LITTLE_ENDIAN));
447
448
  /* pad contents to alignment */
449
3.26k
  fu_byte_array_append_bytes(st_vol->buf, img_blob);
450
3.26k
  fu_byte_array_set_size(st_vol->buf, fv_length, 0xFF);
451
452
  /* success */
453
3.26k
  return g_steal_pointer(&st_vol->buf);
454
3.47k
}
455
456
static void
457
fu_efi_volume_init(FuEfiVolume *self)
458
34.5k
{
459
34.5k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
460
34.5k
  priv->attrs = 0xfeff;
461
34.5k
#ifdef HAVE_FUZZER
462
34.5k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB);
463
34.5k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 10);
464
#else
465
  fu_firmware_set_size_max(FU_FIRMWARE(self), 256 * FU_MB);
466
  fu_firmware_set_images_max(FU_FIRMWARE(self), 1000);
467
#endif
468
34.5k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE);
469
34.5k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_FILESYSTEM);
470
34.5k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_VSS2_VARIABLE_STORE);
471
34.5k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_FTW_STORE);
472
34.5k
}
473
474
static void
475
fu_efi_volume_class_init(FuEfiVolumeClass *klass)
476
3
{
477
3
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
478
3
  firmware_class->validate = fu_efi_volume_validate;
479
3
  firmware_class->parse = fu_efi_volume_parse;
480
3
  firmware_class->write = fu_efi_volume_write;
481
3
  firmware_class->export = fu_efi_volume_export;
482
3
}
483
484
/**
485
 * fu_efi_volume_new:
486
 *
487
 * Creates a new #FuFirmware
488
 *
489
 * Since: 2.0.0
490
 **/
491
FuFirmware *
492
fu_efi_volume_new(void)
493
30.2k
{
494
30.2k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_VOLUME, NULL));
495
30.2k
}