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