1 /*
2 * Copyright 2014-2017 Frank Hunleth
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "fwup_apply.h"
18 #include "util.h"
19 #include "cfgfile.h"
20
21 #include <archive.h>
22 #include <archive_entry.h>
23 #include <confuse.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <stdbool.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include "monocypher.h"
32
33 #include "requirement.h"
34 #include "functions.h"
35 #include "fatfs.h"
36 #include "mbr.h"
37 #include "fwfile.h"
38 #include "archive_open.h"
39 #include "sparse_file.h"
40 #include "progress.h"
41 #include "resources.h"
42 #include "block_cache.h"
43 #include "fwup_xdelta3.h"
44
deprecated_task_is_applicable(cfg_t * task,struct block_cache * output)45 static bool deprecated_task_is_applicable(cfg_t *task, struct block_cache *output)
46 {
47 // Handle legacy require-partition1-offset=x constraint
48 int part1_offset = cfg_getint(task, "require-partition1-offset");
49 if (part1_offset >= 0) {
50 // Try to read the MBR. This won't work if the output
51 // isn't seekable, but that's ok, since this constraint would
52 // fail anyway.
53 uint8_t buffer[FWUP_BLOCK_SIZE];
54 if (block_cache_pread(output, buffer, FWUP_BLOCK_SIZE, 0) < 0)
55 return false;
56
57 struct mbr_partition partitions[4];
58 if (mbr_decode(buffer, partitions) < 0)
59 return false;
60
61 if (partitions[1].block_offset != (uint32_t) part1_offset)
62 return false;
63 }
64
65 // all constraints pass, therefore, it's ok.
66 return true;
67 }
68
task_is_applicable(struct fun_context * fctx,cfg_t * task)69 static bool task_is_applicable(struct fun_context *fctx, cfg_t *task)
70 {
71 cfg_opt_t *reqlist = cfg_getopt(task, "reqlist");
72 if (reqlist) {
73 if (req_apply_reqlist(fctx, reqlist, req_requirement_met) < 0) {
74 // Error indicates that one or more requirements weren't met or
75 // something was messed up in the requirement. Either way, the
76 // task isn't applicable.
77 return false;
78 }
79 }
80
81 // If we get here, then it's ok to apply this task.
82 return true;
83 }
84
find_task(struct fun_context * fctx,const char * task_prefix)85 static cfg_t *find_task(struct fun_context *fctx, const char *task_prefix)
86 {
87 size_t task_len = strlen(task_prefix);
88 cfg_t *task;
89
90 int i;
91 for (i = 0; (task = cfg_getnsec(fctx->cfg, "task", i)) != NULL; i++) {
92 const char *name = cfg_title(task);
93 if (strlen(name) >= task_len &&
94 memcmp(task_prefix, name, task_len) == 0 &&
95 deprecated_task_is_applicable(task, fctx->output) &&
96 task_is_applicable(fctx, task))
97 return task;
98 }
99 return 0;
100 }
101
apply_event(struct fun_context * fctx,cfg_t * task,const char * event_type,const char * event_parameter,int (* fun)(struct fun_context * fctx))102 static int apply_event(struct fun_context *fctx, cfg_t *task, const char *event_type, const char *event_parameter, int (*fun)(struct fun_context *fctx))
103 {
104 if (event_parameter)
105 fctx->on_event = cfg_gettsec(task, event_type, event_parameter);
106 else
107 fctx->on_event = cfg_getsec(task, event_type);
108
109 if (fctx->on_event) {
110 cfg_opt_t *funlist = cfg_getopt(fctx->on_event, "funlist");
111 if (funlist) {
112 if (fun_apply_funlist(fctx, funlist, fun) < 0) {
113 fctx->on_event = NULL;
114 return -1;
115 }
116 }
117 }
118 fctx->on_event = NULL;
119 return 0;
120 }
121
122 struct fwup_apply_data
123 {
124 struct archive *a;
125 bool reading_stdin;
126
127 // Sparse file handling
128 struct sparse_file_map sfm;
129 int sparse_map_ix;
130 off_t sparse_block_offset;
131 off_t actual_offset;
132 const void *sparse_leftover;
133 off_t sparse_leftover_len;
134 };
135
read_callback_normal(struct fun_context * fctx,const void ** buffer,size_t * len,off_t * offset)136 static int read_callback_normal(struct fun_context *fctx, const void **buffer, size_t *len, off_t *offset)
137 {
138 struct fwup_apply_data *p = (struct fwup_apply_data *) fctx->cookie;
139
140 // Even though libarchive's API supports sparse files, the ZIP file format
141 // does not support them, so it can't be used. To workaround this, all of the data
142 // chunks of a sparse file are concatenated together. This function breaks them
143 // apart.
144
145 if (p->sparse_map_ix == p->sfm.map_len) {
146 // End of file
147 *len = 0;
148 *buffer = NULL;
149 *offset = 0;
150 return 0;
151 }
152 off_t sparse_file_chunk_len = p->sfm.map[p->sparse_map_ix];
153 off_t remaining_data_in_sparse_file_chunk =
154 sparse_file_chunk_len - p->sparse_block_offset;
155
156 if (p->sparse_leftover_len > 0) {
157 // Handle the case where a previous call had data remaining
158 *buffer = p->sparse_leftover;
159 *offset = p->actual_offset;
160 if (remaining_data_in_sparse_file_chunk >= p->sparse_leftover_len)
161 *len = p->sparse_leftover_len;
162 else
163 *len = remaining_data_in_sparse_file_chunk;
164
165 p->sparse_leftover += *len;
166 p->sparse_leftover_len -= *len;
167 p->actual_offset += *len;
168 p->sparse_block_offset += *len;
169 if (p->sparse_block_offset == sparse_file_chunk_len) {
170 // Advance over hole (unless this is the end)
171 p->sparse_map_ix++;
172 p->sparse_block_offset = 0;
173 if (p->sparse_map_ix != p->sfm.map_len) {
174 p->actual_offset += p->sfm.map[p->sparse_map_ix];
175
176 // Advance to next data block
177 p->sparse_map_ix++;
178 }
179 }
180 return 0;
181 }
182
183 // Decompress more data
184 int64_t ignored;
185 int rc = fwup_archive_read_data_block(p->a, buffer, len, &ignored);
186
187 if (rc == ARCHIVE_EOF) {
188 *len = 0;
189 *buffer = NULL;
190 *offset = 0;
191 return 0;
192 } else if (rc != ARCHIVE_OK)
193 ERR_RETURN(archive_error_string(p->a));
194
195 *offset = p->actual_offset;
196
197 if (remaining_data_in_sparse_file_chunk > (off_t) *len) {
198 // The amount decompressed doesn't cross a sparse file hole
199 p->actual_offset += *len;
200 p->sparse_block_offset += *len;
201 } else {
202 // The amount decompressed crosses a hole in a sparse file,
203 // so return the contiguous chunk and save the leftovers.
204 p->actual_offset += remaining_data_in_sparse_file_chunk;
205 p->sparse_leftover_len = *len - remaining_data_in_sparse_file_chunk;
206 p->sparse_leftover = *buffer + remaining_data_in_sparse_file_chunk;
207
208 *len = remaining_data_in_sparse_file_chunk;
209
210 // Advance over hole (unless this is the end)
211 p->sparse_map_ix++;
212 p->sparse_block_offset = 0;
213 if (p->sparse_map_ix != p->sfm.map_len) {
214 p->actual_offset += p->sfm.map[p->sparse_map_ix];
215
216 // Advance to next data block
217 p->sparse_map_ix++;
218 }
219 }
220
221 return 0;
222 }
223
xdelta_read_patch_callback(void * cookie,const void ** buffer,size_t * len)224 static int xdelta_read_patch_callback(void* cookie, const void **buffer, size_t *len)
225 {
226 struct fun_context *fctx = (struct fun_context *) cookie;
227 struct fwup_apply_data *p = (struct fwup_apply_data *) fctx->cookie;
228
229 int64_t ignored;
230 int rc = fwup_archive_read_data_block(p->a, buffer, len, &ignored);
231
232 if (rc == ARCHIVE_OK) {
233 return 0;
234 } else if (rc == ARCHIVE_EOF) {
235 *len = 0;
236 *buffer = NULL;
237 return 0;
238 } else {
239 *len = 0;
240 *buffer = NULL;
241 ERR_RETURN(archive_error_string(p->a));
242 }
243 }
244
xdelta_read_source_callback(void * cookie,void * buf,size_t count,off_t offset)245 static int xdelta_read_source_callback(void *cookie, void *buf, size_t count, off_t offset)
246 {
247 struct fun_context *fctx = (struct fun_context *) cookie;
248
249 if (offset < 0 ||
250 offset + count > fctx->xd_source_count)
251 ERR_RETURN("xdelta tried to load outside of allowed byte range (0-%" PRId64 "): offset: %" PRId64 ", count: %d", fctx->xd_source_count, offset, count);
252
253 return block_cache_pread(fctx->output, buf, count, fctx->xd_source_offset + offset);
254 }
255
read_callback_xdelta(struct fun_context * fctx,const void ** buffer,size_t * len,off_t * offset)256 static int read_callback_xdelta(struct fun_context *fctx, const void **buffer, size_t *len, off_t *offset)
257 {
258 struct fwup_apply_data *p = (struct fwup_apply_data *) fctx->cookie;
259
260 // This would be ridiculous...
261 if (p->sfm.map_len != 1)
262 ERR_RETURN("Sparse xdelta not supported");
263
264 OK_OR_RETURN(xdelta_read(fctx->xd, buffer, len));
265
266 // xdelta_read's output has no holes
267 *offset = p->actual_offset;
268 p->actual_offset += *len;
269
270 return 0;
271 }
272
read_callback(struct fun_context * fctx,const void ** buffer,size_t * len,off_t * offset)273 static int read_callback(struct fun_context *fctx, const void **buffer, size_t *len, off_t *offset)
274 {
275 if (fctx->xd)
276 return read_callback_xdelta(fctx, buffer, len, offset);
277 else
278 return read_callback_normal(fctx, buffer, len, offset);
279 }
280
initialize_timestamps()281 static void initialize_timestamps()
282 {
283 // The purpose of this function is to set all timestamps that we create
284 // (e.g., FATFS timestamps) to a fixed date. This is needed
285 // to make sure that the images that we create are bit-for-bit identical.
286
287 // Set the timestamp to FAT time 0
288 struct tm tmp;
289 tmp.tm_year = 80;
290 tmp.tm_mon = 0;
291 tmp.tm_mday = 1;
292 tmp.tm_hour = 0;
293 tmp.tm_min = 0;
294 tmp.tm_sec = 0;
295
296 fatfs_set_time(&tmp);
297 }
298
compute_progress(struct fun_context * fctx)299 static int compute_progress(struct fun_context *fctx)
300 {
301 fctx->type = FUN_CONTEXT_INIT;
302 OK_OR_RETURN(apply_event(fctx, fctx->task, "on-init", NULL, fun_compute_progress));
303
304 fctx->type = FUN_CONTEXT_FILE;
305 cfg_t *sec;
306 int i = 0;
307 while ((sec = cfg_getnsec(fctx->task, "on-resource", i++)) != NULL) {
308 cfg_t *resource = cfg_gettsec(fctx->cfg, "file-resource", sec->title);
309 if (!resource) {
310 // This really shouldn't happen, but failing to calculate
311 // progress for a missing file-resource seems harsh.
312 INFO("Can't find file-resource for %s", sec->title);
313 continue;
314 }
315
316 OK_OR_RETURN(apply_event(fctx, fctx->task, "on-resource", sec->title, fun_compute_progress));
317 }
318
319 fctx->type = FUN_CONTEXT_FINISH;
320 OK_OR_RETURN(apply_event(fctx, fctx->task, "on-finish", NULL, fun_compute_progress));
321
322 return 0;
323 }
324
run_task(struct fun_context * fctx,struct fwup_apply_data * pd)325 static int run_task(struct fun_context *fctx, struct fwup_apply_data *pd)
326 {
327 int rc = 0;
328
329 struct resource_list *resources = NULL;
330 OK_OR_CLEANUP(rlist_get_from_task(fctx->cfg, fctx->task, &resources));
331
332 fctx->type = FUN_CONTEXT_INIT;
333 OK_OR_CLEANUP(apply_event(fctx, fctx->task, "on-init", NULL, fun_run));
334
335 fctx->type = FUN_CONTEXT_FILE;
336 fctx->read = read_callback;
337 struct archive_entry *ae;
338 while (archive_read_next_header(pd->a, &ae) == ARCHIVE_OK) {
339 const char *filename = archive_entry_pathname(ae);
340 char resource_name[FWFILE_MAX_ARCHIVE_PATH];
341
342 OK_OR_CLEANUP(archive_filename_to_resource(filename, resource_name, sizeof(resource_name)));
343
344 // Skip an empty filename. This is easy to get when you run 'zip'
345 // on the command line to create a firmware update file and include
346 // the 'data' directory. It's annoying when it creates an error
347 // (usually when debugging something else), so ignore it.
348 if (resource_name[0] == '\0')
349 continue;
350
351 // See if this resource is used by this task
352 struct resource_list *item = rlist_find_by_name(resources, resource_name);
353 if (item == NULL)
354 continue;
355
356 // See if there's metadata associated with this resource
357 if (item->resource == NULL)
358 ERR_CLEANUP_MSG("Resource '%s' used, but metadata is missing. Archive is corrupt.", resource_name);
359
360 OK_OR_CLEANUP(sparse_file_get_map_from_resource(item->resource, &pd->sfm));
361 pd->sparse_map_ix = 0;
362 pd->sparse_block_offset = 0;
363 pd->actual_offset = 0;
364 pd->sparse_leftover = NULL;
365 pd->sparse_leftover_len = 0;
366 if (pd->sfm.map[0] == 0) {
367 if (pd->sfm.map_len > 2) {
368 // This is the case where there's a hole at the beginning. Advance to
369 // the offset of the data.
370 pd->sparse_map_ix = 2;
371 pd->actual_offset = pd->sfm.map[1];
372 } else {
373 // sparse map has a 0 length data block and possibly a hole,
374 // but it doesn't have another data block. This means that it's
375 // either a 0-length file or it's all sparse. Signal EOF. This
376 // might be a bug, but I can't think of a real use case for a completely
377 // sparse file.
378 pd->sparse_map_ix = pd->sfm.map_len;
379 }
380 }
381
382 // MOVE ME!!!
383 {
384 cfg_t *on_resource = cfg_gettsec(fctx->task, "on-resource", resource_name);
385 if (on_resource) {
386 off_t size_in_archive = archive_entry_size(ae);
387 off_t expected_size_in_archive = sparse_file_data_size(&pd->sfm);
388
389 if (pd->sfm.map_len == 1 && archive_entry_size_is_set(ae) && size_in_archive != expected_size_in_archive) {
390 const char *source_raw_offset_str = cfg_getstr(on_resource, "delta-source-raw-offset");
391 int source_raw_count = cfg_getint(on_resource, "delta-source-raw-count");
392 if (source_raw_count > 0 && source_raw_offset_str != NULL) {
393 off_t source_raw_offset = strtoul(source_raw_offset_str, NULL, 0);
394
395 fctx->xd = malloc(sizeof(struct xdelta_state));
396 xdelta_init(fctx->xd, xdelta_read_patch_callback, xdelta_read_source_callback, fctx);
397 fctx->xd_source_offset = source_raw_offset * FWUP_BLOCK_SIZE;
398 fctx->xd_source_count = source_raw_count * FWUP_BLOCK_SIZE;
399 } else {
400 ERR_CLEANUP_MSG("File '%s' isn't expected size (%d vs %d) and xdelta3 patch support not enabled on it. (Add delta-source-raw-offset or delta-source-raw-count at least)", resource_name, (int) size_in_archive, (int) expected_size_in_archive);
401 }
402 }
403 }
404 }
405 OK_OR_CLEANUP(apply_event(fctx, fctx->task, "on-resource", resource_name, fun_run));
406
407 item->processed = true;
408 sparse_file_free(&pd->sfm);
409
410 { // MOVE ME!!!
411 if (fctx->xd) {
412 xdelta_free(fctx->xd);
413 free(fctx->xd);
414 fctx->xd = NULL;
415 }
416 }
417 }
418
419 // Make sure that all "on-resource" blocks have been run.
420 for (const struct resource_list *r = resources; r != NULL; r = r->next) {
421 if (!r->processed)
422 ERR_CLEANUP_MSG("Resource %s not found in archive", cfg_title(r->resource));
423 }
424
425 // Flush any fatfs filesystem updates before the on-finish. The on-finish
426 // generally has A/B partition swaps or other critical operations that
427 // assume the prior data has already been written. The block_cache will
428 // write output in the right order, but fatfs's caching will not so it
429 // needs to be flushed here.
430 fatfs_closefs();
431
432 fctx->type = FUN_CONTEXT_FINISH;
433 OK_OR_CLEANUP(apply_event(fctx, fctx->task, "on-finish", NULL, fun_run));
434
435 cleanup:
436 if (rc != 0) {
437 // Do a best attempt at running any error handling code
438 fctx->type = FUN_CONTEXT_ERROR;
439
440 // Since there's an error, throw out anything that's in the cache so
441 // that the error handler can start fresh.
442 fatfs_closefs();
443 block_cache_reset(fctx->output);
444
445 if (apply_event(fctx, fctx->task, "on-error", NULL, fun_run) < 0) {
446 // Yet another error so throw out the cache again.
447 fatfs_closefs();
448 block_cache_reset(fctx->output);
449 }
450 }
451 rlist_free(resources);
452 return rc;
453 }
454
fwup_apply(const char * fw_filename,const char * task_prefix,int output_fd,off_t end_offset,struct fwup_progress * progress,unsigned char * const * public_keys,bool enable_trim,bool verify_writes)455 int fwup_apply(const char *fw_filename,
456 const char *task_prefix,
457 int output_fd,
458 off_t end_offset,
459 struct fwup_progress *progress,
460 unsigned char *const*public_keys,
461 bool enable_trim,
462 bool verify_writes)
463 {
464 int rc = 0;
465 unsigned char *meta_conf_signature = NULL;
466 struct fun_context fctx;
467 memset(&fctx, 0, sizeof(fctx));
468 fctx.progress = progress;
469
470 // Report 0 progress before doing anything
471 progress_report(fctx.progress, 0);
472
473 struct fwup_apply_data pd;
474 memset(&pd, 0, sizeof(pd));
475 fctx.cookie = &pd;
476 pd.a = archive_read_new();
477
478 archive_read_support_format_zip(pd.a);
479 int arc = fwup_archive_open_filename(pd.a, fw_filename, progress);
480 if (arc != ARCHIVE_OK)
481 ERR_CLEANUP_MSG("%s", archive_error_string(pd.a));
482
483 struct archive_entry *ae;
484 arc = archive_read_next_header(pd.a, &ae);
485 if (arc != ARCHIVE_OK)
486 ERR_CLEANUP_MSG("%s", archive_error_string(pd.a));
487
488 if (strcmp(archive_entry_pathname(ae), "meta.conf.ed25519") == 0) {
489 off_t total_size;
490 if (archive_read_all_data(pd.a, ae, (char **) &meta_conf_signature, FWUP_SIGNATURE_LEN, &total_size) < 0)
491 ERR_CLEANUP_MSG("Error reading meta.conf.ed25519 from archive.\n"
492 "Check for file corruption or libarchive built without zlib support");
493
494 if (total_size != FWUP_SIGNATURE_LEN)
495 ERR_CLEANUP_MSG("Unexpected meta.conf.ed25519 size: %d", total_size);
496
497 arc = archive_read_next_header(pd.a, &ae);
498 if (arc != ARCHIVE_OK)
499 ERR_CLEANUP_MSG("Expecting more than meta.conf.ed25519 in archive");
500 }
501 if (strcmp(archive_entry_pathname(ae), "meta.conf") != 0)
502 ERR_CLEANUP_MSG("Expecting meta.conf to be at the beginning of %s", fw_filename);
503
504 OK_OR_CLEANUP(cfgfile_parse_fw_ae(pd.a, ae, &fctx.cfg, meta_conf_signature, public_keys));
505
506 initialize_timestamps();
507
508 // Initialize the output. Nothing should have been written before now
509 // and waiting to initialize the output until now forces the point.
510 fctx.output = (struct block_cache *) malloc(sizeof(struct block_cache));
511 OK_OR_CLEANUP(block_cache_init(fctx.output, output_fd, end_offset, enable_trim, verify_writes));
512
513 // Go through all of the tasks and find a matcher
514 fctx.task = find_task(&fctx, task_prefix);
515 if (fctx.task == 0)
516 ERR_CLEANUP_MSG("Couldn't find applicable task '%s'. If task is available, the task's requirements may not be met.", task_prefix);
517
518 // Compute the total progress units
519 OK_OR_CLEANUP(compute_progress(&fctx));
520
521 // Run
522 OK_OR_CLEANUP(run_task(&fctx, &pd));
523
524 // Flush everything
525 fatfs_closefs();
526 OK_OR_CLEANUP(block_cache_flush(fctx.output));
527
528 // Close everything before reporting 100% just in case the OS blocks on the close call.
529 block_cache_free(fctx.output);
530 free(fctx.output);
531 fctx.output = NULL;
532 close(output_fd);
533
534 // Success -> report 100%
535 progress_report_complete(fctx.progress);
536
537 cleanup:
538 // Close the output
539 if (fctx.output) {
540 // In the error case, flush in case the on-error
541 // handler left something. Errors in on-error are
542 // handled with the on-error call.
543 fatfs_closefs();
544 block_cache_flush(fctx.output); // Ignore errors
545
546 block_cache_free(fctx.output);
547 free(fctx.output);
548 fctx.output = NULL;
549 close(output_fd);
550 }
551
552 sparse_file_free(&pd.sfm);
553
554 archive_read_free(pd.a);
555
556 if (meta_conf_signature)
557 free(meta_conf_signature);
558
559 if (fctx.cfg) {
560 cfg_free(fctx.cfg);
561 fctx.cfg = NULL;
562 }
563
564 return rc;
565 }
566