blob: 83a76f84d3aac0c0c1455ba22e21e51b644ae268 [file] [log] [blame]
/* 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 */
#if !defined(HAVE_MACOS) && !defined(__FreeBSD__)
#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 "2common.h"
#include "2sysincludes.h"
#include "file_type.h"
#include "futility.h"
#include "host_common.h"
#include "kernel_blob.h"
#include "vb1_helper.h"
#include "vb2_common.h"
/* 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,
OPT_HELP,
};
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},
{"vmlinuz-out", 1, 0, OPT_VMLINUZ_OUT},
{"flags", 1, 0, OPT_FLAGS},
{"help", 0, 0, OPT_HELP},
{NULL, 0, 0, 0}
};
static const char usage[] =
"\n"
"Usage: " MYNAME " %s --pack <file> [PARAMETERS]\n"
"\n"
" Required parameters:\n"
" --keyblock <file> Keyblock 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> Keyblock 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 keyblock,\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(int argc, char *argv[])
{
printf(usage, argv[0], argv[0], argv[0], argv[0]);
}
/* 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,
uint32_t *size_ptr)
{
FILE *fp = NULL;
struct stat statbuf;
uint8_t *buf;
uint32_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)) {
#if !defined(HAVE_MACOS) && !defined(__FreeBSD__)
int fd = open(filename, O_RDONLY);
if (fd >= 0) {
ioctl(fd, BLKGETSIZE64, &file_size);
close(fd);
}
#endif
} else {
file_size = statbuf.st_size;
}
VB2_DEBUG("%s size is %#x\n", filename, file_size);
if (file_size < opt_pad)
FATAL("%s is too small to be a valid kernel blob\n", filename);
VB2_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;
fclose(fp);
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;
uint32_t min_version = 0;
char *e;
int i = 0;
int errcount = 0;
int rv;
struct vb2_keyblock *keyblock = NULL;
struct vb2_keyblock *t_keyblock = NULL;
struct vb2_private_key *signpriv_key = NULL;
struct vb2_packed_key *signpub_key = NULL;
uint8_t *kpart_data = NULL;
uint32_t kpart_size = 0;
uint8_t *vmlinuz_buf = NULL;
uint32_t vmlinuz_size = 0;
uint8_t *t_config_data;
uint32_t t_config_size;
uint8_t *t_bootloader_data;
uint32_t t_bootloader_size;
uint32_t vmlinuz_header_size = 0;
uint64_t vmlinuz_header_address = 0;
uint32_t vmlinuz_header_offset = 0;
struct vb2_kernel_preamble *preamble = NULL;
uint8_t *kblob_data = NULL;
uint32_t kblob_size = 0;
uint8_t *vblock_data = NULL;
uint32_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_HELP:
print_help(argc, argv);
return !!parse_error;
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;
/* check the first 3 characters to also detect arm64 */
else if ((!strncasecmp(optarg, "arm", 3)) ||
(!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(argc, argv);
return 1;
}
switch (mode) {
case OPT_MODE_PACK:
if (!keyblock_file)
FATAL("Missing required keyblock file.\n");
t_keyblock = (struct vb2_keyblock *)ReadFile(keyblock_file, 0);
if (!t_keyblock)
FATAL("Error reading keyblock.\n");
if (!signprivkey_file)
FATAL("Missing required signprivate file.\n");
signpriv_key = vb2_read_private_key(signprivkey_file);
if (!signpriv_key)
FATAL("Error reading signing key.\n");
if (!config_file)
FATAL("Missing required config file.\n");
VB2_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");
VB2_DEBUG("Reading %s\n", bootloader_file);
if (VB2_SUCCESS != vb2_read_file(bootloader_file,
&t_bootloader_data,
&t_bootloader_size))
FATAL("Error reading bootloader file.\n");
VB2_DEBUG(" bootloader file size=%#x\n", t_bootloader_size);
if (!vmlinuz_file)
FATAL("Missing required vmlinuz file.\n");
VB2_DEBUG("Reading %s\n", vmlinuz_file);
if (VB2_SUCCESS !=
vb2_read_file(vmlinuz_file, &vmlinuz_buf, &vmlinuz_size))
FATAL("Error reading vmlinuz file.\n");
VB2_DEBUG(" vmlinuz file size=%#x\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");
VB2_DEBUG("kblob_size = %#x\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");
VB2_DEBUG("vblock_size = %#x\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);
free(vmlinuz_buf);
free(t_config_data);
free(t_bootloader_data);
free(vblock_data);
vb2_free_private_key(signpriv_key);
return rv;
case OPT_MODE_REPACK:
/* Required */
if (!signprivkey_file)
FATAL("Missing required signprivate file.\n");
signpriv_key = vb2_read_private_key(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 = unpack_kernel_partition(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) {
VB2_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 (vb2_kernel_get_flags(preamble))
flags = vb2_kernel_get_flags(preamble);
if (keyblock_file) {
t_keyblock = (struct vb2_keyblock *)
ReadFile(keyblock_file, 0);
if (!t_keyblock)
FATAL("Error reading keyblock.\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 = vb2_read_packed_key(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 = unpack_kernel_partition(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(argc, argv);
return 1;
}
kpart_data = ReadOldKPartFromFileOrDie(filename, &kpart_size);
kblob_data = unpack_kernel_partition(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) {
FATAL("Can't open output file %s\n", vmlinuz_out_file);
return 1;
}
/* Now stick 16-bit header followed by kernel block into
output */
vb2_kernel_get_vmlinuz_header(preamble,
&vmlinuz_header_address,
&vmlinuz_header_size);
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 (vb2_verify_member_inside(
(void *)preamble->body_load_address,
kblob_size,
(void *)vmlinuz_header_address,
vmlinuz_header_size, 0, 0)) {
fclose(f);
unlink(vmlinuz_out_file);
FATAL("Vmlinuz header not signed!\n");
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->keyblock_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) {
fclose(f);
unlink(vmlinuz_out_file);
FATAL("Can't write output file %s\n", 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(argc, argv);
return 1;
}
DECLARE_FUTIL_COMMAND(vbutil_kernel, do_vbutil_kernel, VBOOT_VERSION_1_0,
"Creates, signs, and verifies the kernel partition");