/*
* Copyright 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Verified boot kernel utility
*/
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h> /* For PRIu64 */
#ifndef HAVE_MACOS
#include <linux/fs.h> /* For BLKGETSIZE64 */
#endif
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "file_type.h"
#include "futility.h"
#include "host_common.h"
#include "kernel_blob.h"
#include "traversal.h"
#include "vb1_helper.h"
static void Fatal(const char *format, ...)
{
va_list ap;
va_start(ap, format);
fprintf(stderr, "ERROR: ");
vfprintf(stderr, format, ap);
va_end(ap);
exit(1);
}
/* Global opts */
static int opt_verbose;
static int opt_vblockonly;
static uint64_t opt_pad = 65536;
/* Command line options */
enum {
OPT_MODE_PACK = 1000,
OPT_MODE_REPACK,
OPT_MODE_VERIFY,
OPT_MODE_GET_VMLINUZ,
OPT_ARCH,
OPT_OLDBLOB,
OPT_KLOADADDR,
OPT_KEYBLOCK,
OPT_SIGNPUBKEY,
OPT_SIGNPRIVATE,
OPT_VERSION,
OPT_VMLINUZ,
OPT_BOOTLOADER,
OPT_CONFIG,
OPT_VBLOCKONLY,
OPT_PAD,
OPT_VERBOSE,
OPT_MINVERSION,
OPT_VMLINUZ_OUT,
OPT_FLAGS,
};
static const struct option long_opts[] = {
{"pack", 1, 0, OPT_MODE_PACK},
{"repack", 1, 0, OPT_MODE_REPACK},
{"verify", 1, 0, OPT_MODE_VERIFY},
{"get-vmlinuz", 1, 0, OPT_MODE_GET_VMLINUZ},
{"arch", 1, 0, OPT_ARCH},
{"oldblob", 1, 0, OPT_OLDBLOB},
{"kloadaddr", 1, 0, OPT_KLOADADDR},
{"keyblock", 1, 0, OPT_KEYBLOCK},
{"signpubkey", 1, 0, OPT_SIGNPUBKEY},
{"signprivate", 1, 0, OPT_SIGNPRIVATE},
{"version", 1, 0, OPT_VERSION},
{"minversion", 1, 0, OPT_MINVERSION},
{"vmlinuz", 1, 0, OPT_VMLINUZ},
{"bootloader", 1, 0, OPT_BOOTLOADER},
{"config", 1, 0, OPT_CONFIG},
{"vblockonly", 0, 0, OPT_VBLOCKONLY},
{"pad", 1, 0, OPT_PAD},
{"verbose", 0, &opt_verbose, 1},
{"debug", 0, &debugging_enabled, 1},
{"vmlinuz-out", 1, 0, OPT_VMLINUZ_OUT},
{"flags", 1, 0, OPT_FLAGS},
{NULL, 0, 0, 0}
};
static const char usage[] =
"\n"
"Usage: " MYNAME " %s --pack <file> [PARAMETERS]\n"
"\n"
" Required parameters:\n"
" --keyblock <file> Key block in .keyblock format\n"
" --signprivate <file> Private key to sign kernel data,\n"
" in .vbprivk format\n"
" --version <number> Kernel version\n"
" --vmlinuz <file> Linux kernel bzImage file\n"
" --bootloader <file> Bootloader stub\n"
" --config <file> Command line file\n"
" --arch <arch> Cpu architecture (default x86)\n"
"\n"
" Optional:\n"
" --kloadaddr <address> Assign kernel body load address\n"
" --pad <number> Verification padding size in bytes\n"
" --vblockonly Emit just the verification blob\n"
" --flags NUM Flags to be passed in the header\n"
"\nOR\n\n"
"Usage: " MYNAME " %s --repack <file> [PARAMETERS]\n"
"\n"
" Required parameters:\n"
" --signprivate <file> Private key to sign kernel data,\n"
" in .vbprivk format\n"
" --oldblob <file> Previously packed kernel blob\n"
" (including verfication blob)\n"
"\n"
" Optional:\n"
" --keyblock <file> Key block in .keyblock format\n"
" --config <file> New command line file\n"
" --version <number> Kernel version\n"
" --kloadaddr <address> Assign kernel body load address\n"
" --pad <number> Verification blob size in bytes\n"
" --vblockonly Emit just the verification blob\n"
"\nOR\n\n"
"Usage: " MYNAME " %s --verify <file> [PARAMETERS]\n"
"\n"
" Optional:\n"
" --signpubkey <file>"
" Public key to verify kernel keyblock,\n"
" in .vbpubk format\n"
" --verbose Print a more detailed report\n"
" --keyblock <file> Outputs the verified key block,\n"
" in .keyblock format\n"
" --pad <number> Verification padding size in bytes\n"
" --minversion <number> Minimum combined kernel key version\n"
"\nOR\n\n"
"Usage: " MYNAME " %s --get-vmlinuz <file> [PARAMETERS]\n"
"\n"
" Required parameters:\n"
" --vmlinuz-out <file> vmlinuz image output file\n"
"\n";
/* Print help and return error */
static void print_help(const char *progname)
{
printf(usage, progname, progname, progname, progname);
}
/* Return an explanation when fread() fails. */
static const char *error_fread(FILE *fp)
{
const char *retval = "beats me why";
if (feof(fp))
retval = "EOF";
else if (ferror(fp))
retval = strerror(errno);
clearerr(fp);
return retval;
}
/* This reads a complete kernel partition into a buffer */
static uint8_t *ReadOldKPartFromFileOrDie(const char *filename,
uint64_t *size_ptr)
{
FILE *fp = NULL;
struct stat statbuf;
uint8_t *buf;
uint64_t file_size = 0;
if (0 != stat(filename, &statbuf))
Fatal("Unable to stat %s: %s\n", filename, strerror(errno));
if (S_ISBLK(statbuf.st_mode)) {
#ifndef HAVE_MACOS
int fd = open(filename, O_RDONLY);
if (fd >= 0) {
ioctl(fd, BLKGETSIZE64, &file_size);
close(fd);
}
#endif
} else {
file_size = statbuf.st_size;
}
Debug("%s size is 0x%" PRIx64 "\n", filename, file_size);
if (file_size < opt_pad)
Fatal("%s is too small to be a valid kernel blob\n", filename);
Debug("Reading %s\n", filename);
fp = fopen(filename, "rb");
if (!fp)
Fatal("Unable to open file %s: %s\n", filename,
strerror(errno));
buf = malloc(file_size);
if (1 != fread(buf, file_size, 1, fp))
Fatal("Unable to read entirety of %s: %s\n", filename,
error_fread(fp));
if (size_ptr)
*size_ptr = file_size;
return buf;
}
/****************************************************************************/
static int do_vbutil_kernel(int argc, char *argv[])
{
char *filename = NULL;
char *oldfile = NULL;
char *keyblock_file = NULL;
char *signpubkey_file = NULL;
char *signprivkey_file = NULL;
char *version_str = NULL;
int version = -1;
char *vmlinuz_file = NULL;
char *bootloader_file = NULL;
char *config_file = NULL;
char *vmlinuz_out_file = NULL;
enum arch_t arch = ARCH_X86;
uint64_t kernel_body_load_address = CROS_32BIT_ENTRY_ADDR;
int mode = 0;
int parse_error = 0;
uint64_t min_version = 0;
char *e;
int i = 0;
int errcount = 0;
int rv;
VbKeyBlockHeader *keyblock = NULL;
VbKeyBlockHeader *t_keyblock = NULL;
VbPrivateKey *signpriv_key = NULL;
VbPublicKey *signpub_key = NULL;
uint8_t *kpart_data = NULL;
uint64_t kpart_size = 0;
uint8_t *vmlinuz_buf = NULL;
uint64_t vmlinuz_size = 0;
uint8_t *t_config_data;
uint64_t t_config_size;
uint8_t *t_bootloader_data;
uint64_t t_bootloader_size;
uint64_t vmlinuz_header_size = 0;
uint64_t vmlinuz_header_address = 0;
uint64_t vmlinuz_header_offset = 0;
VbKernelPreambleHeader *preamble = NULL;
uint8_t *kblob_data = NULL;
uint64_t kblob_size = 0;
uint8_t *vblock_data = NULL;
uint64_t vblock_size = 0;
uint32_t flags = 0;
FILE *f;
while (((i = getopt_long(argc, argv, ":", long_opts, NULL)) != -1) &&
!parse_error) {
switch (i) {
default:
case '?':
/* Unhandled option */
parse_error = 1;
break;
case 0:
/* silently handled option */
break;
case OPT_MODE_PACK:
case OPT_MODE_REPACK:
case OPT_MODE_VERIFY:
case OPT_MODE_GET_VMLINUZ:
if (mode && (mode != i)) {
fprintf(stderr,
"Only one mode can be specified\n");
parse_error = 1;
break;
}
mode = i;
filename = optarg;
break;
case OPT_ARCH:
/* check the first 3 characters to also detect x86_64 */
if ((!strncasecmp(optarg, "x86", 3)) ||
(!strcasecmp(optarg, "amd64")))
arch = ARCH_X86;
else if ((!strcasecmp(optarg, "arm")) ||
(!strcasecmp(optarg, "aarch64")))
arch = ARCH_ARM;
else if (!strcasecmp(optarg, "mips"))
arch = ARCH_MIPS;
else {
fprintf(stderr,
"Unknown architecture string: %s\n",
optarg);
parse_error = 1;
}
break;
case OPT_OLDBLOB:
oldfile = optarg;
break;
case OPT_KLOADADDR:
kernel_body_load_address = strtoul(optarg, &e, 0);
if (!*optarg || (e && *e)) {
fprintf(stderr, "Invalid --kloadaddr\n");
parse_error = 1;
}
break;
case OPT_KEYBLOCK:
keyblock_file = optarg;
break;
case OPT_SIGNPUBKEY:
signpubkey_file = optarg;
break;
case OPT_SIGNPRIVATE:
signprivkey_file = optarg;
break;
case OPT_VMLINUZ:
vmlinuz_file = optarg;
break;
case OPT_FLAGS:
flags = (uint32_t)strtoul(optarg, &e, 0);
if (!*optarg || (e && *e)) {
fprintf(stderr, "Invalid --flags\n");
parse_error = 1;
}
break;
case OPT_BOOTLOADER:
bootloader_file = optarg;
break;
case OPT_CONFIG:
config_file = optarg;
break;
case OPT_VBLOCKONLY:
opt_vblockonly = 1;
break;
case OPT_VERSION:
version_str = optarg;
version = strtoul(optarg, &e, 0);
if (!*optarg || (e && *e)) {
fprintf(stderr, "Invalid --version\n");
parse_error = 1;
}
break;
case OPT_MINVERSION:
min_version = strtoul(optarg, &e, 0);
if (!*optarg || (e && *e)) {
fprintf(stderr, "Invalid --minversion\n");
parse_error = 1;
}
break;
case OPT_PAD:
opt_pad = strtoul(optarg, &e, 0);
if (!*optarg || (e && *e)) {
fprintf(stderr, "Invalid --pad\n");
parse_error = 1;
}
break;
case OPT_VMLINUZ_OUT:
vmlinuz_out_file = optarg;
}
}
if (parse_error) {
print_help(argv[0]);
return 1;
}
switch (mode) {
case OPT_MODE_PACK:
if (!keyblock_file)
Fatal("Missing required keyblock file.\n");
t_keyblock = (VbKeyBlockHeader *)ReadFile(keyblock_file, 0);
if (!t_keyblock)
Fatal("Error reading key block.\n");
if (!signprivkey_file)
Fatal("Missing required signprivate file.\n");
signpriv_key = PrivateKeyRead(signprivkey_file);
if (!signpriv_key)
Fatal("Error reading signing key.\n");
if (!config_file)
Fatal("Missing required config file.\n");
Debug("Reading %s\n", config_file);
t_config_data =
ReadConfigFile(config_file, &t_config_size);
if (!t_config_data)
Fatal("Error reading config file.\n");
if (!bootloader_file)
Fatal("Missing required bootloader file.\n");
Debug("Reading %s\n", bootloader_file);
t_bootloader_data = ReadFile(bootloader_file,
&t_bootloader_size);
if (!t_bootloader_data)
Fatal("Error reading bootloader file.\n");
Debug(" bootloader file size=0x%" PRIx64 "\n",
t_bootloader_size);
if (!vmlinuz_file)
Fatal("Missing required vmlinuz file.\n");
Debug("Reading %s\n", vmlinuz_file);
vmlinuz_buf = ReadFile(vmlinuz_file, &vmlinuz_size);
if (!vmlinuz_buf)
Fatal("Error reading vmlinuz file.\n");
Debug(" vmlinuz file size=0x%" PRIx64 "\n",
vmlinuz_size);
if (!vmlinuz_size)
Fatal("Empty vmlinuz file\n");
kblob_data = CreateKernelBlob(
vmlinuz_buf, vmlinuz_size,
arch, kernel_body_load_address,
t_config_data, t_config_size,
t_bootloader_data, t_bootloader_size,
&kblob_size);
if (!kblob_data)
Fatal("Unable to create kernel blob\n");
Debug("kblob_size = 0x%" PRIx64 "\n", kblob_size);
vblock_data = SignKernelBlob(kblob_data, kblob_size, opt_pad,
version, kernel_body_load_address,
t_keyblock, signpriv_key, flags,
&vblock_size);
if (!vblock_data)
Fatal("Unable to sign kernel blob\n");
Debug("vblock_size = 0x%" PRIx64 "\n", vblock_size);
if (opt_vblockonly)
rv = WriteSomeParts(filename,
vblock_data, vblock_size,
NULL, 0);
else
rv = WriteSomeParts(filename,
vblock_data, vblock_size,
kblob_data, kblob_size);
return rv;
case OPT_MODE_REPACK:
/* Required */
if (!signprivkey_file)
Fatal("Missing required signprivate file.\n");
signpriv_key = PrivateKeyRead(signprivkey_file);
if (!signpriv_key)
Fatal("Error reading signing key.\n");
if (!oldfile)
Fatal("Missing previously packed blob.\n");
/* Load the kernel partition */
kpart_data = ReadOldKPartFromFileOrDie(oldfile, &kpart_size);
/* Make sure we have a kernel partition */
if (FILE_TYPE_KERN_PREAMBLE !=
futil_file_type_buf(kpart_data, kpart_size))
Fatal("%s is not a kernel blob\n", oldfile);
kblob_data = UnpackKPart(kpart_data, kpart_size, opt_pad,
&keyblock, &preamble, &kblob_size);
if (!kblob_data)
Fatal("Unable to unpack kernel partition\n");
kernel_body_load_address = preamble->body_load_address;
/* Update the config if asked */
if (config_file) {
Debug("Reading %s\n", config_file);
t_config_data =
ReadConfigFile(config_file, &t_config_size);
if (!t_config_data)
Fatal("Error reading config file.\n");
if (0 != UpdateKernelBlobConfig(
kblob_data, kblob_size,
t_config_data, t_config_size))
Fatal("Unable to update config\n");
}
if (!version_str)
version = preamble->kernel_version;
if (VbKernelHasFlags(preamble) == VBOOT_SUCCESS)
flags = preamble->flags;
if (keyblock_file) {
t_keyblock =
(VbKeyBlockHeader *)ReadFile(keyblock_file, 0);
if (!t_keyblock)
Fatal("Error reading key block.\n");
}
/* Reuse previous body size */
vblock_data = SignKernelBlob(kblob_data, kblob_size, opt_pad,
version, kernel_body_load_address,
t_keyblock ? t_keyblock : keyblock,
signpriv_key, flags, &vblock_size);
if (!vblock_data)
Fatal("Unable to sign kernel blob\n");
if (opt_vblockonly)
rv = WriteSomeParts(filename,
vblock_data, vblock_size,
NULL, 0);
else
rv = WriteSomeParts(filename,
vblock_data, vblock_size,
kblob_data, kblob_size);
return rv;
case OPT_MODE_VERIFY:
/* Optional */
if (signpubkey_file) {
signpub_key = PublicKeyRead(signpubkey_file);
if (!signpub_key)
Fatal("Error reading public key.\n");
}
/* Do it */
/* Load the kernel partition */
kpart_data = ReadOldKPartFromFileOrDie(filename, &kpart_size);
kblob_data = UnpackKPart(kpart_data, kpart_size, opt_pad,
0, 0, &kblob_size);
if (!kblob_data)
Fatal("Unable to unpack kernel partition\n");
rv = VerifyKernelBlob(kblob_data, kblob_size,
signpub_key, keyblock_file, min_version);
return rv;
case OPT_MODE_GET_VMLINUZ:
if (!vmlinuz_out_file) {
fprintf(stderr,
"USE: vbutil_kernel --get-vmlinuz <file> "
"--vmlinuz-out <file>\n");
print_help(argv[0]);
return 1;
}
kpart_data = ReadOldKPartFromFileOrDie(filename, &kpart_size);
kblob_data = UnpackKPart(kpart_data, kpart_size, opt_pad,
&keyblock, &preamble, &kblob_size);
if (!kblob_data)
Fatal("Unable to unpack kernel partition\n");
f = fopen(vmlinuz_out_file, "wb");
if (!f) {
VbExError("Can't open output file %s\n",
vmlinuz_out_file);
return 1;
}
/* Now stick 16-bit header followed by kernel block into
output */
if (VbGetKernelVmlinuzHeader(preamble,
&vmlinuz_header_address,
&vmlinuz_header_size)
!= VBOOT_SUCCESS) {
Fatal("Unable to retrieve Vmlinuz Header!");
}
if (vmlinuz_header_size) {
// verify that the 16-bit header is included in the
// kblob (to make sure that it's included in the
// signature)
if (VerifyVmlinuzInsideKBlob(preamble->body_load_address,
kblob_size,
vmlinuz_header_address,
vmlinuz_header_size)) {
VbExError("Vmlinuz header not signed!\n");
fclose(f);
unlink(vmlinuz_out_file);
return 1;
}
// calculate the vmlinuz_header offset from
// the beginning of the kpart_data. The kblob doesn't
// include the body_load_offset, but does include
// the keyblock and preamble sections.
vmlinuz_header_offset = vmlinuz_header_address -
preamble->body_load_address +
keyblock->key_block_size +
preamble->preamble_size;
errcount |=
(1 != fwrite(kpart_data + vmlinuz_header_offset,
vmlinuz_header_size,
1,
f));
}
errcount |= (1 != fwrite(kblob_data,
kblob_size,
1,
f));
if (errcount) {
VbExError("Can't write output file %s\n",
vmlinuz_out_file);
fclose(f);
unlink(vmlinuz_out_file);
return 1;
}
fclose(f);
return 0;
}
fprintf(stderr,
"You must specify a mode: "
"--pack, --repack, --verify, or --get-vmlinuz\n");
print_help(argv[0]);
return 1;
}
DECLARE_FUTIL_COMMAND(vbutil_kernel, do_vbutil_kernel,
VBOOT_VERSION_1_0,
"Creates, signs, and verifies the kernel partition",
print_help);