mirror of
https://github.com/openwrt/openwrt.git
synced 2025-02-15 15:12:17 +00:00
The fwutil command will interpret the final 16 byte of a given firmware image files as "struct fwimage_trailer". In case these bytes do look like a valid trailer, we must ensure that we print them out along with the remainder of the image to not accidentally truncate non-trailer-images by 16 bytes when they're piped through fwtool, e.g. as part of an image verification command sequence. Some command sequences pipe images through fwtool in order to strip any possible metadata, certificate or signature trailers and do not expect bare images without any of that metadata to get truncated as other non- fwtool specific metadata is expected at the end of the file, e.g. an information block with an md5sum in case of the combined image format. Signed-off-by: Jo-Philipp Wich <jo@mein.io> (cherry picked from commit 889b841048c5eb7f975135cab363f1fdd9b6cfa1)
469 lines
8.8 KiB
C
469 lines
8.8 KiB
C
/*
|
|
* Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fwimage.h"
|
|
#include "utils.h"
|
|
#include "crc32.h"
|
|
|
|
#define METADATA_MAXLEN 30 * 1024
|
|
#define SIGNATURE_MAXLEN 1 * 1024
|
|
|
|
#define BUFLEN (METADATA_MAXLEN + SIGNATURE_MAXLEN + 1024)
|
|
|
|
enum {
|
|
MODE_DEFAULT = -1,
|
|
MODE_EXTRACT = 0,
|
|
MODE_APPEND = 1,
|
|
};
|
|
|
|
struct data_buf {
|
|
char *cur;
|
|
char *prev;
|
|
int cur_len;
|
|
int file_len;
|
|
};
|
|
|
|
static FILE *signature_file, *metadata_file, *firmware_file;
|
|
static int file_mode = MODE_DEFAULT;
|
|
static bool truncate_file;
|
|
static bool write_truncated;
|
|
static bool quiet = false;
|
|
|
|
static uint32_t crc_table[256];
|
|
|
|
#define msg(...) \
|
|
do { \
|
|
if (!quiet) \
|
|
fprintf(stderr, __VA_ARGS__); \
|
|
} while (0)
|
|
|
|
static int
|
|
usage(const char *progname)
|
|
{
|
|
fprintf(stderr, "Usage: %s <options> <firmware>\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" -S <file>: Append signature file to firmware image\n"
|
|
" -I <file>: Append metadata file to firmware image\n"
|
|
" -s <file>: Extract signature file from firmware image\n"
|
|
" -i <file>: Extract metadata file from firmware image\n"
|
|
" -t: Remove extracted chunks from firmare image (using -s, -i)\n"
|
|
" -T: Output firmware image without extracted chunks to stdout (using -s, -i)\n"
|
|
" -q: Quiet (suppress error messages)\n"
|
|
"\n", progname);
|
|
return 1;
|
|
}
|
|
|
|
static FILE *
|
|
open_file(const char *name, bool write)
|
|
{
|
|
FILE *ret;
|
|
|
|
if (!strcmp(name, "-"))
|
|
return write ? stdout : stdin;
|
|
|
|
ret = fopen(name, write ? "w" : "r+");
|
|
if (!ret && !write)
|
|
ret = fopen(name, "r");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
set_file(FILE **file, const char *name, int mode)
|
|
{
|
|
if (file_mode < 0)
|
|
file_mode = mode;
|
|
else if (file_mode != mode) {
|
|
msg("Error: mixing appending and extracting data is not supported\n");
|
|
return 1;
|
|
}
|
|
|
|
if (*file) {
|
|
msg("Error: the same append/extract option cannot be used multiple times\n");
|
|
return 1;
|
|
}
|
|
|
|
*file = open_file(name, mode == MODE_EXTRACT);
|
|
return !*file;
|
|
}
|
|
|
|
static void
|
|
trailer_update_crc(struct fwimage_trailer *tr, void *buf, int len)
|
|
{
|
|
tr->crc32 = cpu_to_be32(crc32_block(be32_to_cpu(tr->crc32), buf, len, crc_table));
|
|
}
|
|
|
|
static int
|
|
append_data(FILE *in, FILE *out, struct fwimage_trailer *tr, int maxlen)
|
|
{
|
|
while (1) {
|
|
char buf[512];
|
|
int len;
|
|
|
|
len = fread(buf, 1, sizeof(buf), in);
|
|
if (!len)
|
|
break;
|
|
|
|
maxlen -= len;
|
|
if (maxlen < 0)
|
|
return 1;
|
|
|
|
tr->size += len;
|
|
trailer_update_crc(tr, buf, len);
|
|
fwrite(buf, len, 1, out);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
append_trailer(FILE *out, struct fwimage_trailer *tr)
|
|
{
|
|
tr->size = cpu_to_be32(tr->size);
|
|
fwrite(tr, sizeof(*tr), 1, out);
|
|
trailer_update_crc(tr, tr, sizeof(*tr));
|
|
}
|
|
|
|
static int
|
|
add_metadata(struct fwimage_trailer *tr)
|
|
{
|
|
struct fwimage_header hdr = {};
|
|
|
|
tr->type = FWIMAGE_INFO;
|
|
tr->size = sizeof(hdr) + sizeof(*tr);
|
|
|
|
trailer_update_crc(tr, &hdr, sizeof(hdr));
|
|
fwrite(&hdr, sizeof(hdr), 1, firmware_file);
|
|
|
|
if (append_data(metadata_file, firmware_file, tr, METADATA_MAXLEN))
|
|
return 1;
|
|
|
|
append_trailer(firmware_file, tr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
add_signature(struct fwimage_trailer *tr)
|
|
{
|
|
if (!signature_file)
|
|
return 0;
|
|
|
|
tr->type = FWIMAGE_SIGNATURE;
|
|
tr->size = sizeof(*tr);
|
|
|
|
if (append_data(signature_file, firmware_file, tr, SIGNATURE_MAXLEN))
|
|
return 1;
|
|
|
|
append_trailer(firmware_file, tr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
add_data(const char *name)
|
|
{
|
|
struct fwimage_trailer tr = {
|
|
.magic = cpu_to_be32(FWIMAGE_MAGIC),
|
|
.crc32 = ~0,
|
|
};
|
|
int file_len = 0;
|
|
int ret = 0;
|
|
|
|
firmware_file = fopen(name, "r+");
|
|
if (!firmware_file) {
|
|
msg("Failed to open firmware file\n");
|
|
return 1;
|
|
}
|
|
|
|
while (1) {
|
|
char buf[512];
|
|
int len;
|
|
|
|
len = fread(buf, 1, sizeof(buf), firmware_file);
|
|
if (!len)
|
|
break;
|
|
|
|
file_len += len;
|
|
trailer_update_crc(&tr, buf, len);
|
|
}
|
|
|
|
if (metadata_file)
|
|
ret = add_metadata(&tr);
|
|
else if (signature_file)
|
|
ret = add_signature(&tr);
|
|
|
|
if (ret) {
|
|
fflush(firmware_file);
|
|
ftruncate(fileno(firmware_file), file_len);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
remove_tail(struct data_buf *dbuf, int len)
|
|
{
|
|
dbuf->cur_len -= len;
|
|
dbuf->file_len -= len;
|
|
|
|
if (dbuf->cur_len)
|
|
return;
|
|
|
|
free(dbuf->cur);
|
|
dbuf->cur = dbuf->prev;
|
|
dbuf->prev = NULL;
|
|
dbuf->cur_len = BUFLEN;
|
|
}
|
|
|
|
static int
|
|
extract_tail(struct data_buf *dbuf, void *dest, int len)
|
|
{
|
|
int cur_len = dbuf->cur_len;
|
|
|
|
if (!dbuf->cur)
|
|
return 1;
|
|
|
|
if (cur_len >= len)
|
|
cur_len = len;
|
|
|
|
memcpy(dest + (len - cur_len), dbuf->cur + dbuf->cur_len - cur_len, cur_len);
|
|
remove_tail(dbuf, cur_len);
|
|
|
|
cur_len = len - cur_len;
|
|
if (cur_len && !dbuf->cur)
|
|
return 1;
|
|
|
|
memcpy(dest, dbuf->cur + dbuf->cur_len - cur_len, cur_len);
|
|
remove_tail(dbuf, cur_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t
|
|
tail_crc32(struct data_buf *dbuf, uint32_t crc32)
|
|
{
|
|
if (dbuf->prev)
|
|
crc32 = crc32_block(crc32, dbuf->prev, BUFLEN, crc_table);
|
|
|
|
return crc32_block(crc32, dbuf->cur, dbuf->cur_len, crc_table);
|
|
}
|
|
|
|
static int
|
|
validate_metadata(struct fwimage_header *hdr, int data_len)
|
|
{
|
|
if (hdr->version != 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
extract_data(const char *name)
|
|
{
|
|
struct fwimage_header *hdr;
|
|
struct fwimage_trailer tr;
|
|
struct data_buf dbuf = {};
|
|
uint32_t crc32 = ~0;
|
|
int data_len = 0;
|
|
int ret = 1;
|
|
void *buf;
|
|
bool metadata_keep = false;
|
|
|
|
firmware_file = open_file(name, false);
|
|
if (!firmware_file) {
|
|
msg("Failed to open firmware file\n");
|
|
return 1;
|
|
}
|
|
|
|
if (truncate_file && firmware_file == stdin) {
|
|
msg("Cannot truncate file when reading from stdin\n");
|
|
return 1;
|
|
}
|
|
|
|
buf = malloc(BUFLEN);
|
|
if (!buf)
|
|
return 1;
|
|
|
|
do {
|
|
char *tmp = dbuf.cur;
|
|
|
|
if (write_truncated && dbuf.prev)
|
|
fwrite(dbuf.prev, 1, BUFLEN, stdout);
|
|
|
|
dbuf.cur = dbuf.prev;
|
|
dbuf.prev = tmp;
|
|
|
|
if (dbuf.cur)
|
|
crc32 = crc32_block(crc32, dbuf.cur, BUFLEN, crc_table);
|
|
else
|
|
dbuf.cur = malloc(BUFLEN);
|
|
|
|
if (!dbuf.cur)
|
|
goto out;
|
|
|
|
dbuf.cur_len = fread(dbuf.cur, 1, BUFLEN, firmware_file);
|
|
dbuf.file_len += dbuf.cur_len;
|
|
} while (dbuf.cur_len == BUFLEN);
|
|
|
|
while (1) {
|
|
|
|
if (extract_tail(&dbuf, &tr, sizeof(tr)))
|
|
break;
|
|
|
|
if (tr.magic != cpu_to_be32(FWIMAGE_MAGIC)) {
|
|
msg("Data not found\n");
|
|
metadata_keep = true;
|
|
break;
|
|
}
|
|
|
|
data_len = be32_to_cpu(tr.size) - sizeof(tr);
|
|
|
|
if (be32_to_cpu(tr.crc32) != tail_crc32(&dbuf, crc32)) {
|
|
msg("CRC error\n");
|
|
break;
|
|
}
|
|
|
|
if (data_len > BUFLEN) {
|
|
msg("Size error\n");
|
|
break;
|
|
}
|
|
|
|
extract_tail(&dbuf, buf, data_len);
|
|
|
|
if (tr.type == FWIMAGE_SIGNATURE) {
|
|
if (!signature_file)
|
|
continue;
|
|
fwrite(buf, data_len, 1, signature_file);
|
|
ret = 0;
|
|
break;
|
|
} else if (tr.type == FWIMAGE_INFO) {
|
|
if (!metadata_file) {
|
|
dbuf.file_len += data_len + sizeof(tr);
|
|
metadata_keep = true;
|
|
break;
|
|
}
|
|
|
|
hdr = buf;
|
|
data_len -= sizeof(*hdr);
|
|
if (validate_metadata(hdr, data_len))
|
|
continue;
|
|
|
|
fwrite(hdr + 1, data_len, 1, metadata_file);
|
|
ret = 0;
|
|
break;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!ret && truncate_file)
|
|
ftruncate(fileno(firmware_file), dbuf.file_len);
|
|
|
|
if (write_truncated) {
|
|
if (dbuf.prev)
|
|
fwrite(dbuf.prev, 1, BUFLEN, stdout);
|
|
if (dbuf.cur)
|
|
fwrite(dbuf.cur, 1, dbuf.cur_len, stdout);
|
|
if (metadata_keep) {
|
|
fwrite(buf, data_len, 1, stdout);
|
|
fwrite(&tr, sizeof(tr), 1, stdout);
|
|
}
|
|
}
|
|
|
|
out:
|
|
free(buf);
|
|
free(dbuf.cur);
|
|
free(dbuf.prev);
|
|
return ret;
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
if (signature_file)
|
|
fclose(signature_file);
|
|
if (metadata_file)
|
|
fclose(metadata_file);
|
|
if (firmware_file)
|
|
fclose(firmware_file);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *progname = argv[0];
|
|
int ret, ch;
|
|
|
|
crc32_filltable(crc_table);
|
|
|
|
while ((ch = getopt(argc, argv, "i:I:qs:S:tT")) != -1) {
|
|
ret = 0;
|
|
switch(ch) {
|
|
case 'S':
|
|
ret = set_file(&signature_file, optarg, MODE_APPEND);
|
|
break;
|
|
case 'I':
|
|
ret = set_file(&metadata_file, optarg, MODE_APPEND);
|
|
break;
|
|
case 's':
|
|
ret = set_file(&signature_file, optarg, MODE_EXTRACT);
|
|
break;
|
|
case 'i':
|
|
ret = set_file(&metadata_file, optarg, MODE_EXTRACT);
|
|
break;
|
|
case 't':
|
|
truncate_file = true;
|
|
break;
|
|
case 'T':
|
|
write_truncated = true;
|
|
break;
|
|
case 'q':
|
|
quiet = true;
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
if (optind >= argc) {
|
|
ret = usage(progname);
|
|
goto out;
|
|
}
|
|
|
|
if (file_mode == MODE_DEFAULT) {
|
|
ret = usage(progname);
|
|
goto out;
|
|
}
|
|
|
|
if (signature_file && metadata_file) {
|
|
msg("Cannot append/extract metadata and signature in one run\n");
|
|
return 1;
|
|
}
|
|
|
|
if (file_mode)
|
|
ret = add_data(argv[optind]);
|
|
else
|
|
ret = extract_data(argv[optind]);
|
|
|
|
out:
|
|
cleanup();
|
|
return ret;
|
|
}
|