/* 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 #include #include #include /* For PRIu64 */ #ifndef HAVE_MACOS #include /* For BLKGETSIZE64 */ #endif #include #include #include #include #include #include #include "2sysincludes.h" #include "2common.h" #include "file_type.h" #include "futility.h" #include "host_common.h" #include "kernel_blob.h" #include "vb1_helper.h" #include "vb2_common.h" #include "vb2_struct.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 [PARAMETERS]\n" "\n" " Required parameters:\n" " --keyblock Key block in .keyblock format\n" " --signprivate Private key to sign kernel data,\n" " in .vbprivk format\n" " --version Kernel version\n" " --vmlinuz Linux kernel bzImage file\n" " --bootloader Bootloader stub\n" " --config Command line file\n" " --arch Cpu architecture (default x86)\n" "\n" " Optional:\n" " --kloadaddr
Assign kernel body load address\n" " --pad 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 [PARAMETERS]\n" "\n" " Required parameters:\n" " --signprivate Private key to sign kernel data,\n" " in .vbprivk format\n" " --oldblob Previously packed kernel blob\n" " (including verfication blob)\n" "\n" " Optional:\n" " --keyblock Key block in .keyblock format\n" " --config New command line file\n" " --version Kernel version\n" " --kloadaddr
Assign kernel body load address\n" " --pad Verification blob size in bytes\n" " --vblockonly Emit just the verification blob\n" "\nOR\n\n" "Usage: " MYNAME " %s --verify [PARAMETERS]\n" "\n" " Optional:\n" " --signpubkey " " Public key to verify kernel keyblock,\n" " in .vbpubk format\n" " --verbose Print a more detailed report\n" " --keyblock Outputs the verified key block,\n" " in .keyblock format\n" " --pad Verification padding size in bytes\n" " --minversion Minimum combined kernel key version\n" "\nOR\n\n" "Usage: " MYNAME " %s --get-vmlinuz [PARAMETERS]\n" "\n" " Required parameters:\n" " --vmlinuz-out 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)) { #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; } VB2_DEBUG("%s size is 0x%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 key block.\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=0x%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=0x%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 = 0x%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 = 0x%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 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 = 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 " "--vmlinuz-out \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 (VerifyVmlinuzInsideKBlob(preamble->body_load_address, kblob_size, vmlinuz_header_address, vmlinuz_header_size)) { 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");