444 lines
8.2 KiB
C
444 lines
8.2 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 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"
|
||
|
" -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 ret = 1;
|
||
|
void *buf;
|
||
|
|
||
|
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;
|
||
|
|
||
|
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) {
|
||
|
int data_len;
|
||
|
|
||
|
if (extract_tail(&dbuf, &tr, sizeof(tr)))
|
||
|
break;
|
||
|
|
||
|
data_len = be32_to_cpu(tr.size) - sizeof(tr);
|
||
|
if (tr.magic != cpu_to_be32(FWIMAGE_MAGIC)) {
|
||
|
msg("Data not found\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
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);
|
||
|
|
||
|
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:t")) != -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 '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;
|
||
|
}
|