/src/fwupd/plugins/uf2/fu-uf2-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2021 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-uf2-firmware.h" |
10 | | #include "fu-uf2-struct.h" |
11 | | |
12 | | struct _FuUf2Firmware { |
13 | | FuFirmware parent_instance; |
14 | | }; |
15 | | |
16 | 1.71k | G_DEFINE_TYPE(FuUf2Firmware, fu_uf2_firmware, FU_TYPE_FIRMWARE) |
17 | 1.71k | |
18 | 1.71k | static gboolean |
19 | 1.71k | fu_uf2_firmware_parse_extensions(FuUf2Firmware *self, |
20 | 1.71k | const guint8 *buf, |
21 | 1.71k | gsize bufsz, |
22 | 1.71k | gsize offset, |
23 | 1.71k | GError **error) |
24 | 1.71k | { |
25 | 14.1k | while (offset < bufsz) { |
26 | 13.6k | guint8 sz = 0; |
27 | 13.6k | FuUf2FirmwareTag tag = 0; |
28 | 13.6k | g_autoptr(FuStructUf2Extension) st_ext = NULL; |
29 | | |
30 | 13.6k | st_ext = fu_struct_uf2_extension_parse(buf, bufsz, offset, error); |
31 | 13.6k | if (st_ext == NULL) |
32 | 5 | return FALSE; |
33 | 13.6k | sz = fu_struct_uf2_extension_get_size(st_ext); |
34 | 13.6k | if (sz == 0) |
35 | 178 | break; |
36 | 13.4k | if (sz < 4) { |
37 | 8 | g_set_error(error, |
38 | 8 | FWUPD_ERROR, |
39 | 8 | FWUPD_ERROR_INVALID_DATA, |
40 | 8 | "invalid extension tag 0x%x [%s] size 0x%x", |
41 | 8 | tag, |
42 | 8 | fu_uf2_firmware_tag_to_string(tag), |
43 | 8 | (guint)sz); |
44 | 8 | return FALSE; |
45 | 8 | } |
46 | 13.4k | tag = fu_struct_uf2_extension_get_tag(st_ext); |
47 | 13.4k | if (tag == 0) |
48 | 17 | break; |
49 | 13.4k | if (tag == FU_UF2_FIRMWARE_TAG_VERSION) { |
50 | 2.09k | g_autofree gchar *str = NULL; |
51 | 2.09k | str = fu_memstrsafe(buf, |
52 | 2.09k | bufsz, |
53 | 2.09k | offset + st_ext->buf->len, |
54 | 2.09k | sz - st_ext->buf->len, |
55 | 2.09k | error); |
56 | 2.09k | if (str == NULL) |
57 | 17 | return FALSE; |
58 | 2.07k | fu_firmware_set_version(FU_FIRMWARE(self), str); |
59 | 11.3k | } else if (tag == FU_UF2_FIRMWARE_TAG_DESCRIPTION) { |
60 | 2.18k | g_autofree gchar *str = NULL; |
61 | 2.18k | str = fu_memstrsafe(buf, |
62 | 2.18k | bufsz, |
63 | 2.18k | offset + st_ext->buf->len, |
64 | 2.18k | sz - st_ext->buf->len, |
65 | 2.18k | error); |
66 | 2.18k | if (str == NULL) |
67 | 13 | return FALSE; |
68 | 2.17k | fu_firmware_set_id(FU_FIRMWARE(self), str); |
69 | 9.13k | } else { |
70 | 9.13k | if (g_getenv("FWUPD_FUZZER_RUNNING") == NULL) { |
71 | 0 | g_warning("unknown tag 0x%06x [%s]", |
72 | 0 | tag, |
73 | 0 | fu_uf2_firmware_tag_to_string(tag)); |
74 | 0 | } |
75 | 9.13k | } |
76 | | |
77 | | /* next! */ |
78 | 13.3k | if (!fu_size_checked_inc(&offset, |
79 | 13.3k | fu_common_align_up(sz, FU_FIRMWARE_ALIGNMENT_4), |
80 | 13.3k | error)) |
81 | 0 | return FALSE; |
82 | 13.3k | } |
83 | | |
84 | | /* success */ |
85 | 704 | return TRUE; |
86 | 747 | } |
87 | | |
88 | | static gboolean |
89 | | fu_uf2_firmware_parse_chunk(FuUf2Firmware *self, FuChunk *chk, GByteArray *tmp, GError **error) |
90 | 1.36k | { |
91 | 1.36k | gsize bufsz = fu_chunk_get_data_sz(chk); |
92 | 1.36k | const guint8 *buf = fu_chunk_get_data(chk); |
93 | 1.36k | guint32 flags = 0; |
94 | 1.36k | guint32 datasz = 0; |
95 | 1.36k | g_autoptr(FuStructUf2) st = NULL; |
96 | | |
97 | | /* parse */ |
98 | 1.36k | st = fu_struct_uf2_parse(fu_chunk_get_data(chk), |
99 | 1.36k | fu_chunk_get_data_sz(chk), |
100 | 1.36k | 0, /* offset */ |
101 | 1.36k | error); |
102 | 1.36k | if (st == NULL) |
103 | 237 | return FALSE; |
104 | 1.13k | flags = fu_struct_uf2_get_flags(st); |
105 | 1.13k | if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER) { |
106 | 1 | g_set_error_literal(error, |
107 | 1 | FWUPD_ERROR, |
108 | 1 | FWUPD_ERROR_NOT_SUPPORTED, |
109 | 1 | "container U2F firmware not supported"); |
110 | 1 | return FALSE; |
111 | 1 | } |
112 | 1.12k | datasz = fu_struct_uf2_get_payload_size(st); |
113 | 1.12k | if (datasz > 476) { |
114 | 18 | g_set_error(error, |
115 | 18 | FWUPD_ERROR, |
116 | 18 | FWUPD_ERROR_INVALID_DATA, |
117 | 18 | "data size impossible got 0x%08x", |
118 | 18 | datasz); |
119 | 18 | return FALSE; |
120 | 18 | } |
121 | 1.11k | if (fu_struct_uf2_get_block_no(st) != fu_chunk_get_idx(chk)) { |
122 | 58 | g_set_error(error, |
123 | 58 | FWUPD_ERROR, |
124 | 58 | FWUPD_ERROR_INVALID_DATA, |
125 | 58 | "block count invalid, expected 0x%04x and got 0x%04x", |
126 | 58 | fu_chunk_get_idx(chk), |
127 | 58 | fu_struct_uf2_get_block_no(st)); |
128 | 58 | return FALSE; |
129 | 58 | } |
130 | 1.05k | if (fu_struct_uf2_get_num_blocks(st) == 0) { |
131 | 1 | g_set_error_literal(error, |
132 | 1 | FWUPD_ERROR, |
133 | 1 | FWUPD_ERROR_INVALID_DATA, |
134 | 1 | "block count invalid, expected > 0"); |
135 | 1 | return FALSE; |
136 | 1 | } |
137 | 1.05k | if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY) { |
138 | 371 | if (fu_struct_uf2_get_family_id(st) == 0) { |
139 | 2 | g_set_error_literal(error, |
140 | 2 | FWUPD_ERROR, |
141 | 2 | FWUPD_ERROR_INVALID_DATA, |
142 | 2 | "family_id required but not supplied"); |
143 | 2 | return FALSE; |
144 | 2 | } |
145 | 371 | } |
146 | | |
147 | | /* assume first chunk is representative of firmware */ |
148 | 1.05k | if (fu_chunk_get_idx(chk) == 0) { |
149 | 412 | fu_firmware_set_addr(FU_FIRMWARE(self), fu_struct_uf2_get_target_addr(st)); |
150 | 412 | fu_firmware_set_idx(FU_FIRMWARE(self), fu_struct_uf2_get_family_id(st)); |
151 | 412 | } |
152 | | |
153 | | /* just append raw data */ |
154 | 1.05k | g_byte_array_append(tmp, fu_struct_uf2_get_data(st, NULL), datasz); |
155 | 1.05k | if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5) { |
156 | 164 | if (datasz < 24) { |
157 | 5 | g_set_error_literal(error, |
158 | 5 | FWUPD_ERROR, |
159 | 5 | FWUPD_ERROR_INVALID_DATA, |
160 | 5 | "not enough space for MD5 checksum"); |
161 | 5 | return FALSE; |
162 | 5 | } |
163 | 164 | } |
164 | 1.04k | if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG) { |
165 | 747 | if (!fu_uf2_firmware_parse_extensions(self, |
166 | 747 | buf, |
167 | 747 | bufsz, |
168 | 747 | datasz + FU_STRUCT_UF2_OFFSET_DATA, |
169 | 747 | error)) |
170 | 43 | return FALSE; |
171 | 747 | } |
172 | | |
173 | | /* success */ |
174 | 1.00k | return TRUE; |
175 | 1.04k | } |
176 | | |
177 | | static gboolean |
178 | | fu_uf2_firmware_parse(FuFirmware *firmware, |
179 | | GInputStream *stream, |
180 | | FuFirmwareParseFlags flags, |
181 | | GError **error) |
182 | 693 | { |
183 | 693 | FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware); |
184 | 693 | g_autoptr(GByteArray) tmp = g_byte_array_new(); |
185 | 693 | g_autoptr(GBytes) blob = NULL; |
186 | 693 | g_autoptr(FuChunkArray) chunks = NULL; |
187 | | |
188 | | /* read in fixed sized chunks */ |
189 | 693 | chunks = fu_chunk_array_new_from_stream(stream, |
190 | 693 | FU_CHUNK_ADDR_OFFSET_NONE, |
191 | 693 | FU_CHUNK_PAGESZ_NONE, |
192 | 693 | 512, |
193 | 693 | error); |
194 | 693 | if (chunks == NULL) |
195 | 0 | return FALSE; |
196 | 1.69k | for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { |
197 | 1.36k | g_autoptr(FuChunk) chk = NULL; |
198 | | |
199 | | /* prepare chunk */ |
200 | 1.36k | chk = fu_chunk_array_index(chunks, i, error); |
201 | 1.36k | if (chk == NULL) |
202 | 0 | return FALSE; |
203 | 1.36k | if (!fu_uf2_firmware_parse_chunk(self, chk, tmp, error)) |
204 | 365 | return FALSE; |
205 | 1.36k | } |
206 | | |
207 | | /* success */ |
208 | 328 | blob = g_bytes_new(tmp->data, tmp->len); |
209 | 328 | fu_firmware_set_bytes(firmware, blob); |
210 | 328 | return TRUE; |
211 | 693 | } |
212 | | |
213 | | static FuStructUf2Extension * |
214 | | fu_uf2_firmware_build_utf8_extension(FuUf2FirmwareTag tag, const gchar *str) |
215 | 835 | { |
216 | 835 | g_autoptr(FuStructUf2Extension) st = fu_struct_uf2_extension_new(); |
217 | 835 | fu_struct_uf2_extension_set_tag(st, tag); |
218 | 835 | fu_struct_uf2_extension_set_size(st, st->buf->len + strlen(str)); |
219 | 835 | g_byte_array_append(st->buf, (const guint8 *)str, strlen(str)); |
220 | 835 | fu_byte_array_align_up(st->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
221 | 835 | return g_steal_pointer(&st); |
222 | 835 | } |
223 | | |
224 | | static GByteArray * |
225 | | fu_uf2_firmware_write_chunk(FuUf2Firmware *self, FuChunk *chk, guint chk_len, GError **error) |
226 | 884 | { |
227 | 884 | gsize offset_ext = FU_STRUCT_UF2_OFFSET_DATA + fu_chunk_get_data_sz(chk); |
228 | 884 | guint32 addr = fu_firmware_get_addr(FU_FIRMWARE(self)); |
229 | 884 | guint32 flags = FU_UF2_FIRMWARE_BLOCK_FLAG_NONE; |
230 | 884 | g_autoptr(FuStructUf2) st = fu_struct_uf2_new(); |
231 | 884 | g_autoptr(GPtrArray) extensions = |
232 | 884 | g_ptr_array_new_with_free_func((GDestroyNotify)fu_struct_uf2_extension_unref); |
233 | | |
234 | | /* optional */ |
235 | 884 | if (fu_firmware_get_idx(FU_FIRMWARE(self)) > 0) |
236 | 335 | flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY; |
237 | | |
238 | | /* build extensions */ |
239 | 884 | if (fu_firmware_get_idx(FU_FIRMWARE(self)) == 0x0) { |
240 | 549 | if (fu_firmware_get_id(FU_FIRMWARE(self)) != NULL) { |
241 | 419 | g_ptr_array_add(extensions, |
242 | 419 | fu_uf2_firmware_build_utf8_extension( |
243 | 419 | FU_UF2_FIRMWARE_TAG_DESCRIPTION, |
244 | 419 | fu_firmware_get_id(FU_FIRMWARE(self)))); |
245 | 419 | } |
246 | 549 | if (fu_firmware_get_version(FU_FIRMWARE(self)) != NULL) { |
247 | 416 | g_ptr_array_add(extensions, |
248 | 416 | fu_uf2_firmware_build_utf8_extension( |
249 | 416 | FU_UF2_FIRMWARE_TAG_VERSION, |
250 | 416 | fu_firmware_get_version(FU_FIRMWARE(self)))); |
251 | 416 | } |
252 | 549 | if (extensions->len > 0) { |
253 | 427 | g_ptr_array_add(extensions, fu_struct_uf2_extension_new()); |
254 | 427 | flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG; |
255 | 427 | } |
256 | 549 | } |
257 | | |
258 | | /* offset from base address */ |
259 | 884 | addr += fu_chunk_get_idx(chk) * fu_chunk_get_data_sz(chk); |
260 | | |
261 | | /* build UF2 packet */ |
262 | 884 | fu_struct_uf2_set_flags(st, flags); |
263 | 884 | fu_struct_uf2_set_target_addr(st, addr); |
264 | 884 | fu_struct_uf2_set_payload_size(st, fu_chunk_get_data_sz(chk)); |
265 | 884 | fu_struct_uf2_set_block_no(st, fu_chunk_get_idx(chk)); |
266 | 884 | fu_struct_uf2_set_num_blocks(st, chk_len); |
267 | 884 | fu_struct_uf2_set_family_id(st, fu_firmware_get_idx(FU_FIRMWARE(self))); |
268 | 884 | if (!fu_struct_uf2_set_data(st, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) |
269 | 0 | return NULL; |
270 | | |
271 | | /* copy in any extensions */ |
272 | 2.12k | for (guint i = 0; i < extensions->len; i++) { |
273 | 1.25k | FuStructUf2Extension *st_ext = g_ptr_array_index(extensions, i); |
274 | 1.25k | if (!fu_memcpy_safe(st->buf->data, |
275 | 1.25k | st->buf->len, |
276 | 1.25k | offset_ext, |
277 | 1.25k | st_ext->buf->data, |
278 | 1.25k | st_ext->buf->len, |
279 | 1.25k | 0x0, |
280 | 1.25k | st_ext->buf->len, |
281 | 1.25k | error)) |
282 | 11 | return NULL; |
283 | 1.24k | offset_ext += st_ext->buf->len; |
284 | 1.24k | } |
285 | | |
286 | | /* success */ |
287 | 873 | return g_steal_pointer(&st->buf); |
288 | 884 | } |
289 | | |
290 | | static GByteArray * |
291 | | fu_uf2_firmware_write(FuFirmware *firmware, GError **error) |
292 | 328 | { |
293 | 328 | FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware); |
294 | 328 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
295 | 328 | g_autoptr(GInputStream) stream = NULL; |
296 | 328 | g_autoptr(FuChunkArray) chunks = NULL; |
297 | | |
298 | | /* data first */ |
299 | 328 | stream = fu_firmware_get_stream(firmware, error); |
300 | 328 | if (stream == NULL) |
301 | 0 | return NULL; |
302 | | |
303 | | /* write in chunks */ |
304 | 328 | chunks = fu_chunk_array_new_from_stream(stream, |
305 | 328 | fu_firmware_get_addr(firmware), |
306 | 328 | FU_CHUNK_PAGESZ_NONE, |
307 | 328 | 256, |
308 | 328 | error); |
309 | 328 | if (chunks == NULL) |
310 | 0 | return NULL; |
311 | 1.20k | for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { |
312 | 884 | g_autoptr(FuChunk) chk = NULL; |
313 | 884 | g_autoptr(GByteArray) tmp = NULL; |
314 | | |
315 | | /* prepare chunk */ |
316 | 884 | chk = fu_chunk_array_index(chunks, i, error); |
317 | 884 | if (chk == NULL) |
318 | 0 | return NULL; |
319 | 884 | tmp = fu_uf2_firmware_write_chunk(self, chk, fu_chunk_array_length(chunks), error); |
320 | 884 | if (tmp == NULL) |
321 | 11 | return NULL; |
322 | 873 | g_byte_array_append(buf, tmp->data, tmp->len); |
323 | 873 | } |
324 | | |
325 | | /* success */ |
326 | 317 | return g_steal_pointer(&buf); |
327 | 328 | } |
328 | | |
329 | | static void |
330 | | fu_uf2_firmware_init(FuUf2Firmware *self) |
331 | 693 | { |
332 | 693 | fu_firmware_set_size_max(FU_FIRMWARE(self), 256 * FU_MB); |
333 | 693 | } |
334 | | |
335 | | static void |
336 | | fu_uf2_firmware_class_init(FuUf2FirmwareClass *klass) |
337 | 1 | { |
338 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
339 | 1 | firmware_class->parse = fu_uf2_firmware_parse; |
340 | 1 | firmware_class->write = fu_uf2_firmware_write; |
341 | 1 | } |
342 | | |
343 | | FuFirmware * |
344 | | fu_uf2_firmware_new(void) |
345 | 0 | { |
346 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_UF2_FIRMWARE, NULL)); |
347 | 0 | } |