Coverage Report

Created: 2026-01-17 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-elf-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
0
#define G_LOG_DOMAIN "FuFirmware"
8
9
#include "config.h"
10
11
#include "fu-byte-array.h"
12
#include "fu-elf-firmware.h"
13
#include "fu-elf-struct.h"
14
#include "fu-input-stream.h"
15
#include "fu-partial-input-stream.h"
16
#include "fu-string.h"
17
18
/**
19
 * FuElfFirmware:
20
 *
21
 * Executable and Linkable Format is a common standard file format for executable files,
22
 * object code, shared libraries, core dumps -- and sometimes firmware.
23
 *
24
 * Documented:
25
 * https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
26
 */
27
28
1.19k
G_DEFINE_TYPE(FuElfFirmware, fu_elf_firmware, FU_TYPE_FIRMWARE)
29
1.19k
30
1.19k
static gboolean
31
1.19k
fu_elf_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
32
132k
{
33
132k
  return fu_struct_elf_file_header64le_validate_stream(stream, offset, error);
34
132k
}
35
36
static gboolean
37
fu_elf_firmware_parse(FuFirmware *firmware,
38
          GInputStream *stream,
39
          FuFirmwareParseFlags flags,
40
          GError **error)
41
993
{
42
993
  gsize offset_secthdr = 0;
43
993
  gsize offset_proghdr = 0;
44
993
  guint16 phentsize;
45
993
  guint16 phnum;
46
993
  guint16 shnum;
47
993
  g_autoptr(FuStructElfFileHeader64le) st_fhdr = NULL;
48
993
  g_autoptr(GByteArray) shstrndx_buf = NULL;
49
993
  g_autoptr(GPtrArray) sections =
50
993
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_struct_elf_section_header64le_unref);
51
52
  /* file header */
53
993
  st_fhdr = fu_struct_elf_file_header64le_parse_stream(stream, 0x0, error);
54
993
  if (st_fhdr == NULL)
55
0
    return FALSE;
56
57
  /* parse each program header, unused here */
58
993
  offset_proghdr += fu_struct_elf_file_header64le_get_phoff(st_fhdr);
59
993
  phentsize = fu_struct_elf_file_header64le_get_phentsize(st_fhdr);
60
993
  phnum = fu_struct_elf_file_header64le_get_phnum(st_fhdr);
61
3.51M
  for (guint i = 0; i < phnum; i++) {
62
3.51M
    g_autoptr(FuStructElfProgramHeader64le) st_phdr =
63
3.51M
        fu_struct_elf_program_header64le_parse_stream(stream, offset_proghdr, error);
64
3.51M
    if (st_phdr == NULL)
65
139
      return FALSE;
66
3.51M
    offset_proghdr += phentsize;
67
3.51M
  }
68
69
  /* parse all the sections ahead of time */
70
854
  offset_secthdr += fu_struct_elf_file_header64le_get_shoff(st_fhdr);
71
854
  shnum = fu_struct_elf_file_header64le_get_shnum(st_fhdr);
72
12.1M
  for (guint i = 0; i < shnum; i++) {
73
12.1M
    g_autoptr(FuStructElfSectionHeader64le) st_shdr =
74
12.1M
        fu_struct_elf_section_header64le_parse_stream(stream, offset_secthdr, error);
75
12.1M
    if (st_shdr == NULL)
76
88
      return FALSE;
77
12.1M
    g_ptr_array_add(sections, g_steal_pointer(&st_shdr));
78
12.1M
    offset_secthdr += fu_struct_elf_file_header64le_get_shentsize(st_fhdr);
79
12.1M
  }
80
81
  /* add sections as images */
82
5.58M
  for (guint i = 0; i < sections->len; i++) {
83
5.58M
    FuStructElfSectionHeader64le *st_shdr = g_ptr_array_index(sections, i);
84
5.58M
    guint64 sect_offset = fu_struct_elf_section_header64le_get_offset(st_shdr);
85
5.58M
    guint64 sect_size = fu_struct_elf_section_header64le_get_size(st_shdr);
86
5.58M
    g_autoptr(FuFirmware) img = fu_firmware_new();
87
88
    /* catch the strtab */
89
5.58M
    if (i == fu_struct_elf_file_header64le_get_shstrndx(st_fhdr)) {
90
458
      if (fu_struct_elf_section_header64le_get_type(st_shdr) !=
91
458
          FU_ELF_SECTION_HEADER_TYPE_STRTAB) {
92
100
        g_set_error(
93
100
            error,
94
100
            FWUPD_ERROR,
95
100
            FWUPD_ERROR_INVALID_DATA,
96
100
            "shstrndx section type was not strtab, was %s",
97
100
            fu_elf_section_header_type_to_string(
98
100
          fu_struct_elf_section_header64le_get_type(st_shdr)));
99
100
        return FALSE;
100
100
      }
101
358
      shstrndx_buf = fu_input_stream_read_byte_array(stream,
102
358
                       sect_offset,
103
358
                       sect_size,
104
358
                       NULL,
105
358
                       error);
106
358
      if (shstrndx_buf == NULL)
107
21
        return FALSE;
108
337
      continue;
109
358
    }
110
111
5.58M
    if (fu_struct_elf_section_header64le_get_type(st_shdr) ==
112
5.58M
      FU_ELF_SECTION_HEADER_TYPE_NULL ||
113
5.46M
        fu_struct_elf_section_header64le_get_type(st_shdr) ==
114
5.46M
      FU_ELF_SECTION_HEADER_TYPE_STRTAB)
115
5.56M
      continue;
116
19.8k
    if (sect_size > 0) {
117
5.94k
      g_autoptr(GInputStream) img_stream =
118
5.94k
          fu_partial_input_stream_new(stream, sect_offset, sect_size, error);
119
5.94k
      if (img_stream == NULL) {
120
310
        g_prefix_error_literal(error, "failed to cut EFI image: ");
121
310
        return FALSE;
122
310
      }
123
5.63k
      if (!fu_firmware_parse_stream(img, img_stream, 0x0, flags, error))
124
1
        return FALSE;
125
5.63k
    }
126
19.5k
    fu_firmware_set_idx(img, i);
127
19.5k
    if (!fu_firmware_add_image(firmware, img, error))
128
3
      return FALSE;
129
19.5k
  }
130
131
  /* no shstrndx found */
132
331
  if (shstrndx_buf == NULL) {
133
24
    g_set_error_literal(error,
134
24
            FWUPD_ERROR,
135
24
            FWUPD_ERROR_INVALID_DATA,
136
24
            "shstrndx was invalid");
137
24
    return FALSE;
138
24
  }
139
140
  /* fix up the section names */
141
5.31M
  for (guint i = 0; i < sections->len; i++) {
142
5.31M
    FuStructElfSectionHeader64le *st_shdr = g_ptr_array_index(sections, i);
143
5.31M
    guint32 sh_name = fu_struct_elf_section_header64le_get_name(st_shdr);
144
5.31M
    g_autofree gchar *name = NULL;
145
5.31M
    g_autoptr(FuFirmware) img = NULL;
146
147
5.31M
    if (fu_struct_elf_section_header64le_get_type(st_shdr) ==
148
5.31M
      FU_ELF_SECTION_HEADER_TYPE_NULL ||
149
5.31M
        fu_struct_elf_section_header64le_get_type(st_shdr) ==
150
5.31M
      FU_ELF_SECTION_HEADER_TYPE_STRTAB)
151
5.31M
      continue;
152
1.85k
    if (sh_name > shstrndx_buf->len) {
153
72
      g_set_error(error,
154
72
            FWUPD_ERROR,
155
72
            FWUPD_ERROR_INVALID_DATA,
156
72
            "offset into shstrndx invalid for section 0x%x",
157
72
            i);
158
72
      return FALSE;
159
72
    }
160
1.78k
    img = fu_firmware_get_image_by_idx(firmware, i, error);
161
1.78k
    if (img == NULL)
162
0
      return FALSE;
163
1.78k
    name = g_strndup((const gchar *)shstrndx_buf->data + sh_name,
164
1.78k
         shstrndx_buf->len - sh_name);
165
1.78k
    if (name != NULL && name[0] != '\0')
166
1.51k
      fu_firmware_set_id(img, name);
167
1.78k
  }
168
169
  /* success */
170
235
  return TRUE;
171
307
}
172
173
typedef struct {
174
  gchar *name;
175
  gsize namesz;
176
  gsize offset;
177
} FuElfFirmwareStrtabEntry;
178
179
static void
180
fu_elf_firmware_strtab_entry_free(FuElfFirmwareStrtabEntry *entry)
181
1.69k
{
182
1.69k
  g_free(entry->name);
183
1.69k
  g_free(entry);
184
1.69k
}
185
186
static void
187
fu_elf_firmware_strtab_insert(GPtrArray *strtab, const gchar *name)
188
1.69k
{
189
1.69k
  FuElfFirmwareStrtabEntry *entry = g_new0(FuElfFirmwareStrtabEntry, 1);
190
1.69k
  gsize offset = 0;
191
192
1.69k
  g_return_if_fail(name != NULL);
193
194
  /* get the previous entry */
195
1.69k
  if (strtab->len > 0) {
196
1.46k
    FuElfFirmwareStrtabEntry *entry_old = g_ptr_array_index(strtab, strtab->len - 1);
197
1.46k
    offset += entry_old->offset + entry_old->namesz;
198
1.46k
  }
199
1.69k
  entry->namesz = strlen(name) + 1; /* with NUL */
200
1.69k
  entry->name = g_strdup(name);
201
1.69k
  entry->offset = offset;
202
1.69k
  g_ptr_array_add(strtab, entry);
203
1.69k
}
204
205
static GPtrArray *
206
fu_elf_firmware_strtab_new(void)
207
235
{
208
235
  g_autoptr(GPtrArray) strtab =
209
235
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_elf_firmware_strtab_entry_free);
210
235
  fu_elf_firmware_strtab_insert(strtab, "");
211
235
  fu_elf_firmware_strtab_insert(strtab, ".shstrtab");
212
235
  return g_steal_pointer(&strtab);
213
235
}
214
215
static GByteArray *
216
fu_elf_firmware_strtab_write(GPtrArray *strtab)
217
221
{
218
221
  g_autoptr(GByteArray) buf = g_byte_array_new();
219
1.76k
  for (guint i = 0; i < strtab->len; i++) {
220
1.54k
    FuElfFirmwareStrtabEntry *entry = g_ptr_array_index(strtab, i);
221
1.54k
    g_byte_array_append(buf, (const guint8 *)entry->name, entry->namesz);
222
1.54k
  }
223
221
  return g_steal_pointer(&buf);
224
221
}
225
226
static gsize
227
fu_elf_firmware_strtab_get_offset_for_name(GPtrArray *strtab, const gchar *name)
228
0
{
229
0
  for (guint i = 0; i < strtab->len; i++) {
230
0
    FuElfFirmwareStrtabEntry *entry = g_ptr_array_index(strtab, i);
231
0
    if (g_strcmp0(entry->name, name) == 0)
232
0
      return entry->offset;
233
0
  }
234
0
  return 0;
235
0
}
236
237
static GByteArray *
238
fu_elf_firmware_write(FuFirmware *firmware, GError **error)
239
235
{
240
235
  const gsize physical_addr = 0x80000000;
241
235
  gsize section_offset = 0;
242
235
  g_autoptr(FuStructElfFileHeader64le) st_filehdr = fu_struct_elf_file_header64le_new();
243
235
  g_autoptr(FuStructElfProgramHeader64le) st_proghdr = fu_struct_elf_program_header64le_new();
244
235
  g_autoptr(GByteArray) buf = g_byte_array_new();
245
235
  g_autoptr(GByteArray) section_data = g_byte_array_new();
246
235
  g_autoptr(GByteArray) section_hdr = g_byte_array_new();
247
235
  g_autoptr(GByteArray) shstrtab = NULL;
248
235
  g_autoptr(GPtrArray) imgs = NULL;
249
235
  g_autoptr(GPtrArray) strtab = fu_elf_firmware_strtab_new();
250
251
  /* build the string table:
252
   *
253
   *    \0
254
   *    .text\0
255
   *    .rodata\0
256
   */
257
235
  imgs = fu_firmware_get_images(firmware);
258
1.46k
  for (guint i = 0; i < imgs->len; i++) {
259
1.24k
    FuFirmware *img = g_ptr_array_index(imgs, i);
260
1.24k
    if (fu_firmware_get_id(img) == NULL) {
261
14
      g_set_error(error,
262
14
            FWUPD_ERROR,
263
14
            FWUPD_ERROR_INVALID_DATA,
264
14
            "section 0x%x must have an ID",
265
14
            (guint)fu_firmware_get_idx(img));
266
14
      return NULL;
267
14
    }
268
1.22k
    fu_elf_firmware_strtab_insert(strtab, fu_firmware_get_id(img));
269
1.22k
  }
270
221
  shstrtab = fu_elf_firmware_strtab_write(strtab);
271
272
  /* build the section data:
273
   *
274
   *    shstrtab
275
   *    [img]
276
   *    [img]
277
   *    [img]
278
   *
279
   * NOTE: requires shstrtab to be set
280
   */
281
221
  g_byte_array_append(section_data, shstrtab->data, shstrtab->len);
282
221
  for (guint i = 0; i < imgs->len; i++) {
283
67
    FuFirmware *img = g_ptr_array_index(imgs, i);
284
67
    g_autoptr(GBytes) blob = fu_firmware_get_bytes(img, error);
285
67
    if (blob == NULL)
286
67
      return NULL;
287
0
    fu_byte_array_append_bytes(section_data, blob);
288
0
  }
289
290
  /* calculate the offset of each section */
291
154
  section_offset = st_filehdr->buf->len + st_proghdr->buf->len + shstrtab->len;
292
154
  for (guint i = 0; i < imgs->len; i++) {
293
0
    FuFirmware *img = g_ptr_array_index(imgs, i);
294
0
    fu_firmware_set_offset(img, section_offset);
295
0
    section_offset += fu_firmware_get_size(img);
296
0
  }
297
298
  /* build the section header:
299
   *  1. empty section header
300
   *  2. [image] section headers
301
   *  3. shstrtab
302
   *
303
   * NOTE: requires image offset to be set
304
   */
305
154
  if (imgs->len > 0) {
306
0
    g_autoptr(FuStructElfSectionHeader64le) st_secthdr =
307
0
        fu_struct_elf_section_header64le_new();
308
0
    fu_byte_array_append_array(section_hdr, st_secthdr->buf);
309
0
  }
310
154
  for (guint i = 0; i < imgs->len; i++) {
311
0
    FuFirmware *img = g_ptr_array_index(imgs, i);
312
0
    g_autoptr(FuStructElfSectionHeader64le) st_secthdr =
313
0
        fu_struct_elf_section_header64le_new();
314
0
    gsize strtab_offset =
315
0
        fu_elf_firmware_strtab_get_offset_for_name(strtab, fu_firmware_get_id(img));
316
0
    fu_struct_elf_section_header64le_set_name(st_secthdr, strtab_offset);
317
0
    fu_struct_elf_section_header64le_set_type(st_secthdr,
318
0
                FU_ELF_SECTION_HEADER_TYPE_PROGBITS);
319
0
    fu_struct_elf_section_header64le_set_flags(st_secthdr, 0x02);
320
0
    fu_struct_elf_section_header64le_set_addr(st_secthdr,
321
0
                physical_addr +
322
0
                    fu_firmware_get_offset(img));
323
0
    fu_struct_elf_section_header64le_set_offset(st_secthdr,
324
0
                  fu_firmware_get_offset(img));
325
0
    fu_struct_elf_section_header64le_set_size(st_secthdr, fu_firmware_get_size(img));
326
0
    fu_byte_array_append_array(section_hdr, st_secthdr->buf);
327
0
  }
328
154
  if (shstrtab->len > 0) {
329
154
    g_autoptr(FuStructElfSectionHeader64le) st_secthdr =
330
154
        fu_struct_elf_section_header64le_new();
331
154
    fu_struct_elf_section_header64le_set_name(st_secthdr,
332
154
                0x1); /* we made sure this was first */
333
154
    fu_struct_elf_section_header64le_set_type(st_secthdr,
334
154
                FU_ELF_SECTION_HEADER_TYPE_STRTAB);
335
154
    fu_struct_elf_section_header64le_set_offset(st_secthdr,
336
154
                  st_filehdr->buf->len +
337
154
                st_proghdr->buf->len);
338
154
    fu_struct_elf_section_header64le_set_size(st_secthdr, shstrtab->len);
339
154
    fu_byte_array_append_array(section_hdr, st_secthdr->buf);
340
154
  }
341
342
  /* update with the new totals */
343
154
  fu_struct_elf_file_header64le_set_entry(st_filehdr, physical_addr + 0x60);
344
154
  fu_struct_elf_file_header64le_set_shoff(st_filehdr,
345
154
            st_filehdr->buf->len + st_proghdr->buf->len +
346
154
                section_data->len);
347
154
  fu_struct_elf_file_header64le_set_phentsize(st_filehdr,
348
154
                FU_STRUCT_ELF_PROGRAM_HEADER64LE_SIZE);
349
154
  fu_struct_elf_file_header64le_set_phnum(st_filehdr, 1);
350
154
  fu_struct_elf_file_header64le_set_shentsize(st_filehdr,
351
154
                FU_STRUCT_ELF_SECTION_HEADER64LE_SIZE);
352
154
  fu_struct_elf_file_header64le_set_shnum(st_filehdr, 2 + imgs->len); /* <null> & shstrtab */
353
154
  fu_struct_elf_file_header64le_set_shstrndx(st_filehdr, imgs->len + 1);
354
154
  fu_struct_elf_program_header64le_set_vaddr(st_proghdr, physical_addr);
355
154
  fu_struct_elf_program_header64le_set_paddr(st_proghdr, physical_addr);
356
154
  fu_struct_elf_program_header64le_set_filesz(st_proghdr,
357
154
                st_filehdr->buf->len + st_proghdr->buf->len +
358
154
              section_data->len + section_hdr->len);
359
154
  fu_struct_elf_program_header64le_set_memsz(st_proghdr,
360
154
               st_filehdr->buf->len + st_proghdr->buf->len +
361
154
                   section_data->len + section_hdr->len);
362
363
  /* add file header, sections, then section headers */
364
154
  fu_byte_array_append_array(buf, st_filehdr->buf);
365
154
  fu_byte_array_append_array(buf, st_proghdr->buf);
366
154
  fu_byte_array_append_array(buf, section_data);
367
154
  fu_byte_array_append_array(buf, section_hdr);
368
154
  return g_steal_pointer(&buf);
369
221
}
370
371
static void
372
fu_elf_firmware_init(FuElfFirmware *self)
373
1.19k
{
374
1.19k
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE);
375
1.19k
  fu_firmware_set_images_max(FU_FIRMWARE(self), 1024);
376
1.19k
}
377
378
static void
379
fu_elf_firmware_class_init(FuElfFirmwareClass *klass)
380
1
{
381
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
382
1
  firmware_class->validate = fu_elf_firmware_validate;
383
1
  firmware_class->parse = fu_elf_firmware_parse;
384
1
  firmware_class->write = fu_elf_firmware_write;
385
1
}
386
387
/**
388
 * fu_elf_firmware_new:
389
 *
390
 * Creates a new #FuElfFirmware
391
 *
392
 * Since: 1.9.3
393
 **/
394
FuFirmware *
395
fu_elf_firmware_new(void)
396
0
{
397
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_ELF_FIRMWARE, NULL));
398
0
}