Coverage Report

Created: 2026-06-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-efi-file.c
Line
Count
Source
1
/*
2
 * Copyright 2020 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "fu-byte-array.h"
10
#include "fu-common.h"
11
#include "fu-efi-common.h"
12
#include "fu-efi-file.h"
13
#include "fu-efi-section.h"
14
#include "fu-efi-struct.h"
15
#include "fu-input-stream.h"
16
#include "fu-partial-input-stream.h"
17
#include "fu-sum.h"
18
19
/**
20
 * FuEfiFile:
21
 *
22
 * A UEFI file.
23
 *
24
 * See also: [class@FuFirmware]
25
 */
26
27
typedef struct {
28
  guint8 type;
29
  guint8 attrib;
30
} FuEfiFilePrivate;
31
32
25.6k
G_DEFINE_TYPE_WITH_PRIVATE(FuEfiFile, fu_efi_file, FU_TYPE_FIRMWARE)
33
25.6k
#define GET_PRIVATE(o) (fu_efi_file_get_instance_private(o))
34
35
2.99k
#define FU_EFI_FILE_SIZE_MAX (16 * FU_MB)
36
37
static void
38
fu_efi_file_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
39
0
{
40
0
  FuEfiFile *self = FU_EFI_FILE(firmware);
41
0
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
42
43
0
  fu_xmlb_builder_insert_kx(bn, "attrib", priv->attrib);
44
0
  fu_xmlb_builder_insert_kx(bn, "type", priv->type);
45
0
  if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) {
46
0
    fu_xmlb_builder_insert_kv(bn,
47
0
            "name",
48
0
            fu_efi_guid_to_name(fu_firmware_get_id(firmware)));
49
0
    fu_xmlb_builder_insert_kv(bn, "type_name", fu_efi_file_type_to_string(priv->type));
50
0
  }
51
0
}
52
53
static guint8
54
fu_efi_file_hdr_checksum8(GBytes *blob)
55
2.12k
{
56
2.12k
  gsize bufsz = 0;
57
2.12k
  guint8 checksum = 0;
58
2.12k
  const guint8 *buf = g_bytes_get_data(blob, &bufsz);
59
53.1k
  for (gsize i = 0; i < bufsz; i++) {
60
51.0k
    if (i == FU_STRUCT_EFI_FILE_OFFSET_HDR_CHECKSUM)
61
2.12k
      continue;
62
48.9k
    if (i == FU_STRUCT_EFI_FILE_OFFSET_DATA_CHECKSUM)
63
2.12k
      continue;
64
46.7k
    if (i == FU_STRUCT_EFI_FILE_OFFSET_STATE)
65
2.12k
      continue;
66
44.6k
    checksum += buf[i];
67
44.6k
  }
68
2.12k
  return (guint8)(0x100u - (guint)checksum);
69
2.12k
}
70
71
static gboolean
72
fu_efi_file_parse(FuFirmware *firmware,
73
      GInputStream *stream,
74
      FuFirmwareParseFlags flags,
75
      GError **error)
76
9.25k
{
77
9.25k
  FuEfiFile *self = FU_EFI_FILE(firmware);
78
9.25k
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
79
9.25k
  guint32 size = 0x0;
80
9.25k
  g_autofree gchar *guid_str = NULL;
81
9.25k
  g_autoptr(FuStructEfiFile) st = NULL;
82
9.25k
  g_autoptr(GInputStream) partial_stream = NULL;
83
84
  /* parse */
85
9.25k
  st = fu_struct_efi_file_parse_stream(stream, 0x0, error);
86
9.25k
  if (st == NULL)
87
137
    return FALSE;
88
9.11k
  priv->type = fu_struct_efi_file_get_type(st);
89
9.11k
  priv->attrib = fu_struct_efi_file_get_attrs(st);
90
9.11k
  guid_str =
91
9.11k
      fwupd_guid_to_string(fu_struct_efi_file_get_name(st), FWUPD_GUID_FLAG_MIXED_ENDIAN);
92
9.11k
  fu_firmware_set_id(firmware, guid_str);
93
94
  /* extended size exists so size must be set to zero */
95
9.11k
  if (priv->attrib & FU_EFI_FILE_ATTRIB_LARGE_FILE) {
96
231
    if (fu_struct_efi_file_get_size(st) != 0) {
97
69
      g_set_error(error,
98
69
            FWUPD_ERROR,
99
69
            FWUPD_ERROR_INTERNAL,
100
69
            "invalid FFS size -- expected 0x0 and got 0x%x",
101
69
            (guint)fu_struct_efi_file_get_size(st));
102
69
      return FALSE;
103
69
    }
104
162
    fu_struct_efi_file_unref(st);
105
162
    st = (FuStructEfiFile *)fu_struct_efi_file2_parse_stream(stream, 0x0, error);
106
162
    if (st == NULL)
107
17
      return FALSE;
108
145
    size = fu_struct_efi_file2_get_extended_size((FuStructEfiFile2 *)st);
109
8.88k
  } else {
110
8.88k
    size = fu_struct_efi_file_get_size(st);
111
8.88k
  }
112
9.03k
  if (size < st->buf->len) {
113
13
    g_set_error(error,
114
13
          FWUPD_ERROR,
115
13
          FWUPD_ERROR_INTERNAL,
116
13
          "invalid FFS length, got 0x%x",
117
13
          (guint)size);
118
13
    return FALSE;
119
13
  }
120
121
  /* verify header checksum */
122
9.01k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
123
0
    guint8 hdr_checksum_verify;
124
0
    g_autoptr(GBytes) hdr_blob = NULL;
125
126
0
    hdr_blob = fu_input_stream_read_bytes(stream, 0x0, st->buf->len, NULL, error);
127
0
    if (hdr_blob == NULL)
128
0
      return FALSE;
129
0
    hdr_checksum_verify = fu_efi_file_hdr_checksum8(hdr_blob);
130
0
    if (hdr_checksum_verify != fu_struct_efi_file_get_hdr_checksum(st)) {
131
0
      g_set_error(error,
132
0
            FWUPD_ERROR,
133
0
            FWUPD_ERROR_INVALID_FILE,
134
0
            "checksum invalid, got %02x, expected %02x",
135
0
            hdr_checksum_verify,
136
0
            fu_struct_efi_file_get_hdr_checksum(st));
137
0
      return FALSE;
138
0
    }
139
0
  }
140
141
  /* add simple blob */
142
9.01k
  partial_stream =
143
9.01k
      fu_partial_input_stream_new(stream, st->buf->len, size - st->buf->len, error);
144
9.01k
  if (partial_stream == NULL) {
145
122
    g_prefix_error_literal(error, "failed to cut EFI blob: ");
146
122
    return FALSE;
147
122
  }
148
149
  /* verify data checksum */
150
8.89k
  if ((priv->attrib & FU_EFI_FILE_ATTRIB_CHECKSUM) > 0 &&
151
1.14k
      (flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
152
0
    guint8 data_checksum_verify = 0;
153
0
    if (!fu_input_stream_compute_sum8(partial_stream, &data_checksum_verify, error))
154
0
      return FALSE;
155
0
    if (0x100 - data_checksum_verify != fu_struct_efi_file_get_data_checksum(st)) {
156
0
      g_set_error(error,
157
0
            FWUPD_ERROR,
158
0
            FWUPD_ERROR_INVALID_FILE,
159
0
            "checksum invalid, got 0x%02x, expected 0x%02x",
160
0
            0x100u - data_checksum_verify,
161
0
            fu_struct_efi_file_get_data_checksum(st));
162
0
      return FALSE;
163
0
    }
164
0
  }
165
166
  /* add sections */
167
8.89k
  if (priv->type != FU_EFI_FILE_TYPE_FFS_PAD && priv->type != FU_EFI_FILE_TYPE_RAW) {
168
7.74k
    if (!fu_efi_parse_sections(firmware, partial_stream, 0, flags, error)) {
169
3.46k
      g_prefix_error_literal(error, "failed to add firmware image: ");
170
3.46k
      return FALSE;
171
3.46k
    }
172
7.74k
  } else {
173
1.15k
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
174
0
      return FALSE;
175
1.15k
  }
176
177
  /* align size for volume */
178
5.43k
  fu_firmware_set_size(firmware,
179
5.43k
           fu_common_align_up(size, fu_firmware_get_alignment(firmware)));
180
181
  /* success */
182
5.43k
  return TRUE;
183
8.89k
}
184
185
static GBytes *
186
fu_efi_file_write_sections(FuEfiFile *self, GError **error)
187
2.32k
{
188
2.32k
  g_autoptr(GPtrArray) images = fu_firmware_get_images(FU_FIRMWARE(self));
189
2.32k
  g_autoptr(GByteArray) buf = g_byte_array_new();
190
191
  /* sanity check */
192
2.32k
  if (fu_firmware_get_alignment(FU_FIRMWARE(self)) > FU_FIRMWARE_ALIGNMENT_1M) {
193
0
    g_set_error(error,
194
0
          FWUPD_ERROR,
195
0
          FWUPD_ERROR_INVALID_FILE,
196
0
          "alignment invalid, got 0x%02x",
197
0
          fu_firmware_get_alignment(FU_FIRMWARE(self)));
198
0
    return NULL;
199
0
  }
200
201
  /* no sections defined */
202
2.32k
  if (images->len == 0)
203
730
    return fu_firmware_get_bytes_with_patches(FU_FIRMWARE(self), error);
204
205
  /* add each section */
206
4.59k
  for (guint i = 0; i < images->len; i++) {
207
3.14k
    FuFirmware *img = g_ptr_array_index(images, i);
208
3.14k
    g_autoptr(GBytes) blob = NULL;
209
3.14k
    fu_firmware_set_offset(img, buf->len);
210
3.14k
    blob = fu_firmware_write(img, error);
211
3.14k
    if (blob == NULL)
212
141
      return NULL;
213
2.99k
    fu_byte_array_append_bytes(buf, blob);
214
2.99k
    fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_4, 0xFF);
215
216
    /* sanity check */
217
2.99k
    if (buf->len > FU_EFI_FILE_SIZE_MAX) {
218
0
      g_set_error(error,
219
0
            FWUPD_ERROR,
220
0
            FWUPD_ERROR_INVALID_FILE,
221
0
            "EFI file too large, 0x%02x > 0x%02x",
222
0
            (guint)buf->len,
223
0
            (guint)FU_EFI_FILE_SIZE_MAX);
224
0
      return NULL;
225
0
    }
226
2.99k
  }
227
228
  /* success */
229
1.45k
  return g_bytes_new(buf->data, buf->len);
230
1.59k
}
231
232
static GByteArray *
233
fu_efi_file_write(FuFirmware *firmware, GError **error)
234
2.32k
{
235
2.32k
  FuEfiFile *self = FU_EFI_FILE(firmware);
236
2.32k
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
237
2.32k
  fwupd_guid_t guid = {0x0};
238
2.32k
  g_autoptr(FuStructEfiFile) st = fu_struct_efi_file_new();
239
2.32k
  g_autoptr(GBytes) blob = NULL;
240
2.32k
  g_autoptr(GBytes) hdr_blob = NULL;
241
242
  /* simple blob for now */
243
2.32k
  blob = fu_efi_file_write_sections(self, error);
244
2.32k
  if (blob == NULL)
245
197
    return NULL;
246
2.12k
  if (fu_firmware_get_id(firmware) != NULL) {
247
2.12k
    if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
248
2.12k
              &guid,
249
2.12k
              FWUPD_GUID_FLAG_MIXED_ENDIAN,
250
2.12k
              error))
251
0
      return NULL;
252
2.12k
  }
253
2.12k
  fu_struct_efi_file_set_name(st, &guid);
254
2.12k
  fu_struct_efi_file_set_hdr_checksum(st, 0x0);
255
2.12k
  fu_struct_efi_file_set_data_checksum(st, 0x100 - fu_sum8_bytes(blob));
256
2.12k
  fu_struct_efi_file_set_type(st, priv->type);
257
2.12k
  fu_struct_efi_file_set_attrs(st, priv->attrib);
258
2.12k
  fu_struct_efi_file_set_size(st, g_bytes_get_size(blob) + st->buf->len);
259
260
  /* fix up header checksum */
261
2.12k
  hdr_blob = g_bytes_new_static(st->buf->data, st->buf->len);
262
2.12k
  fu_struct_efi_file_set_hdr_checksum(st, fu_efi_file_hdr_checksum8(hdr_blob));
263
264
  /* success */
265
2.12k
  fu_byte_array_append_bytes(st->buf, blob);
266
2.12k
  return g_steal_pointer(&st->buf);
267
2.12k
}
268
269
static gboolean
270
fu_efi_file_build(FuFirmware *firmware, XbNode *n, GError **error)
271
0
{
272
0
  FuEfiFile *self = FU_EFI_FILE(firmware);
273
0
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
274
0
  guint64 tmp;
275
276
  /* simple properties */
277
0
  tmp = xb_node_query_text_as_uint(n, "type", NULL);
278
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
279
0
    priv->type = tmp;
280
0
  tmp = xb_node_query_text_as_uint(n, "attrib", NULL);
281
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
282
0
    priv->attrib = tmp;
283
284
  /* success */
285
0
  return TRUE;
286
0
}
287
288
static void
289
fu_efi_file_init(FuEfiFile *self)
290
9.33k
{
291
9.33k
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
292
9.33k
  priv->attrib = FU_EFI_FILE_ATTRIB_NONE;
293
9.33k
  priv->type = FU_EFI_FILE_TYPE_RAW;
294
9.33k
  fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_8);
295
9.33k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 128 * FU_MB);
296
9.33k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_EFI_SECTION);
297
9.33k
}
298
299
static void
300
fu_efi_file_class_init(FuEfiFileClass *klass)
301
2
{
302
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
303
2
  firmware_class->parse = fu_efi_file_parse;
304
2
  firmware_class->write = fu_efi_file_write;
305
2
  firmware_class->build = fu_efi_file_build;
306
2
  firmware_class->export = fu_efi_file_export;
307
2
}
308
309
/**
310
 * fu_efi_file_new:
311
 *
312
 * Creates a new #FuFirmware
313
 *
314
 * Since: 2.0.0
315
 **/
316
FuFirmware *
317
fu_efi_file_new(void)
318
9.33k
{
319
9.33k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FILE, NULL));
320
9.33k
}