| /* 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__) && !defined(__OpenBSD__) |
| #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" |
| |
| /* 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__) && !defined(__OpenBSD__) |
| 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"); |