Coverage Report

Created: 2025-07-11 06:31

/src/fwupd/libfwupdplugin/fu-efi-volume.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2021 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
11.6k
#define G_LOG_DOMAIN "FuEfiVolume"
8
9
#include "config.h"
10
11
#include "fu-byte-array.h"
12
#include "fu-common.h"
13
#include "fu-efi-common.h"
14
#include "fu-efi-filesystem.h"
15
#include "fu-efi-struct.h"
16
#include "fu-efi-volume.h"
17
#include "fu-input-stream.h"
18
#include "fu-partial-input-stream.h"
19
#include "fu-sum.h"
20
21
/**
22
 * FuEfiVolume:
23
 *
24
 * A UEFI file volume.
25
 *
26
 * See also: [class@FuFirmware]
27
 */
28
29
typedef struct {
30
  guint16 attrs;
31
} FuEfiVolumePrivate;
32
33
G_DEFINE_TYPE_WITH_PRIVATE(FuEfiVolume, fu_efi_volume, FU_TYPE_FIRMWARE)
34
24.5k
#define GET_PRIVATE(o) (fu_efi_volume_get_instance_private(o))
35
36
static void
37
fu_efi_volume_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
38
0
{
39
0
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
40
0
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
41
0
  fu_xmlb_builder_insert_kx(bn, "attrs", priv->attrs);
42
0
  if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) {
43
0
    fu_xmlb_builder_insert_kv(bn,
44
0
            "name",
45
0
            fu_efi_guid_to_name(fu_firmware_get_id(firmware)));
46
0
  }
47
0
}
48
49
static gboolean
50
fu_efi_volume_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
51
4.93M
{
52
4.93M
  return fu_struct_efi_volume_validate_stream(stream, offset, error);
53
4.93M
}
54
55
static gboolean
56
fu_efi_volume_parse(FuFirmware *firmware,
57
        GInputStream *stream,
58
        FuFirmwareParseFlags flags,
59
        GError **error)
60
8.49k
{
61
8.49k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
62
8.49k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
63
8.49k
  gsize blockmap_sz = 0;
64
8.49k
  gsize offset = 0;
65
8.49k
  gsize streamsz = 0;
66
8.49k
  guint16 hdr_length = 0;
67
8.49k
  guint32 attrs = 0;
68
8.49k
  guint64 fv_length = 0;
69
8.49k
  guint8 alignment;
70
8.49k
  g_autofree gchar *guid_str = NULL;
71
8.49k
  g_autoptr(GByteArray) st_hdr = NULL;
72
8.49k
  g_autoptr(GInputStream) partial_stream = NULL;
73
74
  /* parse */
75
8.49k
  st_hdr = fu_struct_efi_volume_parse_stream(stream, 0x0, error);
76
8.49k
  if (st_hdr == NULL)
77
0
    return FALSE;
78
79
  /* guid */
80
8.49k
  guid_str = fwupd_guid_to_string(fu_struct_efi_volume_get_guid(st_hdr),
81
8.49k
          FWUPD_GUID_FLAG_MIXED_ENDIAN);
82
8.49k
  g_debug("volume GUID: %s [%s]", guid_str, fu_efi_guid_to_name(guid_str));
83
84
  /* length */
85
8.49k
  if (!fu_input_stream_size(stream, &streamsz, error))
86
0
    return FALSE;
87
8.49k
  fv_length = fu_struct_efi_volume_get_length(st_hdr);
88
8.49k
  if (fv_length == 0x0) {
89
28
    g_set_error_literal(error,
90
28
            FWUPD_ERROR,
91
28
            FWUPD_ERROR_INTERNAL,
92
28
            "invalid volume length");
93
28
    return FALSE;
94
28
  }
95
8.46k
  fu_firmware_set_size(firmware, fv_length);
96
8.46k
  attrs = fu_struct_efi_volume_get_attrs(st_hdr);
97
8.46k
  alignment = (attrs & 0x00ff0000) >> 16;
98
8.46k
  if (alignment > FU_FIRMWARE_ALIGNMENT_2G) {
99
465
    g_set_error(error,
100
465
          FWUPD_ERROR,
101
465
          FWUPD_ERROR_NOT_FOUND,
102
465
          "0x%x invalid, maximum is 0x%x",
103
465
          (guint)alignment,
104
465
          (guint)FU_FIRMWARE_ALIGNMENT_2G);
105
465
    return FALSE;
106
465
  }
107
8.00k
  fu_firmware_set_alignment(firmware, alignment);
108
8.00k
  priv->attrs = attrs & 0xffff;
109
8.00k
  hdr_length = fu_struct_efi_volume_get_hdr_len(st_hdr);
110
8.00k
  if (hdr_length < st_hdr->len || hdr_length > fv_length || hdr_length > streamsz ||
111
8.00k
      hdr_length % 2 != 0) {
112
1.56k
    g_set_error(error,
113
1.56k
          FWUPD_ERROR,
114
1.56k
          FWUPD_ERROR_INTERNAL,
115
1.56k
          "invalid volume header length 0x%x",
116
1.56k
          (guint)hdr_length);
117
1.56k
    return FALSE;
118
1.56k
  }
119
120
  /* verify checksum */
121
6.43k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
122
6.43k
    guint16 checksum_verify;
123
6.43k
    g_autoptr(GBytes) blob_hdr = NULL;
124
125
6.43k
    blob_hdr = fu_input_stream_read_bytes(stream, 0x0, hdr_length, NULL, error);
126
6.43k
    if (blob_hdr == NULL)
127
0
      return FALSE;
128
6.43k
    checksum_verify = fu_sum16w_bytes(blob_hdr, G_LITTLE_ENDIAN);
129
6.43k
    if (checksum_verify != 0) {
130
540
      g_set_error(error,
131
540
            FWUPD_ERROR,
132
540
            FWUPD_ERROR_INVALID_FILE,
133
540
            "checksum invalid, got %02x, expected %02x",
134
540
            checksum_verify,
135
540
            fu_struct_efi_volume_get_checksum(st_hdr));
136
540
      return FALSE;
137
540
    }
138
6.43k
  }
139
140
  /* extended header items */
141
5.89k
  if (fu_struct_efi_volume_get_ext_hdr(st_hdr) != 0) {
142
401
    g_autoptr(GByteArray) st_ext_hdr = NULL;
143
401
    goffset offset_ext = fu_struct_efi_volume_get_ext_hdr(st_hdr);
144
401
    st_ext_hdr =
145
401
        fu_struct_efi_volume_ext_header_parse_stream(stream, offset_ext, error);
146
401
    if (st_ext_hdr == NULL)
147
52
      return FALSE;
148
349
    offset_ext += fu_struct_efi_volume_ext_header_get_size(st_ext_hdr);
149
3.44k
    do {
150
3.44k
      g_autoptr(GByteArray) st_ext_entry = NULL;
151
3.44k
      st_ext_entry =
152
3.44k
          fu_struct_efi_volume_ext_entry_parse_stream(stream, offset_ext, error);
153
3.44k
      if (st_ext_entry == NULL)
154
173
        return FALSE;
155
3.27k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0x0) {
156
39
        g_set_error_literal(error,
157
39
                FWUPD_ERROR,
158
39
                FWUPD_ERROR_INVALID_DATA,
159
39
                "EFI_VOLUME_EXT_ENTRY invalid size");
160
39
        return FALSE;
161
39
      }
162
3.23k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0xFFFF)
163
29
        break;
164
3.20k
      offset_ext += fu_struct_efi_volume_ext_entry_get_size(st_ext_entry);
165
3.20k
    } while ((gsize)offset_ext < fv_length);
166
349
  }
167
168
  /* add image */
169
5.63k
  partial_stream =
170
5.63k
      fu_partial_input_stream_new(stream, hdr_length, fv_length - hdr_length, error);
171
5.63k
  if (partial_stream == NULL) {
172
292
    g_prefix_error(error, "failed to cut EFI volume: ");
173
292
    return FALSE;
174
292
  }
175
5.33k
  fu_firmware_set_id(firmware, guid_str);
176
5.33k
  fu_firmware_set_size(firmware, fv_length);
177
178
  /* parse, which might cascade and do something like FFS2 */
179
5.33k
  if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS2) == 0 ||
180
5.33k
      g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS3) == 0) {
181
2.13k
    g_autoptr(FuFirmware) img = fu_efi_filesystem_new();
182
2.13k
    fu_firmware_set_alignment(img, fu_firmware_get_alignment(firmware));
183
2.13k
    if (!fu_firmware_parse_stream(img,
184
2.13k
                partial_stream,
185
2.13k
                0x0,
186
2.13k
                flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
187
2.13k
                error))
188
1.62k
      return FALSE;
189
510
    fu_firmware_add_image(firmware, img);
190
3.20k
  } else if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA) == 0 ||
191
3.20k
       g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA2) == 0) {
192
7
    g_debug("ignoring %s [%s] EFI FV", guid_str, fu_efi_guid_to_name(guid_str));
193
7
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
194
0
      return FALSE;
195
3.19k
  } else {
196
3.19k
    g_warning("no idea how to parse %s [%s] EFI volume",
197
3.19k
        guid_str,
198
3.19k
        fu_efi_guid_to_name(guid_str));
199
3.19k
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
200
0
      return FALSE;
201
3.19k
  }
202
203
  /* skip the blockmap */
204
3.71k
  offset += st_hdr->len;
205
344k
  while (offset < streamsz) {
206
344k
    guint32 num_blocks;
207
344k
    guint32 length;
208
344k
    g_autoptr(GByteArray) st_blk = NULL;
209
344k
    st_blk = fu_struct_efi_volume_block_map_parse_stream(stream, offset, error);
210
344k
    if (st_blk == NULL)
211
116
      return FALSE;
212
344k
    num_blocks = fu_struct_efi_volume_block_map_get_num_blocks(st_blk);
213
344k
    length = fu_struct_efi_volume_block_map_get_length(st_blk);
214
344k
    offset += st_blk->len;
215
344k
    if (num_blocks == 0x0 && length == 0x0)
216
3.38k
      break;
217
341k
    blockmap_sz += (gsize)num_blocks * (gsize)length;
218
341k
  }
219
3.59k
  if (blockmap_sz < (gsize)fv_length) {
220
76
    g_set_error_literal(error,
221
76
            FWUPD_ERROR,
222
76
            FWUPD_ERROR_INTERNAL,
223
76
            "blocks allocated is less than volume length");
224
76
    return FALSE;
225
76
  }
226
227
  /* success */
228
3.51k
  return TRUE;
229
3.59k
}
230
231
static GByteArray *
232
fu_efi_volume_write(FuFirmware *firmware, GError **error)
233
1.99k
{
234
1.99k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
235
1.99k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
236
1.99k
  g_autoptr(GByteArray) buf = fu_struct_efi_volume_new();
237
1.99k
  g_autoptr(GByteArray) st_blk = fu_struct_efi_volume_block_map_new();
238
1.99k
  fwupd_guid_t guid = {0x0};
239
1.99k
  guint32 hdr_length = 0x48;
240
1.99k
  guint64 fv_length;
241
1.99k
  g_autoptr(GBytes) img_blob = NULL;
242
1.99k
  g_autoptr(FuFirmware) img = NULL;
243
244
  /* sanity check */
245
1.99k
  if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) {
246
41
    g_set_error(error,
247
41
          FWUPD_ERROR,
248
41
          FWUPD_ERROR_INVALID_FILE,
249
41
          "alignment invalid, got 0x%02x",
250
41
          fu_firmware_get_alignment(firmware));
251
41
    return NULL;
252
41
  }
253
254
  /* GUID */
255
1.95k
  if (fu_firmware_get_id(firmware) == NULL) {
256
0
    g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no GUID set for EFI FV");
257
0
    return NULL;
258
0
  }
259
1.95k
  if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
260
1.95k
            &guid,
261
1.95k
            FWUPD_GUID_FLAG_MIXED_ENDIAN,
262
1.95k
            error))
263
0
    return NULL;
264
265
  /* length */
266
1.95k
  img = fu_firmware_get_image_by_id(firmware, NULL, NULL);
267
1.95k
  if (img != NULL) {
268
304
    img_blob = fu_firmware_write(img, error);
269
304
    if (img_blob == NULL) {
270
83
      g_prefix_error(error, "no EFI FV child payload: ");
271
83
      return NULL;
272
83
    }
273
1.65k
  } else {
274
1.65k
    img_blob = fu_firmware_get_bytes_with_patches(firmware, error);
275
1.65k
    if (img_blob == NULL) {
276
40
      g_prefix_error(error, "no EFI FV payload: ");
277
40
      return NULL;
278
40
    }
279
1.65k
  }
280
281
  /* pack */
282
1.83k
  fu_struct_efi_volume_set_guid(buf, &guid);
283
1.83k
  fv_length = fu_common_align_up(hdr_length + g_bytes_get_size(img_blob),
284
1.83k
               fu_firmware_get_alignment(firmware));
285
1.83k
  fu_struct_efi_volume_set_length(buf, fv_length);
286
1.83k
  fu_struct_efi_volume_set_attrs(buf,
287
1.83k
               priv->attrs |
288
1.83k
             ((guint32)fu_firmware_get_alignment(firmware) << 16));
289
1.83k
  fu_struct_efi_volume_set_hdr_len(buf, hdr_length);
290
291
  /* blockmap */
292
1.83k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, fv_length);
293
1.83k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x1);
294
1.83k
  g_byte_array_append(buf, st_blk->data, st_blk->len);
295
1.83k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, 0x0);
296
1.83k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x0);
297
1.83k
  g_byte_array_append(buf, st_blk->data, st_blk->len);
298
299
  /* fix up checksum */
300
1.83k
  fu_struct_efi_volume_set_checksum(buf,
301
1.83k
            0x10000 -
302
1.83k
                fu_sum16w(buf->data, buf->len, G_LITTLE_ENDIAN));
303
304
  /* pad contents to alignment */
305
1.83k
  fu_byte_array_append_bytes(buf, img_blob);
306
1.83k
  fu_byte_array_set_size(buf, fv_length, 0xFF);
307
308
  /* success */
309
1.83k
  return g_steal_pointer(&buf);
310
1.95k
}
311
312
static void
313
fu_efi_volume_init(FuEfiVolume *self)
314
14.0k
{
315
14.0k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
316
14.0k
  priv->attrs = 0xfeff;
317
14.0k
  g_type_ensure(FU_TYPE_EFI_FILESYSTEM);
318
14.0k
}
319
320
static void
321
fu_efi_volume_class_init(FuEfiVolumeClass *klass)
322
3
{
323
3
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
324
3
  firmware_class->validate = fu_efi_volume_validate;
325
3
  firmware_class->parse = fu_efi_volume_parse;
326
3
  firmware_class->write = fu_efi_volume_write;
327
3
  firmware_class->export = fu_efi_volume_export;
328
3
}
329
330
/**
331
 * fu_efi_volume_new:
332
 *
333
 * Creates a new #FuFirmware
334
 *
335
 * Since: 2.0.0
336
 **/
337
FuFirmware *
338
fu_efi_volume_new(void)
339
12.1k
{
340
12.1k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_VOLUME, NULL));
341
12.1k
}