/src/fwupd/libfwupdplugin/fu-efi-file.c
Line | Count | Source (jump to first uncovered line) |
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 | | G_DEFINE_TYPE_WITH_PRIVATE(FuEfiFile, fu_efi_file, FU_TYPE_FIRMWARE) |
33 | 68.7k | #define GET_PRIVATE(o) (fu_efi_file_get_instance_private(o)) |
34 | | |
35 | 7.53k | #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 | 36.9k | { |
56 | 36.9k | gsize bufsz = 0; |
57 | 36.9k | guint8 checksum = 0; |
58 | 36.9k | const guint8 *buf = g_bytes_get_data(blob, &bufsz); |
59 | 926k | for (gsize i = 0; i < bufsz; i++) { |
60 | 889k | if (i == FU_STRUCT_EFI_FILE_OFFSET_HDR_CHECKSUM) |
61 | 36.9k | continue; |
62 | 852k | if (i == FU_STRUCT_EFI_FILE_OFFSET_DATA_CHECKSUM) |
63 | 36.9k | continue; |
64 | 815k | if (i == FU_STRUCT_EFI_FILE_OFFSET_STATE) |
65 | 36.9k | continue; |
66 | 778k | checksum += buf[i]; |
67 | 778k | } |
68 | 36.9k | return (guint8)(0x100u - (guint)checksum); |
69 | 36.9k | } |
70 | | |
71 | | static gboolean |
72 | | fu_efi_file_parse(FuFirmware *firmware, |
73 | | GInputStream *stream, |
74 | | FuFirmwareParseFlags flags, |
75 | | GError **error) |
76 | 30.5k | { |
77 | 30.5k | FuEfiFile *self = FU_EFI_FILE(firmware); |
78 | 30.5k | FuEfiFilePrivate *priv = GET_PRIVATE(self); |
79 | 30.5k | guint32 size = 0x0; |
80 | 30.5k | g_autofree gchar *guid_str = NULL; |
81 | 30.5k | g_autoptr(GByteArray) st = NULL; |
82 | 30.5k | g_autoptr(GInputStream) partial_stream = NULL; |
83 | | |
84 | | /* parse */ |
85 | 30.5k | st = fu_struct_efi_file_parse_stream(stream, 0x0, error); |
86 | 30.5k | if (st == NULL) |
87 | 88 | return FALSE; |
88 | 30.4k | priv->type = fu_struct_efi_file_get_type(st); |
89 | 30.4k | priv->attrib = fu_struct_efi_file_get_attrs(st); |
90 | 30.4k | guid_str = |
91 | 30.4k | fwupd_guid_to_string(fu_struct_efi_file_get_name(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); |
92 | 30.4k | fu_firmware_set_id(firmware, guid_str); |
93 | | |
94 | | /* extended size exists so size must be set to zero */ |
95 | 30.4k | if (priv->attrib & FU_EFI_FILE_ATTRIB_LARGE_FILE) { |
96 | 337 | if (fu_struct_efi_file_get_size(st) != 0) { |
97 | 99 | g_set_error(error, |
98 | 99 | FWUPD_ERROR, |
99 | 99 | FWUPD_ERROR_INTERNAL, |
100 | 99 | "invalid FFS size -- expected 0x0 and got 0x%x", |
101 | 99 | (guint)fu_struct_efi_file_get_size(st)); |
102 | 99 | return FALSE; |
103 | 99 | } |
104 | 238 | fu_struct_efi_file_unref(st); |
105 | 238 | st = fu_struct_efi_file2_parse_stream(stream, 0x0, error); |
106 | 238 | if (st == NULL) |
107 | 9 | return FALSE; |
108 | 229 | size = fu_struct_efi_file2_get_extended_size(st); |
109 | 30.1k | } else { |
110 | 30.1k | size = fu_struct_efi_file_get_size(st); |
111 | 30.1k | } |
112 | 30.3k | if (size < st->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 | 30.3k | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
123 | 30.3k | guint8 hdr_checksum_verify; |
124 | 30.3k | g_autoptr(GBytes) hdr_blob = NULL; |
125 | | |
126 | 30.3k | hdr_blob = fu_input_stream_read_bytes(stream, 0x0, st->len, NULL, error); |
127 | 30.3k | if (hdr_blob == NULL) |
128 | 0 | return FALSE; |
129 | 30.3k | hdr_checksum_verify = fu_efi_file_hdr_checksum8(hdr_blob); |
130 | 30.3k | if (hdr_checksum_verify != fu_struct_efi_file_get_hdr_checksum(st)) { |
131 | 240 | g_set_error(error, |
132 | 240 | FWUPD_ERROR, |
133 | 240 | FWUPD_ERROR_INVALID_FILE, |
134 | 240 | "checksum invalid, got %02x, expected %02x", |
135 | 240 | hdr_checksum_verify, |
136 | 240 | fu_struct_efi_file_get_hdr_checksum(st)); |
137 | 240 | return FALSE; |
138 | 240 | } |
139 | 30.3k | } |
140 | | |
141 | | /* add simple blob */ |
142 | 30.1k | partial_stream = fu_partial_input_stream_new(stream, st->len, size - st->len, error); |
143 | 30.1k | if (partial_stream == NULL) { |
144 | 68 | g_prefix_error_literal(error, "failed to cut EFI blob: "); |
145 | 68 | return FALSE; |
146 | 68 | } |
147 | | |
148 | | /* verify data checksum */ |
149 | 30.0k | if ((priv->attrib & FU_EFI_FILE_ATTRIB_CHECKSUM) > 0 && |
150 | 30.0k | (flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
151 | 686 | guint8 data_checksum_verify = 0; |
152 | 686 | if (!fu_input_stream_compute_sum8(partial_stream, &data_checksum_verify, error)) |
153 | 0 | return FALSE; |
154 | 686 | if (0x100 - data_checksum_verify != fu_struct_efi_file_get_data_checksum(st)) { |
155 | 72 | g_set_error(error, |
156 | 72 | FWUPD_ERROR, |
157 | 72 | FWUPD_ERROR_INVALID_FILE, |
158 | 72 | "checksum invalid, got 0x%02x, expected 0x%02x", |
159 | 72 | 0x100u - data_checksum_verify, |
160 | 72 | fu_struct_efi_file_get_data_checksum(st)); |
161 | 72 | return FALSE; |
162 | 72 | } |
163 | 686 | } |
164 | | |
165 | | /* add sections */ |
166 | 29.9k | if (priv->type != FU_EFI_FILE_TYPE_FFS_PAD && priv->type != FU_EFI_FILE_TYPE_RAW) { |
167 | 18.9k | if (!fu_efi_parse_sections(firmware, partial_stream, 0, flags, error)) { |
168 | 3.68k | g_prefix_error_literal(error, "failed to add firmware image: "); |
169 | 3.68k | return FALSE; |
170 | 3.68k | } |
171 | 18.9k | } else { |
172 | 11.0k | if (!fu_firmware_set_stream(firmware, partial_stream, error)) |
173 | 0 | return FALSE; |
174 | 11.0k | } |
175 | | |
176 | | /* align size for volume */ |
177 | 26.2k | fu_firmware_set_size(firmware, |
178 | 26.2k | fu_common_align_up(size, fu_firmware_get_alignment(firmware))); |
179 | | |
180 | | /* success */ |
181 | 26.2k | return TRUE; |
182 | 29.9k | } |
183 | | |
184 | | static GBytes * |
185 | | fu_efi_file_write_sections(FuFirmware *firmware, GError **error) |
186 | 7.03k | { |
187 | 7.03k | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
188 | 7.03k | g_autoptr(GByteArray) buf = g_byte_array_new(); |
189 | | |
190 | | /* sanity check */ |
191 | 7.03k | if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) { |
192 | 0 | g_set_error(error, |
193 | 0 | FWUPD_ERROR, |
194 | 0 | FWUPD_ERROR_INVALID_FILE, |
195 | 0 | "alignment invalid, got 0x%02x", |
196 | 0 | fu_firmware_get_alignment(firmware)); |
197 | 0 | return NULL; |
198 | 0 | } |
199 | | |
200 | | /* no sections defined */ |
201 | 7.03k | if (images->len == 0) |
202 | 2.81k | return fu_firmware_get_bytes_with_patches(firmware, error); |
203 | | |
204 | | /* add each section */ |
205 | 11.7k | for (guint i = 0; i < images->len; i++) { |
206 | 7.91k | FuFirmware *img = g_ptr_array_index(images, i); |
207 | 7.91k | g_autoptr(GBytes) blob = NULL; |
208 | 7.91k | fu_firmware_set_offset(img, buf->len); |
209 | 7.91k | blob = fu_firmware_write(img, error); |
210 | 7.91k | if (blob == NULL) |
211 | 383 | return NULL; |
212 | 7.53k | fu_byte_array_append_bytes(buf, blob); |
213 | 7.53k | fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_4, 0xFF); |
214 | | |
215 | | /* sanity check */ |
216 | 7.53k | if (buf->len > FU_EFI_FILE_SIZE_MAX) { |
217 | 0 | g_set_error(error, |
218 | 0 | FWUPD_ERROR, |
219 | 0 | FWUPD_ERROR_INVALID_FILE, |
220 | 0 | "EFI file too large, 0x%02x > 0x%02x", |
221 | 0 | (guint)buf->len, |
222 | 0 | (guint)FU_EFI_FILE_SIZE_MAX); |
223 | 0 | return NULL; |
224 | 0 | } |
225 | 7.53k | } |
226 | | |
227 | | /* success */ |
228 | 3.83k | return g_bytes_new(buf->data, buf->len); |
229 | 4.21k | } |
230 | | |
231 | | static GByteArray * |
232 | | fu_efi_file_write(FuFirmware *firmware, GError **error) |
233 | 7.03k | { |
234 | 7.03k | FuEfiFile *self = FU_EFI_FILE(firmware); |
235 | 7.03k | FuEfiFilePrivate *priv = GET_PRIVATE(self); |
236 | 7.03k | fwupd_guid_t guid = {0x0}; |
237 | 7.03k | g_autoptr(GByteArray) st = fu_struct_efi_file_new(); |
238 | 7.03k | g_autoptr(GBytes) blob = NULL; |
239 | 7.03k | g_autoptr(GBytes) hdr_blob = NULL; |
240 | | |
241 | | /* simple blob for now */ |
242 | 7.03k | blob = fu_efi_file_write_sections(firmware, error); |
243 | 7.03k | if (blob == NULL) |
244 | 421 | return NULL; |
245 | 6.61k | if (fu_firmware_get_id(firmware) != NULL) { |
246 | 6.61k | if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), |
247 | 6.61k | &guid, |
248 | 6.61k | FWUPD_GUID_FLAG_MIXED_ENDIAN, |
249 | 6.61k | error)) |
250 | 0 | return NULL; |
251 | 6.61k | } |
252 | 6.61k | fu_struct_efi_file_set_name(st, &guid); |
253 | 6.61k | fu_struct_efi_file_set_hdr_checksum(st, 0x0); |
254 | 6.61k | fu_struct_efi_file_set_data_checksum(st, 0x100 - fu_sum8_bytes(blob)); |
255 | 6.61k | fu_struct_efi_file_set_type(st, priv->type); |
256 | 6.61k | fu_struct_efi_file_set_attrs(st, priv->attrib); |
257 | 6.61k | fu_struct_efi_file_set_size(st, g_bytes_get_size(blob) + st->len); |
258 | | |
259 | | /* fix up header checksum */ |
260 | 6.61k | hdr_blob = g_bytes_new_static(st->data, st->len); |
261 | 6.61k | fu_struct_efi_file_set_hdr_checksum(st, fu_efi_file_hdr_checksum8(hdr_blob)); |
262 | | |
263 | | /* success */ |
264 | 6.61k | fu_byte_array_append_bytes(st, blob); |
265 | 6.61k | return g_steal_pointer(&st); |
266 | 6.61k | } |
267 | | |
268 | | static gboolean |
269 | | fu_efi_file_build(FuFirmware *firmware, XbNode *n, GError **error) |
270 | 0 | { |
271 | 0 | FuEfiFile *self = FU_EFI_FILE(firmware); |
272 | 0 | FuEfiFilePrivate *priv = GET_PRIVATE(self); |
273 | 0 | guint64 tmp; |
274 | | |
275 | | /* simple properties */ |
276 | 0 | tmp = xb_node_query_text_as_uint(n, "type", NULL); |
277 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) |
278 | 0 | priv->type = tmp; |
279 | 0 | tmp = xb_node_query_text_as_uint(n, "attrib", NULL); |
280 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) |
281 | 0 | priv->attrib = tmp; |
282 | | |
283 | | /* success */ |
284 | 0 | return TRUE; |
285 | 0 | } |
286 | | |
287 | | static void |
288 | | fu_efi_file_init(FuEfiFile *self) |
289 | 31.1k | { |
290 | 31.1k | FuEfiFilePrivate *priv = GET_PRIVATE(self); |
291 | 31.1k | priv->attrib = FU_EFI_FILE_ATTRIB_NONE; |
292 | 31.1k | priv->type = FU_EFI_FILE_TYPE_RAW; |
293 | 31.1k | fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_8); |
294 | 31.1k | g_type_ensure(FU_TYPE_EFI_SECTION); |
295 | 31.1k | } |
296 | | |
297 | | static void |
298 | | fu_efi_file_class_init(FuEfiFileClass *klass) |
299 | 2 | { |
300 | 2 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
301 | 2 | firmware_class->parse = fu_efi_file_parse; |
302 | 2 | firmware_class->write = fu_efi_file_write; |
303 | 2 | firmware_class->build = fu_efi_file_build; |
304 | 2 | firmware_class->export = fu_efi_file_export; |
305 | 2 | } |
306 | | |
307 | | /** |
308 | | * fu_efi_file_new: |
309 | | * |
310 | | * Creates a new #FuFirmware |
311 | | * |
312 | | * Since: 2.0.0 |
313 | | **/ |
314 | | FuFirmware * |
315 | | fu_efi_file_new(void) |
316 | 31.1k | { |
317 | 31.1k | return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FILE, NULL)); |
318 | 31.1k | } |