fwtool: do not omit final 16 byte when image does not contain signature
[oweals/openwrt.git] / package / system / fwtool / src / fwtool.c
1 /*
2  * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2
6  * as published by the Free Software Foundation
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13 #include <sys/types.h>
14 #include <stdio.h>
15 #include <getopt.h>
16 #include <stdbool.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #include "fwimage.h"
22 #include "utils.h"
23 #include "crc32.h"
24
25 #define METADATA_MAXLEN         30 * 1024
26 #define SIGNATURE_MAXLEN        1 * 1024
27
28 #define BUFLEN                  (METADATA_MAXLEN + SIGNATURE_MAXLEN + 1024)
29
30 enum {
31         MODE_DEFAULT = -1,
32         MODE_EXTRACT = 0,
33         MODE_APPEND = 1,
34 };
35
36 struct data_buf {
37         char *cur;
38         char *prev;
39         int cur_len;
40         int file_len;
41 };
42
43 static FILE *signature_file, *metadata_file, *firmware_file;
44 static int file_mode = MODE_DEFAULT;
45 static bool truncate_file;
46 static bool write_truncated;
47 static bool quiet = false;
48
49 static uint32_t crc_table[256];
50
51 #define msg(...)                                        \
52         do {                                            \
53                 if (!quiet)                             \
54                         fprintf(stderr, __VA_ARGS__);   \
55         } while (0)
56
57 static int
58 usage(const char *progname)
59 {
60         fprintf(stderr, "Usage: %s <options> <firmware>\n"
61                 "\n"
62                 "Options:\n"
63                 "  -S <file>:           Append signature file to firmware image\n"
64                 "  -I <file>:           Append metadata file to firmware image\n"
65                 "  -s <file>:           Extract signature file from firmware image\n"
66                 "  -i <file>:           Extract metadata file from firmware image\n"
67                 "  -t:                  Remove extracted chunks from firmare image (using -s, -i)\n"
68                 "  -T:                  Output firmware image without extracted chunks to stdout (using -s, -i)\n"
69                 "  -q:                  Quiet (suppress error messages)\n"
70                 "\n", progname);
71         return 1;
72 }
73
74 static FILE *
75 open_file(const char *name, bool write)
76 {
77         FILE *ret;
78
79         if (!strcmp(name, "-"))
80                 return write ? stdout : stdin;
81
82         ret = fopen(name, write ? "w" : "r+");
83         if (!ret && !write)
84                 ret = fopen(name, "r");
85
86         return ret;
87 }
88
89 static int
90 set_file(FILE **file, const char *name, int mode)
91 {
92         if (file_mode < 0)
93                 file_mode = mode;
94         else if (file_mode != mode) {
95                 msg("Error: mixing appending and extracting data is not supported\n");
96                 return 1;
97         }
98
99         if (*file) {
100                 msg("Error: the same append/extract option cannot be used multiple times\n");
101                 return 1;
102         }
103
104         *file = open_file(name, mode == MODE_EXTRACT);
105         return !*file;
106 }
107
108 static void
109 trailer_update_crc(struct fwimage_trailer *tr, void *buf, int len)
110 {
111         tr->crc32 = cpu_to_be32(crc32_block(be32_to_cpu(tr->crc32), buf, len, crc_table));
112 }
113
114 static int
115 append_data(FILE *in, FILE *out, struct fwimage_trailer *tr, int maxlen)
116 {
117         while (1) {
118                 char buf[512];
119                 int len;
120
121                 len = fread(buf, 1, sizeof(buf), in);
122                 if (!len)
123                         break;
124
125                 maxlen -= len;
126                 if (maxlen < 0)
127                         return 1;
128
129                 tr->size += len;
130                 trailer_update_crc(tr, buf, len);
131                 fwrite(buf, len, 1, out);
132         }
133
134         return 0;
135 }
136
137 static void
138 append_trailer(FILE *out, struct fwimage_trailer *tr)
139 {
140         tr->size = cpu_to_be32(tr->size);
141         fwrite(tr, sizeof(*tr), 1, out);
142         trailer_update_crc(tr, tr, sizeof(*tr));
143 }
144
145 static int
146 add_metadata(struct fwimage_trailer *tr)
147 {
148         struct fwimage_header hdr = {};
149
150         tr->type = FWIMAGE_INFO;
151         tr->size = sizeof(hdr) + sizeof(*tr);
152
153         trailer_update_crc(tr, &hdr, sizeof(hdr));
154         fwrite(&hdr, sizeof(hdr), 1, firmware_file);
155
156         if (append_data(metadata_file, firmware_file, tr, METADATA_MAXLEN))
157                 return 1;
158
159         append_trailer(firmware_file, tr);
160
161         return 0;
162 }
163
164 static int
165 add_signature(struct fwimage_trailer *tr)
166 {
167         if (!signature_file)
168                 return 0;
169
170         tr->type = FWIMAGE_SIGNATURE;
171         tr->size = sizeof(*tr);
172
173         if (append_data(signature_file, firmware_file, tr, SIGNATURE_MAXLEN))
174                 return 1;
175
176         append_trailer(firmware_file, tr);
177
178         return 0;
179 }
180
181 static int
182 add_data(const char *name)
183 {
184         struct fwimage_trailer tr = {
185                 .magic = cpu_to_be32(FWIMAGE_MAGIC),
186                 .crc32 = ~0,
187         };
188         int file_len = 0;
189         int ret = 0;
190
191         firmware_file = fopen(name, "r+");
192         if (!firmware_file) {
193                 msg("Failed to open firmware file\n");
194                 return 1;
195         }
196
197         while (1) {
198                 char buf[512];
199                 int len;
200
201                 len = fread(buf, 1, sizeof(buf), firmware_file);
202                 if (!len)
203                         break;
204
205                 file_len += len;
206                 trailer_update_crc(&tr, buf, len);
207         }
208
209         if (metadata_file)
210                 ret = add_metadata(&tr);
211         else if (signature_file)
212                 ret = add_signature(&tr);
213
214         if (ret) {
215                 fflush(firmware_file);
216                 ftruncate(fileno(firmware_file), file_len);
217         }
218
219         return ret;
220 }
221
222 static void
223 remove_tail(struct data_buf *dbuf, int len)
224 {
225         dbuf->cur_len -= len;
226         dbuf->file_len -= len;
227
228         if (dbuf->cur_len)
229                 return;
230
231         free(dbuf->cur);
232         dbuf->cur = dbuf->prev;
233         dbuf->prev = NULL;
234         dbuf->cur_len = BUFLEN;
235 }
236
237 static int
238 extract_tail(struct data_buf *dbuf, void *dest, int len)
239 {
240         int cur_len = dbuf->cur_len;
241
242         if (!dbuf->cur)
243                 return 1;
244
245         if (cur_len >= len)
246                 cur_len = len;
247
248         memcpy(dest + (len - cur_len), dbuf->cur + dbuf->cur_len - cur_len, cur_len);
249         remove_tail(dbuf, cur_len);
250
251         cur_len = len - cur_len;
252         if (cur_len && !dbuf->cur)
253                 return 1;
254
255         memcpy(dest, dbuf->cur + dbuf->cur_len - cur_len, cur_len);
256         remove_tail(dbuf, cur_len);
257
258         return 0;
259 }
260
261 static uint32_t
262 tail_crc32(struct data_buf *dbuf, uint32_t crc32)
263 {
264         if (dbuf->prev)
265                 crc32 = crc32_block(crc32, dbuf->prev, BUFLEN, crc_table);
266
267         return crc32_block(crc32, dbuf->cur, dbuf->cur_len, crc_table);
268 }
269
270 static int
271 validate_metadata(struct fwimage_header *hdr, int data_len)
272 {
273          if (hdr->version != 0)
274                  return 1;
275          return 0;
276 }
277
278 static int
279 extract_data(const char *name)
280 {
281         struct fwimage_header *hdr;
282         struct fwimage_trailer tr;
283         struct data_buf dbuf = {};
284         uint32_t crc32 = ~0;
285         int data_len = 0;
286         int ret = 1;
287         void *buf;
288         bool metadata_keep = false;
289
290         firmware_file = open_file(name, false);
291         if (!firmware_file) {
292                 msg("Failed to open firmware file\n");
293                 return 1;
294         }
295
296         if (truncate_file && firmware_file == stdin) {
297                 msg("Cannot truncate file when reading from stdin\n");
298                 return 1;
299         }
300
301         buf = malloc(BUFLEN);
302         if (!buf)
303                 return 1;
304
305         do {
306                 char *tmp = dbuf.cur;
307
308                 if (write_truncated && dbuf.prev)
309                         fwrite(dbuf.prev, 1, BUFLEN, stdout);
310
311                 dbuf.cur = dbuf.prev;
312                 dbuf.prev = tmp;
313
314                 if (dbuf.cur)
315                         crc32 = crc32_block(crc32, dbuf.cur, BUFLEN, crc_table);
316                 else
317                         dbuf.cur = malloc(BUFLEN);
318
319                 if (!dbuf.cur)
320                         goto out;
321
322                 dbuf.cur_len = fread(dbuf.cur, 1, BUFLEN, firmware_file);
323                 dbuf.file_len += dbuf.cur_len;
324         } while (dbuf.cur_len == BUFLEN);
325
326         while (1) {
327
328                 if (extract_tail(&dbuf, &tr, sizeof(tr)))
329                         break;
330
331                 if (tr.magic != cpu_to_be32(FWIMAGE_MAGIC)) {
332                         msg("Data not found\n");
333                         metadata_keep = true;
334                         break;
335                 }
336
337                 data_len = be32_to_cpu(tr.size) - sizeof(tr);
338
339                 if (be32_to_cpu(tr.crc32) != tail_crc32(&dbuf, crc32)) {
340                         msg("CRC error\n");
341                         break;
342                 }
343
344                 if (data_len > BUFLEN) {
345                         msg("Size error\n");
346                         break;
347                 }
348
349                 extract_tail(&dbuf, buf, data_len);
350
351                 if (tr.type == FWIMAGE_SIGNATURE) {
352                         if (!signature_file)
353                                 continue;
354                         fwrite(buf, data_len, 1, signature_file);
355                         ret = 0;
356                         break;
357                 } else if (tr.type == FWIMAGE_INFO) {
358                         if (!metadata_file) {
359                                 dbuf.file_len += data_len + sizeof(tr);
360                                 metadata_keep = true;
361                                 break;
362                         }
363
364                         hdr = buf;
365                         data_len -= sizeof(*hdr);
366                         if (validate_metadata(hdr, data_len))
367                                 continue;
368
369                         fwrite(hdr + 1, data_len, 1, metadata_file);
370                         ret = 0;
371                         break;
372                 } else {
373                         continue;
374                 }
375         }
376
377         if (!ret && truncate_file)
378                 ftruncate(fileno(firmware_file), dbuf.file_len);
379
380         if (write_truncated) {
381                 if (dbuf.prev)
382                         fwrite(dbuf.prev, 1, BUFLEN, stdout);
383                 if (dbuf.cur)
384                         fwrite(dbuf.cur, 1, dbuf.cur_len, stdout);
385                 if (metadata_keep) {
386                         fwrite(buf, data_len, 1, stdout);
387                         fwrite(&tr, sizeof(tr), 1, stdout);
388                 }
389         }
390
391 out:
392         free(buf);
393         free(dbuf.cur);
394         free(dbuf.prev);
395         return ret;
396 }
397
398 static void cleanup(void)
399 {
400         if (signature_file)
401                 fclose(signature_file);
402         if (metadata_file)
403                 fclose(metadata_file);
404         if (firmware_file)
405                 fclose(firmware_file);
406 }
407
408 int main(int argc, char **argv)
409 {
410         const char *progname = argv[0];
411         int ret, ch;
412
413         crc32_filltable(crc_table);
414
415         while ((ch = getopt(argc, argv, "i:I:qs:S:tT")) != -1) {
416                 ret = 0;
417                 switch(ch) {
418                 case 'S':
419                         ret = set_file(&signature_file, optarg, MODE_APPEND);
420                         break;
421                 case 'I':
422                         ret = set_file(&metadata_file, optarg, MODE_APPEND);
423                         break;
424                 case 's':
425                         ret = set_file(&signature_file, optarg, MODE_EXTRACT);
426                         break;
427                 case 'i':
428                         ret = set_file(&metadata_file, optarg, MODE_EXTRACT);
429                         break;
430                 case 't':
431                         truncate_file = true;
432                         break;
433                 case 'T':
434                         write_truncated = true;
435                         break;
436                 case 'q':
437                         quiet = true;
438                         break;
439                 }
440
441                 if (ret)
442                         goto out;
443         }
444
445         if (optind >= argc) {
446                 ret = usage(progname);
447                 goto out;
448         }
449
450         if (file_mode == MODE_DEFAULT) {
451                 ret = usage(progname);
452                 goto out;
453         }
454
455         if (signature_file && metadata_file) {
456                 msg("Cannot append/extract metadata and signature in one run\n");
457                 return 1;
458         }
459
460         if (file_mode)
461                 ret = add_data(argv[optind]);
462         else
463                 ret = extract_data(argv[optind]);
464
465 out:
466         cleanup();
467         return ret;
468 }