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