blob: 916f4eb4aa4a9c96bc1c47b7b6410ce07df57f2e [file] [log] [blame]
/* Copyright 2014 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "futility.h"
#include "updater.h"
#include "updater_utils.h"
#include "2gbb_flags.h"
#ifdef USE_FLASHROM
#define FLASH_ARG_HELP \
" --flash \tRead from and write to flash" \
", ignore file arguments.\n"
#define FLASH_MORE_HELP \
"In GET and SET mode, the following options modify the " \
"behaviour of flashing. Presence of any of these implies " \
"--flash.\n" \
SHARED_FLASH_ARGS_HELP \
"\n"
#else
#define FLASH_ARG_HELP
#define FLASH_MORE_HELP
#endif /* USE_FLASHROM */
static void print_help(int argc, char *argv[])
{
printf("\n"
"Usage: " MYNAME " %s [-g|-s|-c] [OPTIONS] "
"[bios_file] [output_file]\n"
"\n"
"GET MODE:\n"
"-g, --get (default)\tGet (read) from bios_file or flash, "
"with following options:\n"
FLASH_ARG_HELP
" --hwid \tReport hardware id (default).\n"
" --flags \tReport header flags.\n"
" --digest \tReport digest of hwid (>= v1.2)\n"
" -k, --rootkey=FILE \tFile name to export Root Key.\n"
" -b, --bmpfv=FILE \tFile name to export Bitmap FV.\n"
" -r --recoverykey=FILE\tFile name to export Recovery Key.\n"
" -e --explicit \tReport header flags by name.\n"
"\n"
"SET MODE:\n"
"-s, --set \tSet (write) to flash or file, "
"with following options:\n"
FLASH_ARG_HELP
" -o, --output=FILE \tNew file name for ouptput.\n"
" --hwid=HWID \tThe new hardware id to be changed.\n"
" --flags=FLAGS \tThe new (numeric) flags value or +/- diff value.\n"
" -k, --rootkey=FILE \tFile name of new Root Key.\n"
" -b, --bmpfv=FILE \tFile name of new Bitmap FV.\n"
" -r --recoverykey=FILE\tFile name of new Recovery Key.\n"
"\n"
"CREATE MODE:\n"
"-c, --create=hwid_size,rootkey_size,bmpfv_size,"
"recoverykey_size\n"
" \tCreate a GBB blob by given size list.\n\n"
FLASH_MORE_HELP
"SAMPLE:\n"
" %s -g bios.bin\n"
" %s --set --hwid='New Model' -k key.bin"
" bios.bin newbios.bin\n"
" %s -c 0x100,0x1000,0x03DE80,0x1000 gbb.blob\n\n"
"GBB Flags:\n"
" To get a developer-friendly device, try 0x18 (dev_mode boot_usb).\n"
" For early bringup development, try 0x40b9.\n",
argv[0], argv[0], argv[0], argv[0]);
for (vb2_gbb_flags_t flag = 1; flag; flag <<= 1) {
const char *name;
const char *description;
if (vb2_get_gbb_flag_description(flag, &name, &description) !=
VB2_SUCCESS)
break;
printf(" 0x%08x\t%s\n"
" \t%s\n",
flag, name, description);
}
}
enum {
OPT_HWID = 0x1000,
OPT_FLAGS,
OPT_DIGEST,
OPT_FLASH,
OPT_HELP,
};
/* Command line options */
static struct option long_opts[] = {
SHARED_FLASH_ARGS_LONGOPTS
/* name has_arg *flag val */
{"get", 0, NULL, 'g'},
{"set", 0, NULL, 's'},
{"create", 1, NULL, 'c'},
{"output", 1, NULL, 'o'},
{"rootkey", 1, NULL, 'k'},
{"bmpfv", 1, NULL, 'b'},
{"recoverykey", 1, NULL, 'r'},
{"hwid", 0, NULL, OPT_HWID},
{"flags", 0, NULL, OPT_FLAGS},
{"explicit", 0, NULL, 'e'},
{"digest", 0, NULL, OPT_DIGEST},
{"flash", 0, NULL, OPT_FLASH},
{"help", 0, NULL, OPT_HELP},
{NULL, 0, NULL, 0},
};
static const char *short_opts = ":gsc:o:k:b:r:e" SHARED_FLASH_ARGS_SHORTOPTS;
/* Change the has_arg field of a long_opts entry */
static void opt_has_arg(const char *name, int val)
{
struct option *p;
for (p = long_opts; p->name; p++)
if (!strcmp(name, p->name)) {
p->has_arg = val;
break;
}
}
static int errorcnt;
#define GBB_SEARCH_STRIDE 4
static struct vb2_gbb_header *FindGbbHeader(uint8_t *ptr, size_t size)
{
size_t i;
struct vb2_gbb_header *tmp, *gbb_header = NULL;
int count = 0;
for (i = 0; i <= size - GBB_SEARCH_STRIDE; i += GBB_SEARCH_STRIDE) {
if (0 != memcmp(ptr + i, VB2_GBB_SIGNATURE,
VB2_GBB_SIGNATURE_SIZE))
continue;
/* Found something. See if it's any good. */
tmp = (struct vb2_gbb_header *) (ptr + i);
if (futil_valid_gbb_header(tmp, size - i, NULL))
if (!count++)
gbb_header = tmp;
}
switch (count) {
case 0:
errorcnt++;
return NULL;
case 1:
return gbb_header;
default:
fprintf(stderr, "ERROR: multiple GBB headers found\n");
errorcnt++;
return NULL;
}
}
static uint8_t *create_gbb(const char *desc, off_t *sizeptr)
{
char *str, *sizes, *param, *e = NULL;
size_t size = EXPECTED_VB2_GBB_HEADER_SIZE;
int i = 0;
/* Danger Will Robinson! four entries ==> four paramater blocks */
uint32_t val[] = { 0, 0, 0, 0 };
uint8_t *buf;
struct vb2_gbb_header *gbb;
sizes = strdup(desc);
if (!sizes) {
errorcnt++;
fprintf(stderr, "ERROR: strdup() failed: %s\n",
strerror(errno));
return NULL;
}
for (str = sizes; (param = strtok(str, ", ")) != NULL; str = NULL) {
val[i] = (uint32_t) strtoul(param, &e, 0);
if (e && *e) {
errorcnt++;
fprintf(stderr,
"ERROR: invalid creation parameter: \"%s\"\n",
param);
free(sizes);
return NULL;
}
size += val[i++];
if (i > ARRAY_SIZE(val))
break;
}
buf = (uint8_t *) calloc(1, size);
if (!buf) {
errorcnt++;
fprintf(stderr, "ERROR: can't malloc %zu bytes: %s\n",
size, strerror(errno));
free(sizes);
return NULL;
}
if (sizeptr)
*sizeptr = size;
gbb = (struct vb2_gbb_header *) buf;
memcpy(gbb->signature, VB2_GBB_SIGNATURE, VB2_GBB_SIGNATURE_SIZE);
gbb->major_version = VB2_GBB_MAJOR_VER;
gbb->minor_version = VB2_GBB_MINOR_VER;
gbb->header_size = EXPECTED_VB2_GBB_HEADER_SIZE;
gbb->flags = 0;
i = EXPECTED_VB2_GBB_HEADER_SIZE;
gbb->hwid_offset = i;
gbb->hwid_size = val[0];
i += val[0];
gbb->rootkey_offset = i;
gbb->rootkey_size = val[1];
i += val[1];
gbb->bmpfv_offset = i;
gbb->bmpfv_size = val[2];
i += val[2];
gbb->recovery_key_offset = i;
gbb->recovery_key_size = val[3];
i += val[1];
free(sizes);
return buf;
}
static uint8_t *read_entire_file(const char *filename, off_t *sizeptr)
{
FILE *fp = NULL;
uint8_t *buf = NULL;
struct stat sb;
fp = fopen(filename, "rb");
if (!fp) {
fprintf(stderr, "ERROR: Unable to open %s for reading: %s\n",
filename, strerror(errno));
goto fail;
}
if (0 != fstat(fileno(fp), &sb)) {
fprintf(stderr, "ERROR: can't fstat %s: %s\n",
filename, strerror(errno));
goto fail;
}
if (sizeptr)
*sizeptr = sb.st_size;
buf = (uint8_t *) malloc(sb.st_size);
if (!buf) {
fprintf(stderr, "ERROR: can't malloc %" PRIi64 " bytes: %s\n",
sb.st_size, strerror(errno));
goto fail;
}
if (1 != fread(buf, sb.st_size, 1, fp)) {
fprintf(stderr, "ERROR: Unable to read from %s: %s\n",
filename, strerror(errno));
goto fail;
}
if (0 != fclose(fp)) {
fprintf(stderr, "ERROR: Unable to close %s: %s\n",
filename, strerror(errno));
fp = NULL; /* Don't try to close it again */
goto fail;
}
return buf;
fail:
errorcnt++;
if (buf)
free(buf);
if (fp && 0 != fclose(fp))
fprintf(stderr, "ERROR: Unable to close %s: %s\n",
filename, strerror(errno));
return NULL;
}
static int read_from_file(const char *msg, const char *filename,
uint8_t *start, uint32_t size)
{
FILE *fp;
struct stat sb;
size_t count;
int r = 0;
fp = fopen(filename, "rb");
if (!fp) {
r = errno;
fprintf(stderr, "ERROR: Unable to open %s for reading: %s\n",
filename, strerror(r));
errorcnt++;
return r;
}
if (0 != fstat(fileno(fp), &sb)) {
r = errno;
fprintf(stderr, "ERROR: can't fstat %s: %s\n", filename,
strerror(r));
errorcnt++;
goto done_close;
}
if (sb.st_size > size) {
fprintf(stderr,
"ERROR: file %s exceeds capacity (%" PRIu32 ")\n",
filename, size);
errorcnt++;
r = -1;
goto done_close;
}
/* Wipe existing data. */
memset(start, 0, size);
/* It's okay if we read less than size. That's just the max. */
count = fread(start, 1, size, fp);
if (ferror(fp)) {
r = errno;
fprintf(stderr,
"ERROR: Read %zu/%" PRIi64 " bytes from %s: %s\n",
count, sb.st_size, filename, strerror(r));
errorcnt++;
}
done_close:
if (0 != fclose(fp)) {
int e = errno;
fprintf(stderr, "ERROR: Unable to close %s: %s\n", filename,
strerror(e));
errorcnt++;
if (!r)
r = e;
}
if (!r && msg)
printf(" - import %s from %s: success\n", msg, filename);
return r;
}
/*
* Prepare for flashrom interaction. Setup cfg from args and put servo into
* flash mode if servo is in use. If this succeeds teardown_flash must be
* called.
*/
static int setup_flash(struct updater_config **cfg,
struct updater_config_arguments *args,
const char **prepare_ctrl_name)
{
#ifdef USE_FLASHROM
*prepare_ctrl_name = NULL;
*cfg = updater_new_config();
if (!*cfg) {
fprintf(stderr, "\nERROR: Out of memory\n");
return 1;
}
if (args->detect_servo) {
char *servo_programmer = host_detect_servo(prepare_ctrl_name);
if (!servo_programmer) {
fprintf(stderr,
"\nERROR: Problem communicating with servo\n");
goto errdelete;
}
if (!args->programmer)
args->programmer = servo_programmer;
else
free(servo_programmer);
}
int ignored;
if (updater_setup_config(*cfg, args, &ignored)) {
fprintf(stderr, "\nERROR: Bad servo options\n");
goto errdelete;
}
prepare_servo_control(*prepare_ctrl_name, 1);
return 0;
errdelete:
updater_delete_config(*cfg);
*cfg = NULL;
return 1;
#else
return 1;
#endif /* USE_FLASHROM */
}
/* Cleanup objects created in setup_flash and release servo from flash mode. */
static void teardown_flash(struct updater_config *cfg,
const char *prepare_ctrl_name,
char *servo_programmer)
{
#ifdef USE_FLASHROM
prepare_servo_control(prepare_ctrl_name, 0);
free(servo_programmer);
updater_delete_config(cfg);
#endif /* USE_FLASHROM */
}
/* Read firmware from flash. */
static uint8_t *read_from_flash(struct updater_config *cfg, off_t *filesize)
{
#ifdef USE_FLASHROM
/*
* Read the FMAP region as well, so that a subsequet write won't
* require another read of FMAP.
*/
const char * const regions[] = {FMAP_RO_FMAP, FMAP_RO_GBB, NULL};
if (flashrom_read_image(&cfg->image_current, regions,
cfg->verbosity + 1))
return NULL;
uint8_t *ret = cfg->image_current.data;
cfg->image_current.data = NULL;
*filesize = cfg->image_current.size;
cfg->image_current.size = 0;
return ret;
#else
return NULL;
#endif /* USE_FLASHROM */
}
/* Write firmware to flash. Takes ownership of inbuf and outbuf data. */
static int write_to_flash(struct updater_config *cfg, uint8_t *outbuf,
off_t filesize)
{
#ifdef USE_FLASHROM
cfg->image.data = outbuf;
cfg->image.size = filesize;
int ret = write_firmware(cfg, &cfg->image, FMAP_RO_GBB);
cfg->image.data = NULL;
cfg->image.size = 0;
return ret;
#else
return 1;
#endif /* USE_FLASHROM */
}
static int parse_flag_value(const char *s, vb2_gbb_flags_t *val)
{
int sign = 0;
if (!strlen(s))
return -1;
if (s[0] == '+')
sign = 1;
else if (s[0] == '-')
sign = 2;
const char *ss = !sign ? s : &s[1];
char *e = NULL;
*val = strtoul(ss, &e, 0);
if (e && *e) {
fprintf(stderr, "ERROR: invalid flags value: %s\n", ss);
return -1;
}
return sign;
}
static int do_gbb(int argc, char *argv[])
{
enum do_what_now { DO_GET, DO_SET, DO_CREATE } mode = DO_GET;
char *infile = NULL;
char *outfile = NULL;
char *opt_create = NULL;
char *opt_rootkey = NULL;
char *opt_bmpfv = NULL;
char *opt_recoverykey = NULL;
char *opt_hwid = NULL;
char *opt_flags = NULL;
int sel_hwid = 0;
int sel_digest = 0;
int sel_flags = 0;
int explicit_flags = 0;
uint8_t *inbuf = NULL;
off_t filesize;
uint8_t *outbuf = NULL;
struct vb2_gbb_header *gbb;
uint8_t *gbb_base;
int i;
struct updater_config *cfg = NULL;
struct updater_config_arguments args = {0};
const char *prepare_ctrl_name = NULL;
char *servo_programmer = NULL;
opterr = 0; /* quiet, you */
while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
#ifdef USE_FLASHROM
if (handle_flash_argument(&args, i, optarg))
continue;
#endif
switch (i) {
case 'g':
mode = DO_GET;
opt_has_arg("flags", 0);
opt_has_arg("hwid", 0);
break;
case 's':
mode = DO_SET;
opt_has_arg("flags", 1);
opt_has_arg("hwid", 1);
break;
case 'c':
mode = DO_CREATE;
opt_create = optarg;
break;
case 'o':
outfile = optarg;
break;
case 'k':
opt_rootkey = optarg;
break;
case 'b':
opt_bmpfv = optarg;
break;
case 'r':
opt_recoverykey = optarg;
break;
case OPT_HWID:
/* --hwid is optional: null might be okay */
opt_hwid = optarg;
sel_hwid = 1;
break;
case OPT_FLAGS:
/* --flags is optional: null might be okay */
opt_flags = optarg;
sel_flags = 1;
break;
case 'e':
sel_flags = 1;
explicit_flags = 1;
break;
case OPT_DIGEST:
sel_digest = 1;
break;
case OPT_FLASH:
#ifndef USE_FLASHROM
fprintf(stderr, "ERROR: futility was built without "
"flashrom support\n");
return 1;
#endif
args.use_flash = 1;
break;
case OPT_HELP:
print_help(argc, argv);
return !!errorcnt;
case '?':
errorcnt++;
if (optopt)
fprintf(stderr,
"ERROR: unrecognized option: -%c\n",
optopt);
else if (argv[optind - 1])
fprintf(stderr,
"ERROR: unrecognized option "
"(possibly \"%s\")\n",
argv[optind - 1]);
else
fprintf(stderr, "ERROR: unrecognized option\n");
break;
case ':':
errorcnt++;
if (argv[optind - 1])
fprintf(stderr,
"ERROR: missing argument to -%c (%s)\n",
optopt, argv[optind - 1]);
else
fprintf(stderr,
"ERROR: missing argument to -%c\n",
optopt);
break;
default:
errorcnt++;
fprintf(stderr,
"ERROR: error while parsing options\n");
}
}
/* Problems? */
if (errorcnt) {
print_help(argc, argv);
return 1;
}
if (args.use_flash) {
if (setup_flash(&cfg, &args, &prepare_ctrl_name)) {
fprintf(stderr,
"ERROR: error while preparing flash\n");
return 1;
}
}
/* Now try to do something */
switch (mode) {
case DO_GET:
if (args.use_flash) {
inbuf = read_from_flash(cfg, &filesize);
} else {
if (argc - optind < 1) {
fprintf(stderr,
"\nERROR: missing input filename\n");
print_help(argc, argv);
errorcnt++;
break;
}
infile = argv[optind++];
inbuf = read_entire_file(infile, &filesize);
}
if (!inbuf) {
errorcnt++;
break;
}
/* With no args, show the HWID */
if (!opt_rootkey && !opt_bmpfv && !opt_recoverykey
&& !sel_flags && !sel_digest)
sel_hwid = 1;
gbb = FindGbbHeader(inbuf, filesize);
if (!gbb) {
fprintf(stderr, "ERROR: No GBB found in %s\n", infile);
errorcnt++;
break;
}
gbb_base = (uint8_t *) gbb;
/* Get the stuff */
if (sel_hwid)
printf("hardware_id: %s\n",
gbb->hwid_size ? (char *)(gbb_base +
gbb->
hwid_offset) : "");
if (sel_digest)
print_hwid_digest(gbb, "digest: ", "\n");
if (sel_flags)
printf("flags: 0x%08x\n", gbb->flags);
if (opt_rootkey)
if (write_to_file(" - exported root_key to file:",
opt_rootkey,
gbb_base + gbb->rootkey_offset,
gbb->rootkey_size)) {
errorcnt++;
break;
}
if (opt_bmpfv)
if (write_to_file(
" - exported bmp_fv to file:", opt_bmpfv,
gbb_base + gbb->bmpfv_offset,
gbb->bmpfv_size)) {
errorcnt++;
break;
}
if (opt_recoverykey)
if (write_to_file(" - exported recovery_key to file:",
opt_recoverykey,
gbb_base + gbb->recovery_key_offset,
gbb->recovery_key_size)) {
errorcnt++;
break;
}
if (explicit_flags) {
vb2_gbb_flags_t remaining_flags = gbb->flags;
while (remaining_flags) {
vb2_gbb_flags_t lsb_flag =
remaining_flags & -remaining_flags;
remaining_flags &= ~lsb_flag;
const char *name;
const char *description;
if (vb2_get_gbb_flag_description(
lsb_flag, &name, &description) ==
VB2_SUCCESS) {
printf("%s\n", name);
} else {
printf("unknown set flag: 0x%08x\n",
lsb_flag);
}
}
}
break;
case DO_SET:
if (args.use_flash) {
inbuf = read_from_flash(cfg, &filesize);
} else {
if (argc - optind < 1) {
fprintf(stderr,
"\nERROR: missing input filename\n");
print_help(argc, argv);
errorcnt++;
break;
}
infile = argv[optind++];
inbuf = read_entire_file(infile, &filesize);
if (!outfile)
outfile = (argc - optind < 1) ? infile
: argv[optind++];
}
if (!inbuf) {
errorcnt++;
break;
}
if (sel_hwid && !opt_hwid) {
fprintf(stderr, "\nERROR: missing new HWID value\n");
print_help(argc, argv);
errorcnt++;
break;
}
if (sel_flags && (!opt_flags || !*opt_flags)) {
fprintf(stderr, "\nERROR: missing new flags value\n");
print_help(argc, argv);
errorcnt++;
break;
}
gbb = FindGbbHeader(inbuf, filesize);
if (!gbb) {
fprintf(stderr, "ERROR: No GBB found in %s\n", infile);
errorcnt++;
break;
}
gbb_base = (uint8_t *) gbb;
outbuf = (uint8_t *) malloc(filesize);
if (!outbuf) {
fprintf(stderr,
"ERROR: can't malloc %" PRIi64 " bytes: %s\n",
filesize, strerror(errno));
errorcnt++;
break;
}
/* Switch pointers to outbuf */
memcpy(outbuf, inbuf, filesize);
gbb = FindGbbHeader(outbuf, filesize);
if (!gbb) {
fprintf(stderr,
"INTERNAL ERROR: No GBB found in outbuf\n");
errorcnt++;
break;
}
gbb_base = (uint8_t *) gbb;
if (opt_hwid) {
if (strlen(opt_hwid) + 1 > gbb->hwid_size) {
fprintf(stderr,
"ERROR: null-terminated HWID"
" exceeds capacity (%d)\n",
gbb->hwid_size);
errorcnt++;
break;
}
/* Wipe data before writing new value. */
memset(gbb_base + gbb->hwid_offset, 0,
gbb->hwid_size);
strcpy((char *)(gbb_base + gbb->hwid_offset),
opt_hwid);
update_hwid_digest(gbb);
}
if (opt_flags) {
vb2_gbb_flags_t val;
const int flag_sign = parse_flag_value(opt_flags, &val);
if (flag_sign < 0) {
errorcnt++;
break;
}
if (flag_sign > 0)
/* flag_sign := 1 => +ve and flag_sign := 2 => -ve. */
gbb->flags = flag_sign == 1 ? (gbb->flags | val) : (gbb->flags & ~val);
else
gbb->flags = val;
}
if (opt_rootkey) {
if (read_from_file("root_key", opt_rootkey,
gbb_base + gbb->rootkey_offset,
gbb->rootkey_size)) {
errorcnt++;
break;
}
}
if (opt_bmpfv)
if (read_from_file("bmp_fv", opt_bmpfv,
gbb_base + gbb->bmpfv_offset,
gbb->bmpfv_size)) {
errorcnt++;
break;
}
if (opt_recoverykey)
if (read_from_file("recovery_key", opt_recoverykey,
gbb_base + gbb->recovery_key_offset,
gbb->recovery_key_size)) {
errorcnt++;
break;
}
/* Write it out if there are no problems. */
if (!errorcnt) {
if (args.use_flash) {
if (write_to_flash(cfg, outbuf, filesize)) {
errorcnt++;
break;
}
} else if (write_to_file(
"successfully saved new image to:",
outfile, outbuf, filesize)) {
errorcnt++;
break;
}
}
break;
case DO_CREATE:
if (!outfile) {
if (argc - optind < 1) {
fprintf(stderr,
"\nERROR: missing output filename\n");
print_help(argc, argv);
errorcnt++;
break;
}
outfile = argv[optind++];
}
/* Parse the creation args */
outbuf = create_gbb(opt_create, &filesize);
if (!outbuf) {
fprintf(stderr,
"\nERROR: unable to parse creation spec (%s)\n",
opt_create);
print_help(argc, argv);
errorcnt++;
break;
}
if (!errorcnt)
if (write_to_file("successfully created new GBB to:",
outfile, outbuf, filesize)) {
errorcnt++;
break;
}
break;
}
if (args.use_flash)
teardown_flash(cfg, prepare_ctrl_name, servo_programmer);
if (inbuf)
free(inbuf);
if (outbuf)
free(outbuf);
return !!errorcnt;
}
DECLARE_FUTIL_COMMAND(gbb, do_gbb, VBOOT_VERSION_ALL,
"Manipulate the Google Binary Block (GBB)");
DECLARE_FUTIL_COMMAND(gbb_utility, do_gbb, VBOOT_VERSION_ALL,
"Legacy name for `gbb` command");