Coverage Report

Created: 2025-07-01 07:09

/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
13.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
27.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
11.2M
{
52
11.2M
  return fu_struct_efi_volume_validate_stream(stream, offset, error);
53
11.2M
}
54
55
static gboolean
56
fu_efi_volume_parse(FuFirmware *firmware,
57
        GInputStream *stream,
58
        FuFirmwareParseFlags flags,
59
        GError **error)
60
9.78k
{
61
9.78k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
62
9.78k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
63
9.78k
  gsize blockmap_sz = 0;
64
9.78k
  gsize offset = 0;
65
9.78k
  gsize streamsz = 0;
66
9.78k
  guint16 hdr_length = 0;
67
9.78k
  guint32 attrs = 0;
68
9.78k
  guint64 fv_length = 0;
69
9.78k
  guint8 alignment;
70
9.78k
  g_autofree gchar *guid_str = NULL;
71
9.78k
  g_autoptr(GByteArray) st_hdr = NULL;
72
9.78k
  g_autoptr(GInputStream) partial_stream = NULL;
73
74
  /* parse */
75
9.78k
  st_hdr = fu_struct_efi_volume_parse_stream(stream, 0x0, error);
76
9.78k
  if (st_hdr == NULL)
77
0
    return FALSE;
78
79
  /* guid */
80
9.78k
  guid_str = fwupd_guid_to_string(fu_struct_efi_volume_get_guid(st_hdr),
81
9.78k
          FWUPD_GUID_FLAG_MIXED_ENDIAN);
82
9.78k
  g_debug("volume GUID: %s [%s]", guid_str, fu_efi_guid_to_name(guid_str));
83
84
  /* length */
85
9.78k
  if (!fu_input_stream_size(stream, &streamsz, error))
86
0
    return FALSE;
87
9.78k
  fv_length = fu_struct_efi_volume_get_length(st_hdr);
88
9.78k
  if (fv_length == 0x0) {
89
30
    g_set_error_literal(error,
90
30
            FWUPD_ERROR,
91
30
            FWUPD_ERROR_INTERNAL,
92
30
            "invalid volume length");
93
30
    return FALSE;
94
30
  }
95
9.75k
  fu_firmware_set_size(firmware, fv_length);
96
9.75k
  attrs = fu_struct_efi_volume_get_attrs(st_hdr);
97
9.75k
  alignment = (attrs & 0x00ff0000) >> 16;
98
9.75k
  if (alignment > FU_FIRMWARE_ALIGNMENT_2G) {
99
491
    g_set_error(error,
100
491
          FWUPD_ERROR,
101
491
          FWUPD_ERROR_NOT_FOUND,
102
491
          "0x%x invalid, maximum is 0x%x",
103
491
          (guint)alignment,
104
491
          (guint)FU_FIRMWARE_ALIGNMENT_2G);
105
491
    return FALSE;
106
491
  }
107
9.26k
  fu_firmware_set_alignment(firmware, alignment);
108
9.26k
  priv->attrs = attrs & 0xffff;
109
9.26k
  hdr_length = fu_struct_efi_volume_get_hdr_len(st_hdr);
110
9.26k
  if (hdr_length < st_hdr->len || hdr_length > fv_length || hdr_length > streamsz ||
111
9.26k
      hdr_length % 2 != 0) {
112
1.83k
    g_set_error(error,
113
1.83k
          FWUPD_ERROR,
114
1.83k
          FWUPD_ERROR_INTERNAL,
115
1.83k
          "invalid volume header length 0x%x",
116
1.83k
          (guint)hdr_length);
117
1.83k
    return FALSE;
118
1.83k
  }
119
120
  /* verify checksum */
121
7.43k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
122
7.43k
    guint16 checksum_verify;
123
7.43k
    g_autoptr(GBytes) blob_hdr = NULL;
124
125
7.43k
    blob_hdr = fu_input_stream_read_bytes(stream, 0x0, hdr_length, NULL, error);
126
7.43k
    if (blob_hdr == NULL)
127
0
      return FALSE;
128
7.43k
    checksum_verify = fu_sum16w_bytes(blob_hdr, G_LITTLE_ENDIAN);
129
7.43k
    if (checksum_verify != 0) {
130
633
      g_set_error(error,
131
633
            FWUPD_ERROR,
132
633
            FWUPD_ERROR_INVALID_FILE,
133
633
            "checksum invalid, got %02x, expected %02x",
134
633
            checksum_verify,
135
633
            fu_struct_efi_volume_get_checksum(st_hdr));
136
633
      return FALSE;
137
633
    }
138
7.43k
  }
139
140
  /* extended header items */
141
6.79k
  if (fu_struct_efi_volume_get_ext_hdr(st_hdr) != 0) {
142
428
    g_autoptr(GByteArray) st_ext_hdr = NULL;
143
428
    goffset offset_ext = fu_struct_efi_volume_get_ext_hdr(st_hdr);
144
428
    st_ext_hdr =
145
428
        fu_struct_efi_volume_ext_header_parse_stream(stream, offset_ext, error);
146
428
    if (st_ext_hdr == NULL)
147
66
      return FALSE;
148
362
    offset_ext += fu_struct_efi_volume_ext_header_get_size(st_ext_hdr);
149
3.56k
    do {
150
3.56k
      g_autoptr(GByteArray) st_ext_entry = NULL;
151
3.56k
      st_ext_entry =
152
3.56k
          fu_struct_efi_volume_ext_entry_parse_stream(stream, offset_ext, error);
153
3.56k
      if (st_ext_entry == NULL)
154
179
        return FALSE;
155
3.38k
      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.34k
      if (fu_struct_efi_volume_ext_entry_get_size(st_ext_entry) == 0xFFFF)
163
32
        break;
164
3.31k
      offset_ext += fu_struct_efi_volume_ext_entry_get_size(st_ext_entry);
165
3.31k
    } while ((gsize)offset_ext < fv_length);
166
362
  }
167
168
  /* add image */
169
6.51k
  partial_stream =
170
6.51k
      fu_partial_input_stream_new(stream, hdr_length, fv_length - hdr_length, error);
171
6.51k
  if (partial_stream == NULL) {
172
297
    g_prefix_error(error, "failed to cut EFI volume: ");
173
297
    return FALSE;
174
297
  }
175
6.21k
  fu_firmware_set_id(firmware, guid_str);
176
6.21k
  fu_firmware_set_size(firmware, fv_length);
177
178
  /* parse, which might cascade and do something like FFS2 */
179
6.21k
  if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS2) == 0 ||
180
6.21k
      g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_FFS3) == 0) {
181
2.31k
    g_autoptr(FuFirmware) img = fu_efi_filesystem_new();
182
2.31k
    fu_firmware_set_alignment(img, fu_firmware_get_alignment(firmware));
183
2.31k
    if (!fu_firmware_parse_stream(img,
184
2.31k
                partial_stream,
185
2.31k
                0x0,
186
2.31k
                flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
187
2.31k
                error))
188
1.75k
      return FALSE;
189
556
    fu_firmware_add_image(firmware, img);
190
3.90k
  } else if (g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA) == 0 ||
191
3.90k
       g_strcmp0(guid_str, FU_EFI_VOLUME_GUID_NVRAM_EVSA2) == 0) {
192
18
    g_debug("ignoring %s [%s] EFI FV", guid_str, fu_efi_guid_to_name(guid_str));
193
18
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
194
0
      return FALSE;
195
3.88k
  } else {
196
3.88k
    g_warning("no idea how to parse %s [%s] EFI volume",
197
3.88k
        guid_str,
198
3.88k
        fu_efi_guid_to_name(guid_str));
199
3.88k
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
200
0
      return FALSE;
201
3.88k
  }
202
203
  /* skip the blockmap */
204
4.45k
  offset += st_hdr->len;
205
331k
  while (offset < streamsz) {
206
331k
    guint32 num_blocks;
207
331k
    guint32 length;
208
331k
    g_autoptr(GByteArray) st_blk = NULL;
209
331k
    st_blk = fu_struct_efi_volume_block_map_parse_stream(stream, offset, error);
210
331k
    if (st_blk == NULL)
211
127
      return FALSE;
212
331k
    num_blocks = fu_struct_efi_volume_block_map_get_num_blocks(st_blk);
213
331k
    length = fu_struct_efi_volume_block_map_get_length(st_blk);
214
331k
    offset += st_blk->len;
215
331k
    if (num_blocks == 0x0 && length == 0x0)
216
4.10k
      break;
217
326k
    blockmap_sz += (gsize)num_blocks * (gsize)length;
218
326k
  }
219
4.33k
  if (blockmap_sz < (gsize)fv_length) {
220
80
    g_set_error_literal(error,
221
80
            FWUPD_ERROR,
222
80
            FWUPD_ERROR_INTERNAL,
223
80
            "blocks allocated is less than volume length");
224
80
    return FALSE;
225
80
  }
226
227
  /* success */
228
4.25k
  return TRUE;
229
4.33k
}
230
231
static GByteArray *
232
fu_efi_volume_write(FuFirmware *firmware, GError **error)
233
2.53k
{
234
2.53k
  FuEfiVolume *self = FU_EFI_VOLUME(firmware);
235
2.53k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
236
2.53k
  g_autoptr(GByteArray) buf = fu_struct_efi_volume_new();
237
2.53k
  g_autoptr(GByteArray) st_blk = fu_struct_efi_volume_block_map_new();
238
2.53k
  fwupd_guid_t guid = {0x0};
239
2.53k
  guint32 hdr_length = 0x48;
240
2.53k
  guint64 fv_length;
241
2.53k
  g_autoptr(GBytes) img_blob = NULL;
242
2.53k
  g_autoptr(FuFirmware) img = NULL;
243
244
  /* sanity check */
245
2.53k
  if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) {
246
48
    g_set_error(error,
247
48
          FWUPD_ERROR,
248
48
          FWUPD_ERROR_INVALID_FILE,
249
48
          "alignment invalid, got 0x%02x",
250
48
          fu_firmware_get_alignment(firmware));
251
48
    return NULL;
252
48
  }
253
254
  /* GUID */
255
2.49k
  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
2.49k
  if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
260
2.49k
            &guid,
261
2.49k
            FWUPD_GUID_FLAG_MIXED_ENDIAN,
262
2.49k
            error))
263
0
    return NULL;
264
265
  /* length */
266
2.49k
  img = fu_firmware_get_image_by_id(firmware, NULL, NULL);
267
2.49k
  if (img != NULL) {
268
323
    img_blob = fu_firmware_write(img, error);
269
323
    if (img_blob == NULL) {
270
79
      g_prefix_error(error, "no EFI FV child payload: ");
271
79
      return NULL;
272
79
    }
273
2.16k
  } else {
274
2.16k
    img_blob = fu_firmware_get_bytes_with_patches(firmware, error);
275
2.16k
    if (img_blob == NULL) {
276
39
      g_prefix_error(error, "no EFI FV payload: ");
277
39
      return NULL;
278
39
    }
279
2.16k
  }
280
281
  /* pack */
282
2.37k
  fu_struct_efi_volume_set_guid(buf, &guid);
283
2.37k
  fv_length = fu_common_align_up(hdr_length + g_bytes_get_size(img_blob),
284
2.37k
               fu_firmware_get_alignment(firmware));
285
2.37k
  fu_struct_efi_volume_set_length(buf, fv_length);
286
2.37k
  fu_struct_efi_volume_set_attrs(buf,
287
2.37k
               priv->attrs |
288
2.37k
             ((guint32)fu_firmware_get_alignment(firmware) << 16));
289
2.37k
  fu_struct_efi_volume_set_hdr_len(buf, hdr_length);
290
291
  /* blockmap */
292
2.37k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, fv_length);
293
2.37k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x1);
294
2.37k
  g_byte_array_append(buf, st_blk->data, st_blk->len);
295
2.37k
  fu_struct_efi_volume_block_map_set_num_blocks(st_blk, 0x0);
296
2.37k
  fu_struct_efi_volume_block_map_set_length(st_blk, 0x0);
297
2.37k
  g_byte_array_append(buf, st_blk->data, st_blk->len);
298
299
  /* fix up checksum */
300
2.37k
  fu_struct_efi_volume_set_checksum(buf,
301
2.37k
            0x10000 -
302
2.37k
                fu_sum16w(buf->data, buf->len, G_LITTLE_ENDIAN));
303
304
  /* pad contents to alignment */
305
2.37k
  fu_byte_array_append_bytes(buf, img_blob);
306
2.37k
  fu_byte_array_set_size(buf, fv_length, 0xFF);
307
308
  /* success */
309
2.37k
  return g_steal_pointer(&buf);
310
2.49k
}
311
312
static void
313
fu_efi_volume_init(FuEfiVolume *self)
314
15.2k
{
315
15.2k
  FuEfiVolumePrivate *priv = GET_PRIVATE(self);
316
15.2k
  priv->attrs = 0xfeff;
317
15.2k
  g_type_ensure(FU_TYPE_EFI_FILESYSTEM);
318
15.2k
}
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
13.0k
{
340
13.0k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_VOLUME, NULL));
341
13.0k
}