Coverage Report

Created: 2026-07-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-pefile-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2023 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
11.9k
#define G_LOG_DOMAIN "FuFirmware"
8
9
#include "config.h"
10
11
#include "fu-byte-array.h"
12
#include "fu-bytes.h"
13
#include "fu-common.h"
14
#include "fu-composite-input-stream.h"
15
#include "fu-coswid-firmware.h"
16
#include "fu-csv-firmware.h"
17
#include "fu-input-stream.h"
18
#include "fu-linear-firmware.h"
19
#include "fu-partial-input-stream.h"
20
#include "fu-pefile-firmware.h"
21
#include "fu-pefile-struct.h"
22
#include "fu-sbatlevel-section.h"
23
#include "fu-string.h"
24
25
/**
26
 * FuPefileFirmware:
27
 *
28
 * A PE file consists of a Microsoft MS-DOS stub, the PE signature, the COFF file header, and an
29
 * optional header, followed by section data.
30
 *
31
 * Documented:
32
 * https://learn.microsoft.com/en-gb/windows/win32/debug/pe-format
33
 */
34
35
typedef struct {
36
  gchar *authenticode_hash;
37
  guint16 subsystem_id;
38
} FuPefileFirmwarePrivate;
39
40
8.74k
G_DEFINE_TYPE_WITH_PRIVATE(FuPefileFirmware, fu_pefile_firmware, FU_TYPE_FIRMWARE)
41
8.74k
#define GET_PRIVATE(o) (fu_pefile_firmware_get_instance_private(o))
42
43
34
#define FU_PEFILE_SECTION_ID_STRTAB_SIZE 16
44
45
2.80k
#define FU_PEFILE_FIRMWARE_SYMBOLS_MAX 1000000
46
47
static void
48
fu_pefile_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
49
0
{
50
0
  FuPefileFirmware *self = FU_PEFILE_FIRMWARE(firmware);
51
0
  FuPefileFirmwarePrivate *priv = GET_PRIVATE(self);
52
0
  fu_xmlb_builder_insert_kv(bn, "authenticode_hash", priv->authenticode_hash);
53
0
  fu_xmlb_builder_insert_kv(bn, "subsystem", fu_coff_subsystem_to_string(priv->subsystem_id));
54
0
}
55
56
static gboolean
57
fu_pefile_firmware_validate(FuFirmware *firmware,
58
          FuInputStream *stream,
59
          gsize offset,
60
          GError **error)
61
2.92k
{
62
2.92k
  return fu_struct_pe_dos_header_validate_stream(stream, offset, error);
63
2.92k
}
64
65
typedef struct {
66
  gsize offset;
67
  gsize size;
68
  gchar *name;
69
} FuPefileFirmwareRegion;
70
71
static void
72
fu_pefile_firmware_add_region(GPtrArray *regions, const gchar *name, gsize offset, gsize size)
73
12.2k
{
74
12.2k
  FuPefileFirmwareRegion *r = g_new0(FuPefileFirmwareRegion, 1);
75
12.2k
  r->name = g_strdup(name);
76
12.2k
  r->offset = offset;
77
12.2k
  r->size = size;
78
12.2k
  g_ptr_array_add(regions, r);
79
12.2k
}
80
81
static void
82
fu_pefile_firmware_region_free(FuPefileFirmwareRegion *r)
83
12.2k
{
84
12.2k
  g_free(r->name);
85
12.2k
  g_free(r);
86
12.2k
}
87
88
static gint
89
fu_pefile_firmware_region_sort_cb(gconstpointer a, gconstpointer b)
90
13.0k
{
91
13.0k
  const FuPefileFirmwareRegion *r1 = *((const FuPefileFirmwareRegion **)a);
92
13.0k
  const FuPefileFirmwareRegion *r2 = *((const FuPefileFirmwareRegion **)b);
93
13.0k
  if (r1->offset < r2->offset)
94
1.87k
    return -1;
95
11.1k
  if (r1->offset > r2->offset)
96
3.46k
    return 1;
97
7.66k
  return 0;
98
11.1k
}
99
100
static gboolean
101
fu_pefile_firmware_parse_section(FuPefileFirmware *self,
102
         FuInputStream *stream,
103
         guint idx,
104
         gsize hdr_offset,
105
         gsize strtab_offset,
106
         GPtrArray *regions,
107
         FuFirmwareParseFlags flags,
108
         GError **error)
109
9.84k
{
110
9.84k
  g_autofree gchar *sect_id = NULL;
111
9.84k
  g_autofree gchar *sect_id_tmp = NULL;
112
9.84k
  g_autoptr(FuFirmware) img = NULL;
113
9.84k
  g_autoptr(FuStructPeCoffSection) st = NULL;
114
115
9.84k
  st = fu_struct_pe_coff_section_parse_stream(stream, hdr_offset, error);
116
9.84k
  if (st == NULL) {
117
81
    g_prefix_error_literal(error, "failed to read section: ");
118
81
    return FALSE;
119
81
  }
120
9.76k
  sect_id_tmp = fu_struct_pe_coff_section_get_name(st);
121
9.76k
  if (sect_id_tmp == NULL) {
122
3.21k
    sect_id = g_strdup_printf(".nul%04x", idx);
123
6.54k
  } else if (sect_id_tmp[0] == '/') {
124
809
    guint64 str_idx = 0x0;
125
809
    guint8 buf[FU_PEFILE_SECTION_ID_STRTAB_SIZE] = {0};
126
809
    gsize read_offset;
127
128
809
    if (!fu_strtoull(sect_id_tmp + 1,
129
809
         &str_idx,
130
809
         0,
131
809
         G_MAXUINT32,
132
809
         FU_INTEGER_BASE_10,
133
809
         error)) {
134
2
      g_prefix_error(error, "failed to parse section ID '%s': ", sect_id_tmp + 1);
135
2
      return FALSE;
136
2
    }
137
138
    /* calculate string table offset with overflow checking */
139
807
    read_offset = strtab_offset;
140
807
    if (!fu_size_checked_inc(&read_offset, str_idx, error)) {
141
0
      g_prefix_error(error, "string table offset overflow for section %u: ", idx);
142
0
      return FALSE;
143
0
    }
144
807
    if (!fu_input_stream_read_safe(stream,
145
807
                 buf,
146
807
                 sizeof(buf),
147
807
                 0x0,
148
807
                 read_offset, /* seek */
149
807
                 sizeof(buf),
150
807
                 error))
151
5
      return FALSE;
152
802
    sect_id = fu_strsafe((const gchar *)buf, sizeof(buf));
153
802
    if (sect_id == NULL) {
154
2
      g_set_error_literal(error,
155
2
              FWUPD_ERROR,
156
2
              FWUPD_ERROR_INVALID_DATA,
157
2
              "no section name");
158
2
      return FALSE;
159
2
    }
160
5.73k
  } else {
161
5.73k
    sect_id = g_steal_pointer(&sect_id_tmp);
162
5.73k
  }
163
164
  /* create new firmware */
165
9.75k
  if (g_strcmp0(sect_id, ".sbom") == 0) {
166
1.46k
    img = fu_linear_firmware_new(FU_TYPE_COSWID_FIRMWARE);
167
8.28k
  } else if (g_strcmp0(sect_id, ".sbat") == 0 || g_strcmp0(sect_id, ".sbata") == 0 ||
168
5.49k
       g_strcmp0(sect_id, ".sbatl") == 0) {
169
2.95k
    img = fu_csv_firmware_new();
170
2.95k
    fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "$id");
171
2.95k
    fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "$version_raw");
172
2.95k
    fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "vendor_name");
173
2.95k
    fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "vendor_package_name");
174
2.95k
    fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "$version");
175
2.95k
    fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "vendor_url");
176
2.95k
    fu_csv_firmware_set_write_column_ids(FU_CSV_FIRMWARE(img), FALSE);
177
5.33k
  } else if (g_strcmp0(sect_id, ".sbatlevel") == 0) {
178
190
    img = fu_sbatlevel_section_new();
179
5.14k
  } else {
180
5.14k
    img = fu_firmware_new();
181
5.14k
  }
182
9.75k
  fu_firmware_set_id(img, sect_id);
183
9.75k
  fu_firmware_set_idx(img, idx);
184
185
  /* add data */
186
9.75k
  if (fu_struct_pe_coff_section_get_virtual_size(st) > 0) {
187
8.10k
    guint32 sect_offset = fu_struct_pe_coff_section_get_pointer_to_raw_data(st);
188
8.10k
    guint32 sect_size = fu_struct_pe_coff_section_get_virtual_size(st);
189
8.10k
    g_autoptr(FuInputStream) img_stream = NULL;
190
191
    /* use the raw data size if the section is compressed */
192
8.10k
    if (fu_struct_pe_coff_section_get_virtual_size(st) >
193
8.10k
        fu_struct_pe_coff_section_get_size_of_raw_data(st)) {
194
7.30k
      g_debug("virtual size 0x%x bigger than raw data, truncating to 0x%x",
195
7.30k
        sect_size,
196
7.30k
        fu_struct_pe_coff_section_get_size_of_raw_data(st));
197
7.30k
      sect_size = fu_struct_pe_coff_section_get_size_of_raw_data(st);
198
7.30k
    }
199
200
8.10k
    fu_firmware_set_offset(img, sect_offset);
201
8.10k
    img_stream = fu_partial_input_stream_new(stream, sect_offset, sect_size, error);
202
8.10k
    if (img_stream == NULL) {
203
522
      g_prefix_error_literal(error, "failed to cut raw PE data: ");
204
522
      return FALSE;
205
522
    }
206
7.57k
    if (!fu_firmware_parse_stream(img, img_stream, 0x0, flags, error)) {
207
1.57k
      g_prefix_error(error, "failed to parse raw data %s: ", sect_id);
208
1.57k
      return FALSE;
209
1.57k
    }
210
211
    /* add region for Authenticode checksum */
212
6.00k
    fu_pefile_firmware_add_region(regions,
213
6.00k
                sect_id,
214
6.00k
                sect_offset,
215
6.00k
                fu_struct_pe_coff_section_get_size_of_raw_data(st));
216
6.00k
  }
217
218
  /* success */
219
7.66k
  return fu_firmware_add_image(FU_FIRMWARE(self), img, error);
220
9.75k
}
221
222
static gboolean
223
fu_pefile_firmware_parse(FuFirmware *firmware,
224
       FuInputStream *stream,
225
       FuFirmwareParseFlags flags,
226
       GError **error)
227
2.89k
{
228
2.89k
  FuPefileFirmware *self = FU_PEFILE_FIRMWARE(firmware);
229
2.89k
  FuPefileFirmwarePrivate *priv = GET_PRIVATE(self);
230
2.89k
  guint32 cert_table_sz = 0;
231
2.89k
  gsize offset = 0;
232
2.89k
  gsize region_end;
233
2.89k
  gsize streamsz = 0;
234
2.89k
  gsize strtab_offset;
235
2.89k
  gsize subsystem_offset;
236
2.89k
  gsize symbols_sz_safe = 0;
237
2.89k
  guint32 nr_sections;
238
2.89k
  guint32 nr_symbols;
239
2.89k
  guint64 symbols_sz;
240
2.89k
  g_autoptr(FuStructPeCoffFileHeader) st_coff = NULL;
241
2.89k
  g_autoptr(FuStructPeDosHeader) st_doshdr = NULL;
242
2.89k
  g_autoptr(GPtrArray) regions = NULL;
243
2.89k
  g_autoptr(FuInputStream) composite_stream = fu_composite_input_stream_new();
244
245
  /* get size */
246
2.89k
  if (!fu_input_stream_size(stream, &streamsz, error))
247
0
    return FALSE;
248
249
  /* parse the DOS header to get the COFF header */
250
2.89k
  st_doshdr = fu_struct_pe_dos_header_parse_stream(stream, offset, error);
251
2.89k
  if (st_doshdr == NULL) {
252
0
    g_prefix_error_literal(error, "failed to read DOS header: ");
253
0
    return FALSE;
254
0
  }
255
2.89k
  if (!fu_size_checked_inc(&offset, fu_struct_pe_dos_header_get_lfanew(st_doshdr), error))
256
0
    return FALSE;
257
2.89k
  st_coff = fu_struct_pe_coff_file_header_parse_stream(stream, offset, error);
258
2.89k
  if (st_coff == NULL) {
259
72
    g_prefix_error_literal(error, "failed to read COFF header: ");
260
72
    return FALSE;
261
72
  }
262
2.82k
  if (!fu_size_checked_inc(&offset, st_coff->buf->len, error)) {
263
0
    g_prefix_error_literal(error, "header offset overflow: ");
264
0
    return FALSE;
265
0
  }
266
267
2.82k
  regions = g_ptr_array_new_with_free_func((GDestroyNotify)fu_pefile_firmware_region_free);
268
269
  /* 1st Authenticode region */
270
2.82k
  region_end = offset;
271
2.82k
  if (!fu_size_checked_inc(&region_end,
272
2.82k
         FU_STRUCT_PE_COFF_OPTIONAL_HEADER64_OFFSET_CHECKSUM,
273
2.82k
         error))
274
0
    return FALSE;
275
2.82k
  fu_pefile_firmware_add_region(regions, "pre-cksum", 0x0, region_end);
276
277
  /* 2nd Authenticode region */
278
2.82k
  subsystem_offset = offset;
279
2.82k
  if (!fu_size_checked_inc(&subsystem_offset,
280
2.82k
         FU_STRUCT_PE_COFF_OPTIONAL_HEADER64_OFFSET_SUBSYSTEM,
281
2.82k
         error))
282
0
    return FALSE;
283
2.82k
  if (!fu_input_stream_read_safe(stream,
284
2.82k
               (guint8 *)&priv->subsystem_id,
285
2.82k
               sizeof(priv->subsystem_id),
286
2.82k
               0x0,
287
2.82k
               subsystem_offset, /* seek */
288
2.82k
               sizeof(priv->subsystem_id),
289
2.82k
               error))
290
1
    return FALSE;
291
2.82k
  fu_pefile_firmware_add_region(
292
2.82k
      regions,
293
2.82k
      "chksum->cert-table",
294
2.82k
      subsystem_offset,
295
2.82k
      FU_STRUCT_PE_COFF_OPTIONAL_HEADER64_OFFSET_CERTIFICATE_TABLE -
296
2.82k
    FU_STRUCT_PE_COFF_OPTIONAL_HEADER64_OFFSET_SUBSYSTEM); /* end */
297
298
  /* verify optional extra header */
299
2.82k
  if (fu_struct_pe_coff_file_header_get_size_of_optional_header(st_coff) > 0) {
300
265
    g_autoptr(FuStructPeCoffOptionalHeader64) st_opt =
301
265
        fu_struct_pe_coff_optional_header64_parse_stream(stream, offset, error);
302
265
    if (st_opt == NULL) {
303
12
      g_prefix_error_literal(error, "failed to read optional header: ");
304
12
      return FALSE;
305
12
    }
306
307
    /* 3rd Authenticode region */
308
253
    if (fu_struct_pe_coff_optional_header64_get_size_of_headers(st_opt) > 0) {
309
162
      gsize debug_table_offset = offset;
310
162
      gsize size_of_headers =
311
162
          fu_struct_pe_coff_optional_header64_get_size_of_headers(st_opt);
312
313
162
      if (!fu_size_checked_inc(
314
162
        &debug_table_offset,
315
162
        FU_STRUCT_PE_COFF_OPTIONAL_HEADER64_OFFSET_DEBUG_TABLE,
316
162
        error))
317
0
        return FALSE;
318
319
162
      if (size_of_headers < debug_table_offset) {
320
8
        g_set_error(error,
321
8
              FWUPD_ERROR,
322
8
              FWUPD_ERROR_INVALID_DATA,
323
8
              "header size too small: 0x%x < 0x%x",
324
8
              (guint)size_of_headers,
325
8
              (guint)debug_table_offset);
326
8
        return FALSE;
327
8
      }
328
329
154
      fu_pefile_firmware_add_region(regions,
330
154
                  "cert-table->end-of-headers",
331
154
                  debug_table_offset,
332
154
                  size_of_headers - debug_table_offset);
333
154
    }
334
335
    /* 4th Authenticode region */
336
245
    cert_table_sz =
337
245
        fu_struct_pe_coff_optional_header64_get_size_of_certificate_table(st_opt);
338
339
245
    if (!fu_size_checked_inc(
340
245
      &offset,
341
245
      fu_struct_pe_coff_file_header_get_size_of_optional_header(st_coff),
342
245
      error))
343
0
      return FALSE;
344
245
  }
345
346
  /* read number of sections */
347
2.80k
  nr_sections = fu_struct_pe_coff_file_header_get_number_of_sections(st_coff);
348
2.80k
  if (nr_sections == 0) {
349
1
    g_set_error_literal(error,
350
1
            FWUPD_ERROR,
351
1
            FWUPD_ERROR_INVALID_FILE,
352
1
            "invalid number of sections");
353
1
    return FALSE;
354
1
  }
355
2.80k
  strtab_offset = fu_struct_pe_coff_file_header_get_pointer_to_symbol_table(st_coff);
356
357
2.80k
  nr_symbols = fu_struct_pe_coff_file_header_get_number_of_symbols(st_coff);
358
2.80k
  if (nr_symbols > FU_PEFILE_FIRMWARE_SYMBOLS_MAX) {
359
24
    g_set_error(error,
360
24
          FWUPD_ERROR,
361
24
          FWUPD_ERROR_INVALID_DATA,
362
24
          "excessive number of symbols: %u",
363
24
          nr_symbols);
364
24
    return FALSE;
365
24
  }
366
2.78k
  symbols_sz = (guint64)nr_symbols * FU_STRUCT_PE_COFF_SYMBOL_SIZE;
367
2.78k
  if (!fu_size_from_uint64(symbols_sz, &symbols_sz_safe, error))
368
0
    return FALSE;
369
2.78k
  if (!fu_size_checked_inc(&strtab_offset, symbols_sz_safe, error))
370
0
    return FALSE;
371
372
  /* read out each section */
373
10.4k
  for (guint idx = 0; idx < nr_sections; idx++) {
374
9.84k
    if (!fu_pefile_firmware_parse_section(self,
375
9.84k
                  stream,
376
9.84k
                  idx,
377
9.84k
                  offset,
378
9.84k
                  strtab_offset,
379
9.84k
                  regions,
380
9.84k
                  flags,
381
9.84k
                  error)) {
382
2.21k
      g_prefix_error(error, "failed to read section 0x%x: ", idx);
383
2.21k
      return FALSE;
384
2.21k
    }
385
7.63k
    if (!fu_size_checked_inc(&offset, FU_STRUCT_PE_COFF_SECTION_SIZE, error)) {
386
0
      g_prefix_error(error, "section %u offset overflow: ", idx);
387
0
      return FALSE;
388
0
    }
389
7.63k
  }
390
391
  /* make sure ordered by address */
392
567
  g_ptr_array_sort(regions, fu_pefile_firmware_region_sort_cb);
393
394
  /* for the data at the end of the image */
395
567
  if (regions->len > 0) {
396
567
    FuPefileFirmwareRegion *r = g_ptr_array_index(regions, regions->len - 1);
397
567
    gsize offset_end = r->offset;
398
567
    gsize end_with_cert;
399
400
    /* check for overflow when calculating region end */
401
567
    if (!fu_size_checked_inc(&offset_end, r->size, error))
402
0
      return FALSE;
403
404
    /* sanity check */
405
567
    if (offset_end > streamsz) {
406
163
      g_set_error(error,
407
163
            FWUPD_ERROR,
408
163
            FWUPD_ERROR_INVALID_DATA,
409
163
            "section extends beyond file boundary: 0x%x > 0x%x",
410
163
            (guint)offset_end,
411
163
            (guint)streamsz);
412
163
      return FALSE;
413
163
    }
414
415
    /* check for overflow when adding certificate table size */
416
404
    end_with_cert = offset_end;
417
404
    if (!fu_size_checked_inc(&end_with_cert, cert_table_sz, error))
418
0
      return FALSE;
419
420
404
    if (end_with_cert > streamsz) {
421
1
      g_set_error(error,
422
1
            FWUPD_ERROR,
423
1
            FWUPD_ERROR_INVALID_DATA,
424
1
            "certificate table extends beyond file: 0x%x + 0x%x > 0x%x",
425
1
            (guint)offset_end,
426
1
            cert_table_sz,
427
1
            (guint)streamsz);
428
1
      return FALSE;
429
1
    }
430
403
    fu_pefile_firmware_add_region(regions,
431
403
                "tabledata->cert-table",
432
403
                offset_end,
433
403
                streamsz - end_with_cert);
434
403
  }
435
436
  /* calculate the checksum we would find in the dbx */
437
4.87k
  for (guint i = 0; i < regions->len; i++) {
438
4.53k
    FuPefileFirmwareRegion *r = g_ptr_array_index(regions, i);
439
4.53k
    g_autoptr(FuInputStream) partial_stream = NULL;
440
441
4.53k
    if (r->size == 0)
442
58
      continue;
443
4.48k
    g_debug("authenticode region %s: 0x%04x -> 0x%04x [0x%04x]",
444
4.48k
      r->name,
445
4.48k
      (guint)r->offset,
446
4.48k
      (guint)(r->offset + r->size),
447
4.48k
      (guint)r->size);
448
4.48k
    partial_stream = fu_partial_input_stream_new(stream, r->offset, r->size, error);
449
4.48k
    if (partial_stream == NULL) {
450
70
      g_prefix_error_literal(error, "failed to cut Authenticode region: ");
451
70
      return FALSE;
452
70
    }
453
4.41k
    if (!fu_composite_input_stream_add_partial_stream(
454
4.41k
      FU_COMPOSITE_INPUT_STREAM(composite_stream),
455
4.41k
      FU_PARTIAL_INPUT_STREAM(partial_stream),
456
4.41k
      error))
457
0
      return FALSE;
458
4.41k
  }
459
333
  priv->authenticode_hash =
460
333
      fu_input_stream_compute_checksum(composite_stream, G_CHECKSUM_SHA256, error);
461
333
  if (priv->authenticode_hash == NULL)
462
0
    return FALSE;
463
464
  /* success */
465
333
  return TRUE;
466
333
}
467
468
typedef struct {
469
  GBytes *blob;
470
  gchar *id;
471
  gsize offset;
472
  gsize blobsz_aligned;
473
} FuPefileSection;
474
475
static void
476
fu_pefile_firmware_section_free(FuPefileSection *section)
477
699
{
478
699
  if (section->blob != NULL)
479
591
    g_bytes_unref(section->blob);
480
699
  g_free(section->id);
481
699
  g_free(section);
482
699
}
483
484
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuPefileSection, fu_pefile_firmware_section_free)
485
486
static GByteArray *
487
fu_pefile_firmware_write(FuFirmware *firmware, GError **error)
488
333
{
489
333
  gsize offset = 0;
490
333
  g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware);
491
333
  g_autoptr(FuStructPeDosHeader) st = fu_struct_pe_dos_header_new();
492
333
  g_autoptr(FuStructPeCoffFileHeader) st_hdr = fu_struct_pe_coff_file_header_new();
493
333
  g_autoptr(FuStructPeCoffOptionalHeader64) st_opt =
494
333
      fu_struct_pe_coff_optional_header64_new();
495
333
  g_autoptr(GByteArray) strtab = g_byte_array_new();
496
333
  g_autoptr(GPtrArray) sections =
497
333
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_pefile_firmware_section_free);
498
499
  /* calculate the offset for each of the sections */
500
333
  if (!fu_size_checked_inc(&offset, st->buf->len, error))
501
0
    return NULL;
502
333
  if (!fu_size_checked_inc(&offset, st_hdr->buf->len, error))
503
0
    return NULL;
504
333
  if (!fu_size_checked_inc(&offset, st_opt->buf->len, error))
505
0
    return NULL;
506
333
  if (!fu_size_checked_inc_product(&offset, FU_STRUCT_PE_COFF_SECTION_SIZE, imgs->len, error))
507
0
    return NULL;
508
924
  for (guint i = 0; i < imgs->len; i++) {
509
699
    g_autoptr(FuPefileSection) section = g_new0(FuPefileSection, 1);
510
699
    FuFirmware *img = g_ptr_array_index(imgs, i);
511
512
699
    section->offset = offset;
513
699
    section->blob = fu_firmware_write(img, error);
514
699
    if (section->blob == NULL)
515
108
      return NULL;
516
591
    if (g_bytes_get_size(section->blob) == 0) {
517
166
      g_debug("skipping zero length section %u", i);
518
166
      continue;
519
166
    }
520
425
    section->id = g_strdup(fu_firmware_get_id(img));
521
425
    section->blobsz_aligned = fu_common_align_up(g_bytes_get_size(section->blob), 4);
522
425
    if (!fu_size_checked_inc(&offset, section->blobsz_aligned, error))
523
0
      return NULL;
524
425
    g_ptr_array_add(sections, g_steal_pointer(&section));
525
425
  }
526
527
  /* export_table -> architecture_table */
528
225
  fu_struct_pe_coff_optional_header64_set_number_of_rva_and_sizes(st_opt, 7);
529
530
  /* COFF file header */
531
225
  fu_struct_pe_coff_file_header_set_size_of_optional_header(st_hdr, st_opt->buf->len);
532
225
  fu_struct_pe_coff_file_header_set_number_of_sections(st_hdr, sections->len);
533
225
  fu_struct_pe_coff_file_header_set_pointer_to_symbol_table(st_hdr, offset);
534
225
  fu_byte_array_append_array(st->buf, st_hdr->buf);
535
225
  fu_byte_array_append_array(st->buf, st_opt->buf);
536
537
  /* add sections */
538
623
  for (guint i = 0; i < sections->len; i++) {
539
398
    FuPefileSection *section = g_ptr_array_index(sections, i);
540
398
    g_autoptr(FuStructPeCoffSection) st_sect = fu_struct_pe_coff_section_new();
541
542
398
    fu_struct_pe_coff_section_set_size_of_raw_data(st_sect,
543
398
                     g_bytes_get_size(section->blob));
544
398
    fu_struct_pe_coff_section_set_virtual_address(st_sect, 0x0);
545
398
    fu_struct_pe_coff_section_set_virtual_size(st_sect, section->blobsz_aligned);
546
398
    fu_struct_pe_coff_section_set_pointer_to_raw_data(st_sect, section->offset);
547
548
    /* set the name directly, or add to the string table */
549
398
    if (section->id == NULL) {
550
0
      g_set_error(error,
551
0
            FWUPD_ERROR,
552
0
            FWUPD_ERROR_INVALID_DATA,
553
0
            "image %u has no ID",
554
0
            i);
555
0
      return NULL;
556
0
    }
557
398
    if (strlen(section->id) <= 8) {
558
381
      if (!fu_struct_pe_coff_section_set_name(st_sect, section->id, error))
559
0
        return NULL;
560
381
    } else {
561
17
      g_autofree gchar *name_tmp = g_strdup_printf("/%u", strtab->len);
562
17
      g_autoptr(GByteArray) strtab_buf = g_byte_array_new();
563
564
17
      if (!fu_struct_pe_coff_section_set_name(st_sect, name_tmp, error))
565
0
        return NULL;
566
567
      /* create a byte buffer of exactly the correct chunk size */
568
17
      g_byte_array_append(strtab_buf,
569
17
              (const guint8 *)section->id,
570
17
              strlen(section->id));
571
17
      if (strtab_buf->len > FU_PEFILE_SECTION_ID_STRTAB_SIZE) {
572
0
        g_set_error(error,
573
0
              FWUPD_ERROR,
574
0
              FWUPD_ERROR_INVALID_DATA,
575
0
              "image ID %s is too long",
576
0
              section->id);
577
0
        return NULL;
578
0
      }
579
17
      fu_byte_array_set_size(strtab_buf, FU_PEFILE_SECTION_ID_STRTAB_SIZE, 0x0);
580
17
      g_byte_array_append(strtab, strtab_buf->data, strtab_buf->len);
581
17
    }
582
398
    fu_byte_array_append_array(st->buf, st_sect->buf);
583
398
  }
584
585
  /* add the section data itself */
586
623
  for (guint i = 0; i < sections->len; i++) {
587
398
    FuPefileSection *section = g_ptr_array_index(sections, i);
588
398
    g_autoptr(GBytes) blob_aligned =
589
398
        fu_bytes_pad(section->blob, section->blobsz_aligned, 0xFF);
590
398
    fu_byte_array_append_bytes(st->buf, blob_aligned);
591
398
  }
592
593
  /* string table comes last */
594
225
  g_byte_array_append(st->buf, strtab->data, strtab->len);
595
596
  /* success */
597
225
  return g_steal_pointer(&st->buf);
598
225
}
599
600
static gchar *
601
fu_pefile_firmware_get_checksum(FuFirmware *firmware, GChecksumType csum_kind, GError **error)
602
0
{
603
0
  FuPefileFirmware *self = FU_PEFILE_FIRMWARE(firmware);
604
0
  FuPefileFirmwarePrivate *priv = GET_PRIVATE(self);
605
0
  if (csum_kind != G_CHECKSUM_SHA256) {
606
0
    g_set_error_literal(error,
607
0
            FWUPD_ERROR,
608
0
            FWUPD_ERROR_NOT_SUPPORTED,
609
0
            "Authenticode only supports SHA256");
610
0
    return NULL;
611
0
  }
612
0
  if (priv->authenticode_hash == NULL) {
613
0
    g_set_error_literal(error,
614
0
            FWUPD_ERROR,
615
0
            FWUPD_ERROR_INVALID_DATA,
616
0
            "Authenticode checksum not set");
617
0
    return NULL;
618
0
  }
619
0
  return g_strdup(priv->authenticode_hash);
620
0
}
621
622
static void
623
fu_pefile_firmware_init(FuPefileFirmware *self)
624
2.92k
{
625
2.92k
}
626
627
static void
628
fu_pefile_firmware_finalize(GObject *object)
629
2.92k
{
630
2.92k
  FuPefileFirmware *self = FU_PEFILE_FIRMWARE(object);
631
2.92k
  FuPefileFirmwarePrivate *priv = GET_PRIVATE(self);
632
2.92k
  g_free(priv->authenticode_hash);
633
2.92k
  G_OBJECT_CLASS(fu_pefile_firmware_parent_class)->finalize(object);
634
2.92k
}
635
636
static void
637
fu_pefile_firmware_class_init(FuPefileFirmwareClass *klass)
638
1
{
639
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
640
1
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
641
1
  object_class->finalize = fu_pefile_firmware_finalize;
642
1
  firmware_class->validate = fu_pefile_firmware_validate;
643
1
  firmware_class->parse = fu_pefile_firmware_parse;
644
1
  firmware_class->write = fu_pefile_firmware_write;
645
1
  firmware_class->export = fu_pefile_firmware_export;
646
1
  firmware_class->get_checksum = fu_pefile_firmware_get_checksum;
647
1
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_FIRMWARE);
648
1
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_CSV_FIRMWARE);
649
1
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_SBATLEVEL_SECTION);
650
1
  fu_firmware_set_images_max(firmware_class, 100);
651
1
  fu_firmware_set_size_max(firmware_class, 256 * FU_MB);
652
1
}
653
654
/**
655
 * fu_pefile_firmware_new:
656
 *
657
 * Creates a new #FuPefileFirmware
658
 *
659
 * Since: 1.8.10
660
 **/
661
FuFirmware *
662
fu_pefile_firmware_new(void)
663
0
{
664
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_PEFILE_FIRMWARE, NULL));
665
0
}