/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 | } |