Coverage Report

Created: 2025-11-11 06:44

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
78.9k
G_DEFINE_TYPE_WITH_PRIVATE(FuEfiFile, fu_efi_file, FU_TYPE_FIRMWARE)
33
78.9k
#define GET_PRIVATE(o) (fu_efi_file_get_instance_private(o))
34
35
8.96k
#define FU_EFI_FILE_SIZE_MAX 0x1000000 /* 16 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
39.8k
{
56
39.8k
  gsize bufsz = 0;
57
39.8k
  guint8 checksum = 0;
58
39.8k
  const guint8 *buf = g_bytes_get_data(blob, &bufsz);
59
997k
  for (gsize i = 0; i < bufsz; i++) {
60
957k
    if (i == FU_STRUCT_EFI_FILE_OFFSET_HDR_CHECKSUM)
61
39.8k
      continue;
62
917k
    if (i == FU_STRUCT_EFI_FILE_OFFSET_DATA_CHECKSUM)
63
39.8k
      continue;
64
877k
    if (i == FU_STRUCT_EFI_FILE_OFFSET_STATE)
65
39.8k
      continue;
66
837k
    checksum += buf[i];
67
837k
  }
68
39.8k
  return (guint8)(0x100u - (guint)checksum);
69
39.8k
}
70
71
static gboolean
72
fu_efi_file_parse(FuFirmware *firmware,
73
      GInputStream *stream,
74
      FuFirmwareParseFlags flags,
75
      GError **error)
76
32.1k
{
77
32.1k
  FuEfiFile *self = FU_EFI_FILE(firmware);
78
32.1k
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
79
32.1k
  guint32 size = 0x0;
80
32.1k
  g_autofree gchar *guid_str = NULL;
81
32.1k
  g_autoptr(FuStructEfiFile) st = NULL;
82
32.1k
  g_autoptr(GInputStream) partial_stream = NULL;
83
84
  /* parse */
85
32.1k
  st = fu_struct_efi_file_parse_stream(stream, 0x0, error);
86
32.1k
  if (st == NULL)
87
96
    return FALSE;
88
32.0k
  priv->type = fu_struct_efi_file_get_type(st);
89
32.0k
  priv->attrib = fu_struct_efi_file_get_attrs(st);
90
32.0k
  guid_str =
91
32.0k
      fwupd_guid_to_string(fu_struct_efi_file_get_name(st), FWUPD_GUID_FLAG_MIXED_ENDIAN);
92
32.0k
  fu_firmware_set_id(firmware, guid_str);
93
94
  /* extended size exists so size must be set to zero */
95
32.0k
  if (priv->attrib & FU_EFI_FILE_ATTRIB_LARGE_FILE) {
96
293
    if (fu_struct_efi_file_get_size(st) != 0) {
97
104
      g_set_error(error,
98
104
            FWUPD_ERROR,
99
104
            FWUPD_ERROR_INTERNAL,
100
104
            "invalid FFS size -- expected 0x0 and got 0x%x",
101
104
            (guint)fu_struct_efi_file_get_size(st));
102
104
      return FALSE;
103
104
    }
104
189
    fu_struct_efi_file_unref(st);
105
189
    st = (FuStructEfiFile *)fu_struct_efi_file2_parse_stream(stream, 0x0, error);
106
189
    if (st == NULL)
107
6
      return FALSE;
108
183
    size = fu_struct_efi_file2_get_extended_size((FuStructEfiFile2 *)st);
109
31.7k
  } else {
110
31.7k
    size = fu_struct_efi_file_get_size(st);
111
31.7k
  }
112
31.9k
  if (size < st->buf->len) {
113
25
    g_set_error(error,
114
25
          FWUPD_ERROR,
115
25
          FWUPD_ERROR_INTERNAL,
116
25
          "invalid FFS length, got 0x%x",
117
25
          (guint)size);
118
25
    return FALSE;
119
25
  }
120
121
  /* verify header checksum */
122
31.9k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
123
31.9k
    guint8 hdr_checksum_verify;
124
31.9k
    g_autoptr(GBytes) hdr_blob = NULL;
125
126
31.9k
    hdr_blob = fu_input_stream_read_bytes(stream, 0x0, st->buf->len, NULL, error);
127
31.9k
    if (hdr_blob == NULL)
128
0
      return FALSE;
129
31.9k
    hdr_checksum_verify = fu_efi_file_hdr_checksum8(hdr_blob);
130
31.9k
    if (hdr_checksum_verify != fu_struct_efi_file_get_hdr_checksum(st)) {
131
251
      g_set_error(error,
132
251
            FWUPD_ERROR,
133
251
            FWUPD_ERROR_INVALID_FILE,
134
251
            "checksum invalid, got %02x, expected %02x",
135
251
            hdr_checksum_verify,
136
251
            fu_struct_efi_file_get_hdr_checksum(st));
137
251
      return FALSE;
138
251
    }
139
31.9k
  }
140
141
  /* add simple blob */
142
31.6k
  partial_stream =
143
31.6k
      fu_partial_input_stream_new(stream, st->buf->len, size - st->buf->len, error);
144
31.6k
  if (partial_stream == NULL) {
145
74
    g_prefix_error_literal(error, "failed to cut EFI blob: ");
146
74
    return FALSE;
147
74
  }
148
149
  /* verify data checksum */
150
31.5k
  if ((priv->attrib & FU_EFI_FILE_ATTRIB_CHECKSUM) > 0 &&
151
728
      (flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
152
728
    guint8 data_checksum_verify = 0;
153
728
    if (!fu_input_stream_compute_sum8(partial_stream, &data_checksum_verify, error))
154
0
      return FALSE;
155
728
    if (0x100 - data_checksum_verify != fu_struct_efi_file_get_data_checksum(st)) {
156
76
      g_set_error(error,
157
76
            FWUPD_ERROR,
158
76
            FWUPD_ERROR_INVALID_FILE,
159
76
            "checksum invalid, got 0x%02x, expected 0x%02x",
160
76
            0x100u - data_checksum_verify,
161
76
            fu_struct_efi_file_get_data_checksum(st));
162
76
      return FALSE;
163
76
    }
164
728
  }
165
166
  /* add sections */
167
31.5k
  if (priv->type != FU_EFI_FILE_TYPE_FFS_PAD && priv->type != FU_EFI_FILE_TYPE_RAW) {
168
19.9k
    if (!fu_efi_parse_sections(firmware, partial_stream, 0, flags, error)) {
169
3.85k
      g_prefix_error_literal(error, "failed to add firmware image: ");
170
3.85k
      return FALSE;
171
3.85k
    }
172
19.9k
  } else {
173
11.6k
    if (!fu_firmware_set_stream(firmware, partial_stream, error))
174
0
      return FALSE;
175
11.6k
  }
176
177
  /* align size for volume */
178
27.6k
  fu_firmware_set_size(firmware,
179
27.6k
           fu_common_align_up(size, fu_firmware_get_alignment(firmware)));
180
181
  /* success */
182
27.6k
  return TRUE;
183
31.5k
}
184
185
static GBytes *
186
fu_efi_file_write_sections(FuEfiFile *self, GError **error)
187
8.32k
{
188
8.32k
  g_autoptr(GPtrArray) images = fu_firmware_get_images(FU_FIRMWARE(self));
189
8.32k
  g_autoptr(GByteArray) buf = g_byte_array_new();
190
191
  /* sanity check */
192
8.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
8.32k
  if (images->len == 0)
203
3.43k
    return fu_firmware_get_bytes_with_patches(FU_FIRMWARE(self), error);
204
205
  /* add each section */
206
13.8k
  for (guint i = 0; i < images->len; i++) {
207
9.31k
    FuFirmware *img = g_ptr_array_index(images, i);
208
9.31k
    g_autoptr(GBytes) blob = NULL;
209
9.31k
    fu_firmware_set_offset(img, buf->len);
210
9.31k
    blob = fu_firmware_write(img, error);
211
9.31k
    if (blob == NULL)
212
354
      return NULL;
213
8.96k
    fu_byte_array_append_bytes(buf, blob);
214
8.96k
    fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_4, 0xFF);
215
216
    /* sanity check */
217
8.96k
    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
8.96k
  }
227
228
  /* success */
229
4.53k
  return g_bytes_new(buf->data, buf->len);
230
4.89k
}
231
232
static GByteArray *
233
fu_efi_file_write(FuFirmware *firmware, GError **error)
234
8.32k
{
235
8.32k
  FuEfiFile *self = FU_EFI_FILE(firmware);
236
8.32k
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
237
8.32k
  fwupd_guid_t guid = {0x0};
238
8.32k
  g_autoptr(FuStructEfiFile) st = fu_struct_efi_file_new();
239
8.32k
  g_autoptr(GBytes) blob = NULL;
240
8.32k
  g_autoptr(GBytes) hdr_blob = NULL;
241
242
  /* simple blob for now */
243
8.32k
  blob = fu_efi_file_write_sections(self, error);
244
8.32k
  if (blob == NULL)
245
402
    return NULL;
246
7.92k
  if (fu_firmware_get_id(firmware) != NULL) {
247
7.92k
    if (!fwupd_guid_from_string(fu_firmware_get_id(firmware),
248
7.92k
              &guid,
249
7.92k
              FWUPD_GUID_FLAG_MIXED_ENDIAN,
250
7.92k
              error))
251
0
      return NULL;
252
7.92k
  }
253
7.92k
  fu_struct_efi_file_set_name(st, &guid);
254
7.92k
  fu_struct_efi_file_set_hdr_checksum(st, 0x0);
255
7.92k
  fu_struct_efi_file_set_data_checksum(st, 0x100 - fu_sum8_bytes(blob));
256
7.92k
  fu_struct_efi_file_set_type(st, priv->type);
257
7.92k
  fu_struct_efi_file_set_attrs(st, priv->attrib);
258
7.92k
  fu_struct_efi_file_set_size(st, g_bytes_get_size(blob) + st->buf->len);
259
260
  /* fix up header checksum */
261
7.92k
  hdr_blob = g_bytes_new_static(st->buf->data, st->buf->len);
262
7.92k
  fu_struct_efi_file_set_hdr_checksum(st, fu_efi_file_hdr_checksum8(hdr_blob));
263
264
  /* success */
265
7.92k
  fu_byte_array_append_bytes(st->buf, blob);
266
7.92k
  return g_steal_pointer(&st->buf);
267
7.92k
}
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
32.7k
{
291
32.7k
  FuEfiFilePrivate *priv = GET_PRIVATE(self);
292
32.7k
  priv->attrib = FU_EFI_FILE_ATTRIB_NONE;
293
32.7k
  priv->type = FU_EFI_FILE_TYPE_RAW;
294
32.7k
  fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_8);
295
32.7k
  g_type_ensure(FU_TYPE_EFI_SECTION);
296
32.7k
}
297
298
static void
299
fu_efi_file_class_init(FuEfiFileClass *klass)
300
2
{
301
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
302
2
  firmware_class->parse = fu_efi_file_parse;
303
2
  firmware_class->write = fu_efi_file_write;
304
2
  firmware_class->build = fu_efi_file_build;
305
2
  firmware_class->export = fu_efi_file_export;
306
2
}
307
308
/**
309
 * fu_efi_file_new:
310
 *
311
 * Creates a new #FuFirmware
312
 *
313
 * Since: 2.0.0
314
 **/
315
FuFirmware *
316
fu_efi_file_new(void)
317
32.7k
{
318
32.7k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FILE, NULL));
319
32.7k
}