| /* 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. | |
| * | |
| * 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" | |
| " --config <file> Command line file\n" | |
| " --arch <arch> Cpu architecture (default x86)\n" | |
| "\n" | |
| " Optional:\n" | |
| " --kloadaddr <address> Assign kernel body load address\n" | |
| " --bootloader <file> Bootloader stub\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 (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_info = 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)) { | |
| ERROR("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 { | |
| ERROR("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)) { | |
| ERROR("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_info = optarg; | |
| break; | |
| case OPT_VMLINUZ: | |
| vmlinuz_file = optarg; | |
| break; | |
| case OPT_FLAGS: | |
| flags = (uint32_t)strtoul(optarg, &e, 0); | |
| if (!*optarg || (e && *e)) { | |
| ERROR("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)) { | |
| ERROR("Invalid --version\n"); | |
| parse_error = 1; | |
| } | |
| break; | |
| case OPT_MINVERSION: | |
| min_version = strtoul(optarg, &e, 0); | |
| if (!*optarg || (e && *e)) { | |
| ERROR("Invalid --minversion\n"); | |
| parse_error = 1; | |
| } | |
| break; | |
| case OPT_PAD: | |
| opt_pad = strtoul(optarg, &e, 0); | |
| if (!*optarg || (e && *e)) { | |
| ERROR("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_info) | |
| FATAL("Missing required signprivate info.\n"); | |
| signpriv_key = vb2_read_private_key(signprivkey_info); | |
| 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) { | |
| 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); | |
| } else { | |
| t_bootloader_data = NULL; | |
| t_bootloader_size = 0; | |
| VB2_DEBUG("No external bootloader file passed in.\n"); | |
| } | |
| 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_info) | |
| FATAL("Missing required signprivate info.\n"); | |
| if (bootloader_file) | |
| FATAL("--repack doesn't support --bootloader.\n"); | |
| signpriv_key = vb2_read_private_key(signprivkey_info); | |
| 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, | |
| &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 (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, | |
| 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) { | |
| ERROR("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, | |
| &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; | |
| } | |
| ERROR("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"); |