Codebase list fwupd / 1bc9d99
libdfu: Add DfuPatch This allows us to binary patch firmware images. The diff generation is implemented with a forwards-only algorithm; this allows a vendor to remove non-free code without shipping a "reversed" version of the non-redistributable code. Richard Hughes authored 6 years ago Mario Limonciello committed 6 years ago
7 changed file(s) with 1027 addition(s) and 3 deletion(s). Raw diff Collapse all Expand all
0 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
1 *
2 * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
3 *
4 * Licensed under the GNU Lesser General Public License Version 2.1
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * SECTION:dfu-patch
23 * @short_description: Object representing a binary patch
24 *
25 * This object represents an binary patch that can be applied on a firmware
26 * image. The patch itself is made up of chunks of data that have an offset
27 * and that can replace the data to upgrade the firmware.
28 *
29 * Note: this is one way operation -- the patch can only be used to go forwards
30 * and also cannot be used to truncate the existing image.
31 *
32 * See also: #DfuImage, #DfuFirmware
33 */
34
35 #include "config.h"
36
37 #include <string.h>
38 #include <stdio.h>
39
40 #include "dfu-common.h"
41 #include "dfu-patch.h"
42 #include "dfu-error.h"
43
44 static void dfu_patch_finalize (GObject *object);
45
46 typedef struct __attribute__((packed)) {
47 guint32 off;
48 guint32 sz;
49 guint32 flags;
50 } DfuPatchChunkHeader;
51
52 typedef struct __attribute__((packed)) {
53 guint8 signature[4]; /* 'DfuP' */
54 guint8 reserved[4];
55 guint8 checksum_old[20]; /* SHA1 */
56 guint8 checksum_new[20]; /* SHA1 */
57 } DfuPatchFileHeader;
58
59 typedef struct {
60 GBytes *checksum_old;
61 GBytes *checksum_new;
62 GPtrArray *chunks; /* of DfuPatchChunk */
63 } DfuPatchPrivate;
64
65 typedef struct {
66 guint32 off;
67 GBytes *blob;
68 } DfuPatchChunk;
69
70 G_DEFINE_TYPE_WITH_PRIVATE (DfuPatch, dfu_patch, G_TYPE_OBJECT)
71 #define GET_PRIVATE(o) (dfu_patch_get_instance_private (o))
72
73 static void
74 dfu_patch_class_init (DfuPatchClass *klass)
75 {
76 GObjectClass *object_class = G_OBJECT_CLASS (klass);
77 object_class->finalize = dfu_patch_finalize;
78 }
79
80 static void
81 dfu_patch_chunk_free (DfuPatchChunk *chunk)
82 {
83 g_bytes_unref (chunk->blob);
84 g_free (chunk);
85 }
86
87 static void
88 dfu_patch_init (DfuPatch *self)
89 {
90 DfuPatchPrivate *priv = GET_PRIVATE (self);
91 priv->chunks = g_ptr_array_new_with_free_func ((GDestroyNotify) dfu_patch_chunk_free);
92 }
93
94 static void
95 dfu_patch_finalize (GObject *object)
96 {
97 DfuPatch *self = DFU_PATCH (object);
98 DfuPatchPrivate *priv = GET_PRIVATE (self);
99
100 if (priv->checksum_old != NULL)
101 g_bytes_unref (priv->checksum_old);
102 if (priv->checksum_new != NULL)
103 g_bytes_unref (priv->checksum_new);
104 g_ptr_array_unref (priv->chunks);
105
106 G_OBJECT_CLASS (dfu_patch_parent_class)->finalize (object);
107 }
108
109 /**
110 * dfu_patch_export:
111 * @self: a #DfuPatch
112 * @error: a #GError, or %NULL
113 *
114 * Converts the patch to a binary blob that can be stored as a file.
115 *
116 * Return value: (transfer full): blob
117 *
118 * Since: 0.9.6
119 **/
120 GBytes *
121 dfu_patch_export (DfuPatch *self, GError **error)
122 {
123 DfuPatchPrivate *priv = GET_PRIVATE (self);
124 gsize addr;
125 gsize sz;
126 guint8 *data;
127
128 g_return_val_if_fail (DFU_IS_PATCH (self), NULL);
129
130 /* check we have something to write */
131 if (priv->chunks->len == 0) {
132 g_set_error_literal (error,
133 DFU_ERROR,
134 DFU_ERROR_INVALID_FILE,
135 "no chunks to process");
136 return NULL;
137 }
138
139 /* calculate the size of the new blob */
140 sz = sizeof(DfuPatchFileHeader);
141 for (guint i = 0; i < priv->chunks->len; i++) {
142 DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i);
143 sz += sizeof(DfuPatchChunkHeader) + g_bytes_get_size (chunk->blob);
144 }
145 g_debug ("blob size is %" G_GSIZE_FORMAT, sz);
146
147 /* actually allocate and fill in the blob */
148 data = g_malloc0 (sz);
149 memcpy (data, "DfuP", 4);
150
151 /* add checksums */
152 if (priv->checksum_old != NULL) {
153 gsize csum_sz = 0;
154 const guint8 *csum_data = g_bytes_get_data (priv->checksum_old, &csum_sz);
155 memcpy (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_old),
156 csum_data, csum_sz);
157 }
158 if (priv->checksum_new != NULL) {
159 gsize csum_sz = 0;
160 const guint8 *csum_data = g_bytes_get_data (priv->checksum_new, &csum_sz);
161 memcpy (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_new),
162 csum_data, csum_sz);
163 }
164
165 addr = sizeof(DfuPatchFileHeader);
166 for (guint i = 0; i < priv->chunks->len; i++) {
167 DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i);
168 DfuPatchChunkHeader chunkhdr;
169 gsize sz_tmp = 0;
170 guint8 *data_new = g_bytes_get_data (chunk->blob, &sz_tmp);
171
172 /* build chunk header and append data */
173 chunkhdr.off = GUINT32_TO_LE (chunk->off);
174 chunkhdr.sz = GUINT32_TO_LE (sz_tmp);
175 chunkhdr.flags = 0;
176 memcpy (data + addr, &chunkhdr, sizeof(DfuPatchChunkHeader));
177 memcpy (data + addr + sizeof(DfuPatchChunkHeader), data_new, sz_tmp);
178
179 /* move up after the copied data */
180 addr += sizeof(DfuPatchChunkHeader) + sz_tmp;
181 }
182 return g_bytes_new_take (data, sz);
183
184 }
185
186 /**
187 * dfu_patch_import:
188 * @self: a #DfuPatch
189 * @blob: patch data
190 * @error: a #GError, or %NULL
191 *
192 * Creates a patch from a serialized patch, possibly from a file.
193 *
194 * Return value: %TRUE on success
195 *
196 * Since: 0.9.6
197 **/
198 gboolean
199 dfu_patch_import (DfuPatch *self, GBytes *blob, GError **error)
200 {
201 DfuPatchPrivate *priv = GET_PRIVATE (self);
202 const guint8 *data;
203 gsize sz = 0;
204 guint32 off;
205
206 g_return_val_if_fail (DFU_IS_PATCH (self), FALSE);
207 g_return_val_if_fail (blob != NULL, FALSE);
208
209 /* cannot reuse object */
210 if (priv->chunks->len > 0) {
211 g_set_error_literal (error,
212 DFU_ERROR,
213 DFU_ERROR_INVALID_FILE,
214 "patch has already been loaded");
215 return FALSE;
216 }
217
218 /* check minimum size */
219 data = g_bytes_get_data (blob, &sz);
220 if (sz < sizeof(DfuPatchFileHeader) + sizeof(DfuPatchChunkHeader) + 1) {
221 g_set_error_literal (error,
222 DFU_ERROR,
223 DFU_ERROR_INVALID_FILE,
224 "file is too small");
225 return FALSE;
226 }
227
228 /* check header */
229 if (memcmp (data, "DfuP", 4) != 0) {
230 g_set_error_literal (error,
231 DFU_ERROR,
232 DFU_ERROR_INVALID_FILE,
233 "header signature is not correct");
234 return FALSE;
235 }
236
237 /* get checksums */
238 priv->checksum_old = g_bytes_new (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_old), 20);
239 priv->checksum_new = g_bytes_new (data + G_STRUCT_OFFSET(DfuPatchFileHeader,checksum_new), 20);
240
241 /* look for each chunk */
242 off = sizeof(DfuPatchFileHeader);
243 while (off < (guint32) sz) {
244 DfuPatchChunkHeader *chunkhdr = (DfuPatchChunkHeader *) (data + off);
245 DfuPatchChunk *chunk;
246 guint32 chunk_sz = GUINT32_FROM_LE (chunkhdr->sz);
247 guint32 chunk_off = GUINT32_FROM_LE (chunkhdr->off);
248
249 /* check chunk size, assuming it can overflow */
250 if (chunk_sz > sz || off + chunk_sz > sz) {
251 g_set_error (error,
252 DFU_ERROR,
253 DFU_ERROR_INVALID_FILE,
254 "chunk offset 0x%04x outsize file size 0x%04x",
255 (guint) (off + chunk_sz), (guint) sz);
256 return FALSE;
257 }
258 chunk = g_new0 (DfuPatchChunk, 1);
259 chunk->off = chunk_off;
260 chunk->blob = g_bytes_new_from_bytes (blob, off + sizeof(DfuPatchChunkHeader), chunk_sz);
261 g_ptr_array_add (priv->chunks, chunk);
262 off += sizeof(DfuPatchChunkHeader) + chunk_sz;
263 }
264
265 /* check we finished properly */
266 if (off != sz) {
267 g_set_error_literal (error,
268 DFU_ERROR,
269 DFU_ERROR_INVALID_FILE,
270 "blob chunk sizes did not sum to total");
271 return FALSE;
272 }
273
274 /* success */
275 return TRUE;
276 }
277
278
279 static GBytes *
280 dfu_patch_calculate_checksum (GBytes *blob)
281 {
282 const guchar *data;
283 gsize digest_len = 20;
284 gsize sz = 0;
285 guint8 *buf = g_malloc0 (digest_len);
286 g_autoptr(GChecksum) csum = NULL;
287 csum = g_checksum_new (G_CHECKSUM_SHA1);
288 data = g_bytes_get_data (blob, &sz);
289 g_checksum_update (csum, data, (gssize) sz);
290 g_checksum_get_digest (csum, buf, &digest_len);
291 return g_bytes_new (buf, digest_len);
292 }
293
294 typedef struct {
295 guint32 diff_start;
296 guint32 diff_end;
297 GBytes *blob; /* no ref */
298 } DfuPatchCreateHelper;
299
300 static void
301 dfu_patch_flush (DfuPatch *self, DfuPatchCreateHelper *helper)
302 {
303 DfuPatchChunk *chunk;
304 DfuPatchPrivate *priv = GET_PRIVATE (self);
305
306 if (helper->diff_end == 0xffff)
307 return;
308 g_debug ("add chunk @0x%04x (len %" G_GUINT32_FORMAT ")",
309 (guint) helper->diff_start, helper->diff_end - helper->diff_start + 1);
310
311 chunk = g_new0 (DfuPatchChunk, 1);
312 chunk->off = helper->diff_start;
313 chunk->blob = g_bytes_new_from_bytes (helper->blob, chunk->off,
314 helper->diff_end - helper->diff_start + 1);
315 g_ptr_array_add (priv->chunks, chunk);
316 helper->diff_end = 0xffff;
317 }
318
319 /**
320 * dfu_patch_create:
321 * @self: a #DfuPatch
322 * @blob1: a #GBytes, typically the old firmware image
323 * @blob2: a #GBytes, typically the new firmware image
324 * @error: a #GError, or %NULL
325 *
326 * Creates a patch from two blobs of memory.
327 *
328 * The blobs should ideally be the same size. If @blob2 is has grown in size
329 * the binary diff will still work but the algorithm will probably not perform
330 * well unless the majority of data has just been appended.
331 *
332 * As an additional constrainst, @blob2 cannot be smaller than @blob1, i.e.
333 * the firmware cannot be truncated by this format.
334 *
335 * Return value: %TRUE on success
336 *
337 * Since: 0.9.6
338 **/
339 gboolean
340 dfu_patch_create (DfuPatch *self, GBytes *blob1, GBytes *blob2, GError **error)
341 {
342 DfuPatchPrivate *priv = GET_PRIVATE (self);
343 DfuPatchCreateHelper helper;
344 guint8 *data1;
345 guint8 *data2;
346 gsize sz1 = 0;
347 gsize sz2 = 0;
348 guint32 same_sz = 0;
349
350 g_return_val_if_fail (DFU_IS_PATCH (self), FALSE);
351 g_return_val_if_fail (blob1 != NULL, FALSE);
352 g_return_val_if_fail (blob2 != NULL, FALSE);
353
354 /* are the blobs the same */
355 if (g_bytes_equal (blob1, blob2)) {
356 g_set_error_literal (error,
357 DFU_ERROR,
358 DFU_ERROR_INVALID_FILE,
359 "old and new binaries are the same");
360 return FALSE;
361 }
362
363 /* cannot reuse object */
364 if (priv->chunks->len > 0) {
365 g_set_error_literal (error,
366 DFU_ERROR,
367 DFU_ERROR_INVALID_FILE,
368 "patch has already been loaded");
369 return FALSE;
370 }
371
372 /* get the hash of the old firmware file */
373 priv->checksum_old = dfu_patch_calculate_checksum (blob1);
374 priv->checksum_new = dfu_patch_calculate_checksum (blob2);
375
376 /* get the raw data, and ensure they are the same size */
377 data1 = g_bytes_get_data (blob1, &sz1);
378 data2 = g_bytes_get_data (blob2, &sz2);
379 if (sz1 > sz2) {
380 g_set_error (error,
381 DFU_ERROR,
382 DFU_ERROR_NOT_SUPPORTED,
383 "firmware binary cannot go down, got "
384 "%" G_GSIZE_FORMAT " and %" G_GSIZE_FORMAT,
385 sz1, sz2);
386 return FALSE;
387 }
388 if (sz1 == sz2) {
389 g_debug ("binary staying same size: %" G_GSIZE_FORMAT, sz1);
390 } else {
391 g_debug ("binary growing from: %" G_GSIZE_FORMAT
392 " to %" G_GSIZE_FORMAT, sz1, sz2);
393 }
394
395 /* start the dumb comparison algorithm */
396 helper.diff_start = 0;
397 helper.diff_end = 0xffff;
398 helper.blob = blob2;
399 for (gsize i = 0; i < sz1 || i < sz2; i++) {
400 if (i < sz1 && i < sz2 &&
401 data1[i] == data2[i]) {
402 /* if we got enough the same, dump what is pending */
403 if (++same_sz > sizeof(DfuPatchChunkHeader) * 2)
404 dfu_patch_flush (self, &helper);
405 continue;
406 }
407 if (helper.diff_end == 0xffff)
408 helper.diff_start = (guint32) i;
409 helper.diff_end = (guint32) i;
410 same_sz = 0;
411 }
412 dfu_patch_flush (self, &helper);
413 return TRUE;
414 }
415
416 static gchar *
417 _g_bytes_to_string (GBytes *blob)
418 {
419 gsize sz = 0;
420 const guint8 *data = g_bytes_get_data (blob, &sz);
421 GString *str = g_string_new (NULL);
422 for (gsize i = 0; i < sz; i++)
423 g_string_append_printf (str, "%02x", (guint) data[i]);
424 return g_string_free (str, FALSE);
425 }
426
427 /**
428 * dfu_patch_get_checksum_old:
429 * @self: a #DfuPatch
430 *
431 * Get the checksum for the old firmware image.
432 *
433 * Return value: A #GBytes, or %NULL if nothing has been loaded.
434 *
435 * Since: 0.9.6
436 **/
437 GBytes *
438 dfu_patch_get_checksum_old (DfuPatch *self)
439 {
440 DfuPatchPrivate *priv = GET_PRIVATE (self);
441 return priv->checksum_old;
442 }
443
444 /**
445 * dfu_patch_get_checksum_new:
446 * @self: a #DfuPatch
447 *
448 * Get the checksum for the new firmware image.
449 *
450 * Return value: A #GBytes, or %NULL if nothing has been loaded.
451 *
452 * Since: 0.9.6
453 **/
454 GBytes *
455 dfu_patch_get_checksum_new (DfuPatch *self)
456 {
457 DfuPatchPrivate *priv = GET_PRIVATE (self);
458 return priv->checksum_new;
459 }
460
461 /**
462 * dfu_patch_apply:
463 * @self: a #DfuPatch
464 * @blob: a #GBytes, typically the old firmware image
465 * @flags: a #DfuPatchApplyFlags, e.g. %DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM
466 * @error: a #GError, or %NULL
467 *
468 * Apply the currently loaded patch to a new firmware image.
469 *
470 * Return value: A #GBytes, typically saved as the new firmware file
471 *
472 * Since: 0.9.6
473 **/
474 GBytes *
475 dfu_patch_apply (DfuPatch *self, GBytes *blob, DfuPatchApplyFlags flags, GError **error)
476 {
477 DfuPatchPrivate *priv = GET_PRIVATE (self);
478 const guint8 *data_old;
479 gsize sz;
480 gsize sz_max = 0;
481 g_autofree guint8 *data_new = NULL;
482 g_autoptr(GBytes) blob_checksum_new = NULL;
483 g_autoptr(GBytes) blob_checksum = NULL;
484 g_autoptr(GBytes) blob_new = NULL;
485
486 /* not loaded yet */
487 if (priv->chunks->len == 0) {
488 g_set_error_literal (error,
489 DFU_ERROR,
490 DFU_ERROR_INVALID_DEVICE,
491 "no patches loaded");
492 return NULL;
493 }
494
495 /* get the hash of the old firmware file */
496 blob_checksum = dfu_patch_calculate_checksum (blob);
497 if ((flags & DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM) == 0 &&
498 !g_bytes_equal (blob_checksum, priv->checksum_old)) {
499 g_autofree gchar *actual = _g_bytes_to_string (blob_checksum);
500 g_autofree gchar *expect = _g_bytes_to_string (priv->checksum_old);
501 g_set_error (error,
502 DFU_ERROR,
503 DFU_ERROR_INVALID_DEVICE,
504 "checksum for source did not match, expected %s, got %s",
505 expect, actual);
506 return NULL;
507 }
508
509 /* get the size of the new image size */
510 for (guint i = 0; i < priv->chunks->len; i++) {
511 DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i);
512 gsize chunk_sz = g_bytes_get_size (chunk->blob);
513 if (chunk->off + chunk_sz > sz_max)
514 sz_max = chunk->off + chunk_sz;
515 }
516
517 /* first, copy the data buffer */
518 data_old = g_bytes_get_data (blob, &sz);
519 if (sz_max < sz) {
520 g_set_error_literal (error,
521 DFU_ERROR,
522 DFU_ERROR_INVALID_FILE,
523 "binary patch cannot truncate binary");
524 return NULL;
525 }
526 if (sz == sz_max) {
527 g_debug ("binary staying same size: %" G_GSIZE_FORMAT, sz);
528 } else {
529 g_debug ("binary growing from: %" G_GSIZE_FORMAT
530 " to %" G_GSIZE_FORMAT, sz, sz_max);
531 }
532
533 data_new = g_malloc0 (sz_max);
534 memcpy (data_new, data_old, sz_max);
535 for (guint i = 0; i < priv->chunks->len; i++) {
536 DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i);
537 const guint8 *chunk_data;
538 gsize chunk_sz;
539
540 /* bigger than the total size */
541 chunk_data = g_bytes_get_data (chunk->blob, &chunk_sz);
542 if (chunk->off + chunk_sz > sz_max) {
543 g_set_error (error,
544 DFU_ERROR,
545 DFU_ERROR_INVALID_FILE,
546 "cannot apply chunk as larger than max size");
547 return NULL;
548 }
549
550 /* apply one chunk */
551 g_debug ("applying chunk %u/%u @0x%04x (length %" G_GSIZE_FORMAT ")",
552 i + 1, priv->chunks->len, chunk->off, chunk_sz);
553 memcpy (data_new + chunk->off, chunk_data, chunk_sz);
554 }
555
556 /* check we got the desired hash */
557 blob_new = g_bytes_new (data_new, sz_max);
558 blob_checksum_new = dfu_patch_calculate_checksum (blob_new);
559 if ((flags & DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM) == 0 &&
560 !g_bytes_equal (blob_checksum_new, priv->checksum_new)) {
561 g_autofree gchar *actual = _g_bytes_to_string (blob_checksum_new);
562 g_autofree gchar *expect = _g_bytes_to_string (priv->checksum_new);
563 g_set_error (error,
564 DFU_ERROR,
565 DFU_ERROR_INVALID_DEVICE,
566 "checksum for result did not match, expected %s, got %s",
567 expect, actual);
568 return NULL;
569 }
570
571 /* success */
572 return g_steal_pointer (&blob_new);
573 }
574
575 /**
576 * dfu_patch_to_string:
577 * @self: a #DfuPatch
578 *
579 * Returns a string representaiton of the object.
580 *
581 * Return value: NULL terminated string, or %NULL for invalid
582 *
583 * Since: 0.9.6
584 **/
585 gchar *
586 dfu_patch_to_string (DfuPatch *self)
587 {
588 DfuPatchPrivate *priv = GET_PRIVATE (self);
589 GString *str = g_string_new (NULL);
590 g_autofree gchar *checksum_old = NULL;
591 g_autofree gchar *checksum_new = NULL;
592
593 g_return_val_if_fail (DFU_IS_PATCH (self), NULL);
594
595 /* add checksums */
596 checksum_old = _g_bytes_to_string (priv->checksum_old);
597 g_string_append_printf (str, "checksum-old: %s\n", checksum_old);
598 checksum_new = _g_bytes_to_string (priv->checksum_new);
599 g_string_append_printf (str, "checksum-new: %s\n", checksum_new);
600
601 /* add chunks */
602 for (guint i = 0; i < priv->chunks->len; i++) {
603 DfuPatchChunk *chunk = g_ptr_array_index (priv->chunks, i);
604 g_string_append_printf (str, "chunk #%02u 0x%04x, length %" G_GSIZE_FORMAT "\n",
605 i, chunk->off, g_bytes_get_size (chunk->blob));
606 }
607 g_string_truncate (str, str->len - 1);
608 return g_string_free (str, FALSE);
609 }
610
611 /**
612 * dfu_patch_new:
613 *
614 * Creates a new DFU patch object.
615 *
616 * Return value: a new #DfuPatch
617 *
618 * Since: 0.9.6
619 **/
620 DfuPatch *
621 dfu_patch_new (void)
622 {
623 DfuPatch *self;
624 self = g_object_new (DFU_TYPE_PATCH, NULL);
625 return self;
626 }
0 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
1 *
2 * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
3 *
4 * Licensed under the GNU Lesser General Public License Version 2.1
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #ifndef __DFU_PATCH_H
22 #define __DFU_PATCH_H
23
24 #include <glib-object.h>
25 #include <gio/gio.h>
26
27 G_BEGIN_DECLS
28
29 #define DFU_TYPE_PATCH (dfu_patch_get_type ())
30 G_DECLARE_DERIVABLE_TYPE (DfuPatch, dfu_patch, DFU, PATCH, GObject)
31
32 struct _DfuPatchClass
33 {
34 GObjectClass parent_class;
35 /*< private >*/
36 void (*_dfu_patch_reserved1) (void);
37 void (*_dfu_patch_reserved2) (void);
38 void (*_dfu_patch_reserved3) (void);
39 void (*_dfu_patch_reserved4) (void);
40 void (*_dfu_patch_reserved5) (void);
41 void (*_dfu_patch_reserved6) (void);
42 void (*_dfu_patch_reserved7) (void);
43 void (*_dfu_patch_reserved8) (void);
44 void (*_dfu_patch_reserved9) (void);
45 };
46
47 /**
48 * DfuPatchApplyFlags:
49 * @DFU_PATCH_APPLY_FLAG_NONE: No flags set
50 * @DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM: Do not check the checksum
51 *
52 * The optional flags used for applying a patch.
53 **/
54 typedef enum {
55 DFU_PATCH_APPLY_FLAG_NONE = 0,
56 DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM = (1 << 0),
57 /*< private >*/
58 DFU_PATCH_APPLY_FLAG_LAST
59 } DfuPatchApplyFlags;
60
61 DfuPatch *dfu_patch_new (void);
62
63 gchar *dfu_patch_to_string (DfuPatch *self);
64 GBytes *dfu_patch_export (DfuPatch *self,
65 GError **error);
66 gboolean dfu_patch_import (DfuPatch *self,
67 GBytes *blob,
68 GError **error);
69 gboolean dfu_patch_create (DfuPatch *self,
70 GBytes *blob1,
71 GBytes *blob2,
72 GError **error);
73 GBytes *dfu_patch_apply (DfuPatch *self,
74 GBytes *blob,
75 DfuPatchApplyFlags flags,
76 GError **error);
77 GBytes *dfu_patch_get_checksum_old (DfuPatch *self);
78 GBytes *dfu_patch_get_checksum_new (DfuPatch *self);
79
80 G_END_DECLS
81
82 #endif /* __DFU_PATCH_H */
3030 #include "dfu-device.h"
3131 #include "dfu-error.h"
3232 #include "dfu-firmware.h"
33 #include "dfu-patch.h"
3334 #include "dfu-sector-private.h"
3435 #include "dfu-target-private.h"
3536
806807 g_assert_cmpint (dfu_target_get_cipher_kind (target), ==, DFU_CIPHER_KIND_XTEA);
807808 }
808809
810 static gboolean
811 dfu_patch_create_from_strings (DfuPatch *patch,
812 const gchar *dold,
813 const gchar *dnew,
814 GError **error)
815 {
816 guint32 sz1 = strlen (dold);
817 guint32 sz2 = strlen (dnew);
818 g_autoptr(GBytes) blob1 = g_bytes_new (dold, sz1);
819 g_autoptr(GBytes) blob2 = g_bytes_new (dnew, sz2);
820 g_debug ("compare:\n%s\n%s", dold, dnew);
821 return dfu_patch_create (patch, blob1, blob2, error);
822 }
823
824 static void
825 dfu_patch_merges_func (void)
826 {
827 const guint8 *data;
828 gboolean ret;
829 gsize sz;
830 g_autoptr(DfuPatch) patch = dfu_patch_new ();
831 g_autoptr(GBytes) blob = NULL;
832 g_autoptr(GError) error = NULL;
833
834 /* check merges happen */
835 ret = dfu_patch_create_from_strings (patch, "XXX", "YXY", &error);
836 g_assert_no_error (error);
837 g_assert (ret);
838 blob = dfu_patch_export (patch, &error);
839 g_assert_no_error (error);
840 g_assert (ret);
841 data = g_bytes_get_data (blob, &sz);
842 g_assert_cmpint (data[0x00], ==, 'D');
843 g_assert_cmpint (data[0x01], ==, 'f');
844 g_assert_cmpint (data[0x02], ==, 'u');
845 g_assert_cmpint (data[0x03], ==, 'P');
846 g_assert_cmpint (data[0x04], ==, 0x00); /* reserved */
847 g_assert_cmpint (data[0x05], ==, 0x00);
848 g_assert_cmpint (data[0x06], ==, 0x00);
849 g_assert_cmpint (data[0x07], ==, 0x00);
850 g_assert_cmpint (data[0x08 + 0x28], ==, 0x00); /* chunk1, offset */
851 g_assert_cmpint (data[0x09 + 0x28], ==, 0x00);
852 g_assert_cmpint (data[0x0a + 0x28], ==, 0x00);
853 g_assert_cmpint (data[0x0b + 0x28], ==, 0x00);
854 g_assert_cmpint (data[0x0c + 0x28], ==, 0x03); /* chunk1, size */
855 g_assert_cmpint (data[0x0d + 0x28], ==, 0x00);
856 g_assert_cmpint (data[0x0e + 0x28], ==, 0x00);
857 g_assert_cmpint (data[0x0f + 0x28], ==, 0x00);
858 g_assert_cmpint (data[0x10 + 0x28], ==, 0x00); /* reserved */
859 g_assert_cmpint (data[0x11 + 0x28], ==, 0x00);
860 g_assert_cmpint (data[0x12 + 0x28], ==, 0x00);
861 g_assert_cmpint (data[0x13 + 0x28], ==, 0x00);
862 g_assert_cmpint (data[0x14 + 0x28], ==, 'Y');
863 g_assert_cmpint (data[0x15 + 0x28], ==, 'X');
864 g_assert_cmpint (data[0x16 + 0x28], ==, 'Y');
865 g_assert_cmpint (sz, ==, 48 /* hdr */ + 12 /* chunk */ + 3 /* data */);
866 }
867
868 static void
869 dfu_patch_apply_func (void)
870 {
871 gboolean ret;
872 g_autoptr(DfuPatch) patch = dfu_patch_new ();
873 g_autoptr(GBytes) blob_new2 = NULL;
874 g_autoptr(GBytes) blob_new3 = NULL;
875 g_autoptr(GBytes) blob_new4 = NULL;
876 g_autoptr(GBytes) blob_new = NULL;
877 g_autoptr(GBytes) blob_old = NULL;
878 g_autoptr(GBytes) blob_wrong = NULL;
879 g_autoptr(GError) error = NULL;
880
881 /* create a patch */
882 blob_old = g_bytes_new_static ("helloworldhelloworldhelloworldhelloworld", 40);
883 blob_new = g_bytes_new_static ("XelloXorldhelloworldhelloworldhelloworlXXX", 42);
884 ret = dfu_patch_create (patch, blob_old, blob_new, &error);
885 g_assert_no_error (error);
886 g_assert (ret);
887
888 /* apply the patch */
889 blob_new2 = dfu_patch_apply (patch, blob_old, DFU_PATCH_APPLY_FLAG_NONE, &error);
890 g_assert_no_error (error);
891 g_assert (blob_new2 != NULL);
892 g_assert_cmpint (g_bytes_compare (blob_new, blob_new2), ==, 0);
893
894 /* check we force the patch to an unrelated blob */
895 blob_wrong = g_bytes_new_static ("wrongwrongwrongwrongwrongwrongwrongwrong", 40);
896 blob_new3 = dfu_patch_apply (patch, blob_wrong, DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM, &error);
897 g_assert_no_error (error);
898 g_assert (blob_new3 != NULL);
899
900 /* check we can't apply the patch to an unrelated blob */
901 blob_new4 = dfu_patch_apply (patch, blob_wrong, DFU_PATCH_APPLY_FLAG_NONE, &error);
902 g_assert_error (error, DFU_ERROR, DFU_ERROR_INVALID_DEVICE);
903 g_assert (blob_new4 == NULL);
904 }
905
906 static void
907 dfu_patch_func (void)
908 {
909 const guint8 *data;
910 gboolean ret;
911 gsize sz;
912 g_autoptr(DfuPatch) patch = dfu_patch_new ();
913 g_autoptr(DfuPatch) patch2 = dfu_patch_new ();
914 g_autoptr(GBytes) blob = NULL;
915 g_autoptr(GError) error = NULL;
916 g_autofree gchar *serialized_str = NULL;
917
918 /* create binary diff */
919 ret = dfu_patch_create_from_strings (patch, "XXX", "XYY", &error);
920 g_assert_no_error (error);
921 g_assert (ret);
922
923 /* check we can serialize this object to a blob */
924 blob = dfu_patch_export (patch, &error);
925 g_assert_no_error (error);
926 g_assert (ret);
927 data = g_bytes_get_data (blob, &sz);
928 g_assert_cmpint (data[0x00], ==, 'D');
929 g_assert_cmpint (data[0x01], ==, 'f');
930 g_assert_cmpint (data[0x02], ==, 'u');
931 g_assert_cmpint (data[0x03], ==, 'P');
932 g_assert_cmpint (data[0x04], ==, 0x00); /* reserved */
933 g_assert_cmpint (data[0x05], ==, 0x00);
934 g_assert_cmpint (data[0x06], ==, 0x00);
935 g_assert_cmpint (data[0x07], ==, 0x00);
936 g_assert_cmpint (data[0x08 + 0x28], ==, 0x01); /* chunk1, offset */
937 g_assert_cmpint (data[0x09 + 0x28], ==, 0x00);
938 g_assert_cmpint (data[0x0a + 0x28], ==, 0x00);
939 g_assert_cmpint (data[0x0b + 0x28], ==, 0x00);
940 g_assert_cmpint (data[0x0c + 0x28], ==, 0x02); /* chunk1, size */
941 g_assert_cmpint (data[0x0d + 0x28], ==, 0x00);
942 g_assert_cmpint (data[0x0e + 0x28], ==, 0x00);
943 g_assert_cmpint (data[0x0f + 0x28], ==, 0x00);
944 g_assert_cmpint (data[0x10 + 0x28], ==, 0x00); /* reserved */
945 g_assert_cmpint (data[0x11 + 0x28], ==, 0x00);
946 g_assert_cmpint (data[0x12 + 0x28], ==, 0x00);
947 g_assert_cmpint (data[0x13 + 0x28], ==, 0x00);
948 g_assert_cmpint (data[0x14 + 0x28], ==, 'Y');
949 g_assert_cmpint (data[0x15 + 0x28], ==, 'Y');
950 g_assert_cmpint (sz, ==, 48 /* hdr */ + 12 /* chunk */ + 2 /* data */);
951
952 /* try to load it from the serialized blob */
953 ret = dfu_patch_import (patch2, blob, &error);
954 g_assert_no_error (error);
955 g_assert (ret);
956 serialized_str = dfu_patch_to_string (patch2);
957 g_debug ("serialized blob %s", serialized_str);
958 }
959
809960 int
810961 main (int argc, char **argv)
811962 {
818969 g_setenv ("G_MESSAGES_DEBUG", "all", FALSE);
819970
820971 /* tests go here */
972 g_test_add_func ("/libdfu/patch", dfu_patch_func);
973 g_test_add_func ("/libdfu/patch{merges}", dfu_patch_merges_func);
974 g_test_add_func ("/libdfu/patch{apply}", dfu_patch_apply_func);
821975 g_test_add_func ("/libdfu/enums", dfu_enums_func);
822976 g_test_add_func ("/libdfu/target(DfuSe}", dfu_target_dfuse_func);
823977 g_test_add_func ("/libdfu/firmware{raw}", dfu_firmware_raw_func);
497497 }
498498 }
499499 return cnt;
500 }
501
502 static gboolean
503 dfu_tool_patch_dump (DfuToolPrivate *priv, gchar **values, GError **error)
504 {
505 gsize sz = 0;
506 g_autofree gchar *data = NULL;
507 g_autofree gchar *str = NULL;
508 g_autoptr(DfuPatch) patch = NULL;
509 g_autoptr(GBytes) blob = NULL;
510
511 if (g_strv_length (values) != 1) {
512 g_set_error_literal (error,
513 DFU_ERROR,
514 DFU_ERROR_INTERNAL,
515 "Invalid arguments, expected FILE.bdiff");
516 return FALSE;
517 }
518
519 /* load file */
520 if (!g_file_get_contents (values[0], &data, &sz, error))
521 return FALSE;
522 blob = g_bytes_new (data, sz);
523
524 /* dump the patch to disk */
525 patch = dfu_patch_new ();
526 if (!dfu_patch_import (patch, blob, error))
527 return FALSE;
528 str = dfu_patch_to_string (patch);
529 g_print ("%s\n", str);
530
531 /* success */
532 return TRUE;
533 }
534
535 static gboolean
536 dfu_tool_patch_apply (DfuToolPrivate *priv, gchar **values, GError **error)
537 {
538 DfuPatchApplyFlags flags = DFU_PATCH_APPLY_FLAG_NONE;
539 const gchar *data_new;
540 gsize sz_diff = 0;
541 gsize sz_new = 0;
542 gsize sz_old = 0;
543 g_autofree gchar *data_diff = NULL;
544 g_autofree gchar *data_old = NULL;
545 g_autoptr(DfuPatch) patch = NULL;
546 g_autoptr(GBytes) blob_diff = NULL;
547 g_autoptr(GBytes) blob_new = NULL;
548 g_autoptr(GBytes) blob_old = NULL;
549
550 if (g_strv_length (values) != 3) {
551 g_set_error_literal (error,
552 DFU_ERROR,
553 DFU_ERROR_INTERNAL,
554 "Invalid arguments, expected OLD.bin OUT.bdiff NEW.bin");
555 return FALSE;
556 }
557
558 /* allow the user to shoot themselves in the foot */
559 if (priv->force)
560 flags |= DFU_PATCH_APPLY_FLAG_IGNORE_CHECKSUM;
561
562 if (!g_file_get_contents (values[0], &data_old, &sz_old, error))
563 return FALSE;
564 blob_old = g_bytes_new (data_old, sz_old);
565 if (!g_file_get_contents (values[1], &data_diff, &sz_diff, error))
566 return FALSE;
567 blob_diff = g_bytes_new (data_diff, sz_diff);
568 patch = dfu_patch_new ();
569 if (!dfu_patch_import (patch, blob_diff, error))
570 return FALSE;
571 blob_new = dfu_patch_apply (patch, blob_old, flags, error);
572 if (blob_new == NULL)
573 return FALSE;
574
575 /* save to disk */
576 data_new = g_bytes_get_data (blob_new, &sz_new);
577 return g_file_set_contents (values[2], data_new, sz_new, error);
578 }
579
580 static gboolean
581 dfu_tool_patch_create (DfuToolPrivate *priv, gchar **values, GError **error)
582 {
583 const gchar *data_diff;
584 gsize sz_diff = 0;
585 gsize sz_new = 0;
586 gsize sz_old = 0;
587 g_autofree gchar *data_new = NULL;
588 g_autofree gchar *data_old = NULL;
589 g_autoptr(DfuPatch) patch = NULL;
590 g_autoptr(GBytes) blob_diff = NULL;
591 g_autoptr(GBytes) blob_new = NULL;
592 g_autoptr(GBytes) blob_old = NULL;
593
594 if (g_strv_length (values) != 3) {
595 g_set_error_literal (error,
596 DFU_ERROR,
597 DFU_ERROR_INTERNAL,
598 "Invalid arguments, expected OLD.bin NEW.bin OUT.bdiff");
599 return FALSE;
600 }
601
602 /* read files */
603 if (!g_file_get_contents (values[0], &data_old, &sz_old, error))
604 return FALSE;
605 blob_old = g_bytes_new (data_old, sz_old);
606 if (!g_file_get_contents (values[1], &data_new, &sz_new, error))
607 return FALSE;
608 blob_new = g_bytes_new (data_new, sz_new);
609
610 /* create patch */
611 patch = dfu_patch_new ();
612 if (!dfu_patch_create (patch, blob_old, blob_new, error))
613 return FALSE;
614 blob_diff = dfu_patch_export (patch, error);
615 if (blob_diff == NULL)
616 return FALSE;
617
618 /* save to disk */
619 data_diff = g_bytes_get_data (blob_diff, &sz_diff);
620 return g_file_set_contents (values[2], data_diff, sz_diff, error);
500621 }
501622
502623 static gboolean
22412362 /* TRANSLATORS: command description */
22422363 _("Replace data in an existing firmware file"),
22432364 dfu_tool_replace_data);
2365 dfu_tool_add (priv->cmd_array,
2366 "patch-create",
2367 NULL,
2368 /* TRANSLATORS: command description */
2369 _("Create a binary patch using two files"),
2370 dfu_tool_patch_create);
2371 dfu_tool_add (priv->cmd_array,
2372 "patch-apply",
2373 NULL,
2374 /* TRANSLATORS: command description */
2375 _("Apply a binary patch"),
2376 dfu_tool_patch_apply);
2377 dfu_tool_add (priv->cmd_array,
2378 "patch-dump",
2379 NULL,
2380 /* TRANSLATORS: command description */
2381 _("Dump information about a binary patch to the screen"),
2382 dfu_tool_patch_dump);
22442383
22452384 /* use animated progress bar */
22462385 priv->progress_bar = dfu_progress_bar_new ();
3535 #include <libdfu/dfu-error.h>
3636 #include <libdfu/dfu-firmware.h>
3737 #include <libdfu/dfu-image.h>
38 #include <libdfu/dfu-patch.h>
3839 #include <libdfu/dfu-sector.h>
3940 #include <libdfu/dfu-target.h>
4041
0 CC=afl-gcc ./configure --disable-shared
1 AFL_HARDEN=1 make
2 afl-fuzz -m 300 -i fuzzing -o findings ./dfu-tool --force dump @@
0 Fuzzing
1 =======
2
3 CC=afl-gcc meson --default-library=static ../
4 AFL_HARDEN=1 ninja
5 afl-fuzz -m 300 -i fuzzing -o findings ./libdfu/dfu-tool --force dump @@
6 afl-fuzz -m 300 -i fuzzing-patch-dump -o findings ./libdfu/dfu-tool --force patch-dump @@
7
8 Generating
9 ----------
10
11 mkdir -p fuzzing-patch-dump
12 echo -n hello > complete-replace.old
13 echo -n XXXXX > complete-replace.new
14 ./libdfu/dfu-tool patch-create complete-replace.old complete-replace.new fuzzing-patch-dump/complete-replace.bdiff
15
16 echo -n helloworldhelloworldhelloworldhelloworld > grow-two-chunks.old
17 echo -n XelloXorldhelloworldhelloworldhelloworlXXX > grow-two-chunks.new
18 ./libdfu/dfu-tool patch-create grow-two-chunks.old grow-two-chunks.new fuzzing-patch-dump/grow-two-chunks.bdiff
1010 'dfu-error.h',
1111 'dfu-firmware.h',
1212 'dfu-image.h',
13 'dfu-patch.h',
1314 'dfu-sector.h',
1415 'dfu-target.h',
1516 ],
4748 'dfu-format-metadata.c',
4849 'dfu-format-raw.c',
4950 'dfu-image.c',
51 'dfu-patch.c',
5052 'dfu-sector.c',
5153 'dfu-target.c',
5254 ],
105107 'dfu-firmware.h',
106108 'dfu-image.c',
107109 'dfu-image.h',
110 'dfu-patch.c',
111 'dfu-patch.h',
108112 'dfu-sector.c',
109113 'dfu-sector.h',
110114 'dfu-target.c',