blob: 7e7fa2ea4be5c468d81da2dd94e5064b33a9cb7e [file] [log] [blame]
/* Copyright 2025 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Functions to load and verify an Android kernel.
*/
#include "2api.h"
#include "2avb.h"
#include "2common.h"
#include "2load_android_kernel.h"
#include "2misc.h"
#include "cgptlib.h"
#include "cgptlib_internal.h"
#include "gpt_misc.h"
#include "vboot_api.h"
#include "vb2_android_bootimg.h"
#define GPT_ENT_NAME_ANDROID_A_SUFFIX "_a"
#define GPT_ENT_NAME_ANDROID_B_SUFFIX "_b"
#define VERIFIED_BOOT_PROPERTY_NAME "androidboot.verifiedbootstate"
#define SLOT_SUFFIX_BOOT_PROPERTY_NAME "androidboot.slot_suffix"
#define ANDROID_FORCE_NORMAL_BOOT_PROPERTY_NAME "androidboot.force_normal_boot"
/* Android BCB (boot control block) commands */
enum vb2_boot_command {
VB2_BOOT_CMD_NORMAL_BOOT,
VB2_BOOT_CMD_RECOVERY_BOOT,
VB2_BOOT_CMD_BOOTLOADER_BOOT,
};
/* BCB structure from Android recovery bootloader_message.h */
struct bootloader_message {
char command[32];
char status[32];
char recovery[768];
char stage[32];
char reserved[1184];
};
_Static_assert(sizeof(struct bootloader_message) == 2048,
"bootloader_message size is incorrect");
/* Possible values of BCB command */
#define BCB_CMD_BOOTONCE_BOOTLOADER "bootonce-bootloader"
#define BCB_CMD_BOOT_RECOVERY "boot-recovery"
static vb2_error_t vb2_map_libavb_errors(AvbSlotVerifyResult avb_error)
{
/* Map AVB error into VB2 */
switch (avb_error) {
case AVB_SLOT_VERIFY_RESULT_OK:
return VB2_SUCCESS;
case AVB_SLOT_VERIFY_RESULT_ERROR_OOM:
return VB2_ERROR_AVB_OOM;
case AVB_SLOT_VERIFY_RESULT_ERROR_IO:
return VB2_ERROR_AVB_ERROR_IO;
case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
return VB2_ERROR_AVB_ERROR_VERIFICATION;
case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
return VB2_ERROR_AVB_ERROR_ROLLBACK_INDEX;
case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
return VB2_ERROR_AVB_ERROR_PUBLIC_KEY_REJECTED;
case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA:
return VB2_ERROR_AVB_ERROR_INVALID_METADATA;
case AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION:
return VB2_ERROR_AVB_ERROR_UNSUPPORTED_VERSION;
case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT:
return VB2_ERROR_AVB_ERROR_INVALID_ARGUMENT;
default:
return VB2_ERROR_AVB_ERROR_VERIFICATION;
}
}
static enum vb2_boot_command bcb_command(AvbOps *ops)
{
struct bootloader_message bcb;
AvbIOResult io_ret;
size_t num_bytes_read;
enum vb2_boot_command cmd;
io_ret = ops->read_from_partition(ops,
GptPartitionNames[GPT_ANDROID_MISC],
0,
sizeof(bcb),
&bcb,
&num_bytes_read);
if (io_ret != AVB_IO_RESULT_OK || num_bytes_read != sizeof(bcb)) {
/*
* TODO(b/349304841): Handle IO errors, for now just try to boot
* normally
*/
VB2_DEBUG("Cannot read misc partition, err: %d\n", io_ret);
return VB2_BOOT_CMD_NORMAL_BOOT;
}
/* BCB command field is for the bootloader */
if (!strcmp(bcb.command, BCB_CMD_BOOT_RECOVERY)) {
cmd = VB2_BOOT_CMD_RECOVERY_BOOT;
} else if (!strcmp(bcb.command, BCB_CMD_BOOTONCE_BOOTLOADER)) {
cmd = VB2_BOOT_CMD_BOOTLOADER_BOOT;
} else {
/* If empty or unknown command, just boot normally */
if (bcb.command[0] != '\0')
VB2_DEBUG("Unknown boot command \"%.*s\". Use normal boot.\n",
(int)sizeof(bcb.command), bcb.command);
cmd = VB2_BOOT_CMD_NORMAL_BOOT;
}
return cmd;
}
/*
* Copy bootconfig into separate buffer, it can be overwritten when ramdisks
* are concatenated. Bootconfig buffer will be processed by depthcharge.
*/
static vb2_error_t save_bootconfig(struct vendor_boot_img_hdr_v4 *vendor_hdr,
size_t total_size,
struct vb2_kernel_params *params)
{
uint8_t *bootconfig;
size_t bootconfig_offset;
uint32_t page_size = vendor_hdr->page_size;
if (!vendor_hdr->bootconfig_size)
return VB2_SUCCESS;
bootconfig_offset = VB2_ALIGN_UP(sizeof(struct vendor_boot_img_hdr_v4), page_size) +
VB2_ALIGN_UP(vendor_hdr->vendor_ramdisk_size, page_size) +
VB2_ALIGN_UP(vendor_hdr->dtb_size, page_size) +
VB2_ALIGN_UP(vendor_hdr->vendor_ramdisk_table_size, page_size);
if (bootconfig_offset > total_size ||
total_size - bootconfig_offset < vendor_hdr->bootconfig_size) {
VB2_DEBUG("Broken 'vendor_boot' image\n");
return VB2_ERROR_ANDROID_BROKEN_VENDOR_BOOT;
}
params->bootconfig = malloc(vendor_hdr->bootconfig_size);
if (!params->bootconfig) {
VB2_DEBUG("Cannot malloc %u bytes for bootconfig", vendor_hdr->bootconfig_size);
return VB2_ERROR_ANDROID_MEMORY_ALLOC;
}
bootconfig = (uint8_t *)vendor_hdr + bootconfig_offset;
memcpy(params->bootconfig, bootconfig, vendor_hdr->bootconfig_size);
params->bootconfig_size = vendor_hdr->bootconfig_size;
return VB2_SUCCESS;
}
static bool gki_is_recovery_boot(enum vb2_boot_command boot_command)
{
switch (boot_command) {
case VB2_BOOT_CMD_NORMAL_BOOT:
return false;
case VB2_BOOT_CMD_BOOTLOADER_BOOT:
/*
* TODO(b/358088653): We should enter fastboot mode and clear
* BCB command in misc partition. For now ignore that and boot
* to recovery where fastbootd should be available.
*/
return true;
case VB2_BOOT_CMD_RECOVERY_BOOT:
return true;
}
return false;
}
static bool gki_ramdisk_fragment_needed(struct vendor_ramdisk_table_entry_v4 *fragment,
bool recovery_boot)
{
/* Ignore all other properties except ramdisk type */
switch (fragment->ramdisk_type) {
case VENDOR_RAMDISK_TYPE_PLATFORM:
case VENDOR_RAMDISK_TYPE_DLKM:
return true;
case VENDOR_RAMDISK_TYPE_RECOVERY:
return recovery_boot;
default:
printf("Unknown ramdisk type 0x%x\n", fragment->ramdisk_type);
return false;
}
}
/*
* This function removes unnecessary ramdisks from ramdisk table, concatenates rest of
* them and returns start and end of new ramdisk.
*/
static vb2_error_t prepare_vendor_ramdisks(struct vendor_boot_img_hdr_v4 *vendor_hdr,
size_t total_size,
bool recovery_boot,
uint8_t **vendor_ramdisk,
uint8_t **vendor_ramdisk_end)
{
uint32_t ramdisk_offset;
uint32_t ramdisk_table_offset;
uint32_t ramdisk_table_size = vendor_hdr->vendor_ramdisk_table_size;
uint32_t ramdisk_table_entry_size = vendor_hdr->vendor_ramdisk_table_entry_size;
uint32_t ramdisk_table_entry_num = vendor_hdr->vendor_ramdisk_table_entry_num;
uint32_t page_size = vendor_hdr->page_size;
uintptr_t fragment_ptr;
/* Calculate address offset of vendor_ramdisk section on vendor_boot partition */
ramdisk_offset = VB2_ALIGN_UP(sizeof(struct vendor_boot_img_hdr_v4), page_size);
ramdisk_table_offset = ramdisk_offset +
VB2_ALIGN_UP(vendor_hdr->vendor_ramdisk_size, page_size) +
VB2_ALIGN_UP(vendor_hdr->dtb_size, page_size);
/* Check if vendor ramdisk table is correct */
if (ramdisk_offset > total_size ||
ramdisk_table_offset > total_size ||
ramdisk_table_entry_size < sizeof(struct vendor_ramdisk_table_entry_v4) ||
total_size - ramdisk_offset < vendor_hdr->vendor_ramdisk_size ||
total_size - ramdisk_table_offset < ramdisk_table_size ||
ramdisk_table_size < (ramdisk_table_entry_num * ramdisk_table_entry_size)) {
VB2_DEBUG("Broken 'vendor_boot' image\n");
return VB2_ERROR_ANDROID_BROKEN_VENDOR_BOOT;
}
*vendor_ramdisk = (uint8_t *)vendor_hdr + ramdisk_offset;
*vendor_ramdisk_end = *vendor_ramdisk;
fragment_ptr = (uintptr_t)vendor_hdr + ramdisk_table_offset;
/* Go through all ramdisk fragments and keep only the required ones */
for (int i = 0; i < ramdisk_table_entry_num;
fragment_ptr += ramdisk_table_entry_size, i++) {
struct vendor_ramdisk_table_entry_v4 *fragment;
uint8_t *fragment_src;
fragment = (struct vendor_ramdisk_table_entry_v4 *)fragment_ptr;
if (!gki_ramdisk_fragment_needed(fragment, recovery_boot))
continue;
uint32_t fragment_size = fragment->ramdisk_size;
uint32_t fragment_offset = fragment->ramdisk_offset;
if (fragment_offset > vendor_hdr->vendor_ramdisk_size ||
vendor_hdr->vendor_ramdisk_size - fragment_offset < fragment_size) {
VB2_DEBUG("Incorrect fragment - offset:%x size:%x, ramdisk_size: %x\n",
fragment_offset, fragment_size,
vendor_hdr->vendor_ramdisk_size);
}
fragment_src = *vendor_ramdisk + fragment_offset;
if (*vendor_ramdisk_end != fragment_src)
/*
* A fragment was skipped before, we need to move current one
* at the correct place.
*/
memmove(*vendor_ramdisk_end, fragment_src, fragment_size);
/* Update location of the end of vendor ramdisk */
*vendor_ramdisk_end += fragment_size;
}
return VB2_SUCCESS;
}
/*
* This function validates the partitions magic numbers and move them into place requested
* from linux.
*/
static vb2_error_t rearrange_partitions(AvbOps *avb_ops,
struct vb2_kernel_params *params,
bool recovery_boot)
{
struct vendor_boot_img_hdr_v4 *vendor_hdr;
struct boot_img_hdr_v4 *init_hdr;
size_t vendor_boot_size, init_boot_size;
uint8_t *vendor_ramdisk_end = 0;
if (vb2_android_get_buffer(avb_ops, GPT_ANDROID_VENDOR_BOOT, (void **)&vendor_hdr,
&vendor_boot_size) ||
vb2_android_get_buffer(avb_ops, GPT_ANDROID_INIT_BOOT, (void **)&init_hdr,
&init_boot_size)) {
VB2_DEBUG("Cannot get information about preloaded paritition\n");
return VB2_ERROR_ANDROID_RAMDISK_ERROR;
}
if (vendor_boot_size < sizeof(*vendor_hdr) ||
memcmp(vendor_hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE)) {
VB2_DEBUG("Incorrect magic or size (%zx) of 'vendor_boot' image\n",
vendor_boot_size);
return VB2_ERROR_ANDROID_BROKEN_VENDOR_BOOT;
}
/* Save bootconfig for depthcharge, it can be overwritten when ramdisk are moved */
VB2_TRY(save_bootconfig(vendor_hdr, vendor_boot_size, params));
/* Remove unused ramdisks */
VB2_TRY(prepare_vendor_ramdisks(vendor_hdr, vendor_boot_size, recovery_boot,
&params->ramdisk, &vendor_ramdisk_end));
params->ramdisk_size = vendor_ramdisk_end - params->ramdisk;
/* Validate init_boot partition */
if (init_boot_size < BOOT_HEADER_SIZE ||
init_boot_size - BOOT_HEADER_SIZE < init_hdr->ramdisk_size ||
init_hdr->kernel_size != 0 ||
memcmp(init_hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
VB2_DEBUG("Incorrect 'init_boot' header, total size: %zx\n",
init_boot_size);
return VB2_ERROR_ANDROID_BROKEN_INIT_BOOT;
}
/* On init_boot there's no kernel, so ramdisk follows the header */
uint8_t *init_boot_ramdisk = (uint8_t *)init_hdr + BOOT_HEADER_SIZE;
size_t init_boot_ramdisk_size = init_hdr->ramdisk_size;
/*
* Move init_boot ramdisk to directly follow the vendor_boot ramdisk.
* This is a requirement from Android system. The cpio/gzip/lz4
* compression formats support this type of concatenation. After
* the kernel decompresses, it extracts concatenated file into
* an initramfs, which results in a file structure that's a generic
* ramdisk (from init_boot) overlaid on the vendor ramdisk (from
* vendor_boot) file structure.
*/
VB2_ASSERT(vendor_ramdisk_end < init_boot_ramdisk);
memmove(vendor_ramdisk_end, init_boot_ramdisk, init_boot_ramdisk_size);
params->ramdisk_size += init_boot_ramdisk_size;
/* Save vendor cmdline for booting */
vendor_hdr->cmdline[sizeof(vendor_hdr->cmdline) - 1] = '\0';
params->vendor_cmdline_buffer = (char *)vendor_hdr->cmdline;
return VB2_SUCCESS;
}
vb2_error_t vb2_load_android(struct vb2_context *ctx, GptData *gpt, GptEntry *entry,
struct vb2_kernel_params *params, vb2ex_disk_handle_t disk_handle)
{
AvbSlotVerifyData *verify_data = NULL;
AvbOps *avb_ops;
AvbSlotVerifyFlags avb_flags;
AvbSlotVerifyResult result;
vb2_error_t rv;
const char *boot_partitions[] = {
GptPartitionNames[GPT_ANDROID_BOOT],
GptPartitionNames[GPT_ANDROID_INIT_BOOT],
GptPartitionNames[GPT_ANDROID_VENDOR_BOOT],
NULL,
};
const char *slot_suffix = NULL;
bool need_verification = vb2_need_kernel_verification(ctx);
/* Update flags to mark loaded GKI image */
params->flags = VB2_KERNEL_TYPE_BOOTIMG;
const char *vbmeta = GptPartitionNames[GPT_ANDROID_VBMETA];
if (GptEntryHasName(entry, vbmeta, GPT_ENT_NAME_ANDROID_A_SUFFIX))
slot_suffix = GPT_ENT_NAME_ANDROID_A_SUFFIX;
else if (GptEntryHasName(entry, vbmeta, GPT_ENT_NAME_ANDROID_B_SUFFIX))
slot_suffix = GPT_ENT_NAME_ANDROID_B_SUFFIX;
else
return VB2_ERROR_ANDROID_INVALID_SLOT_SUFFIX;
avb_ops = vboot_avb_ops_new(ctx, params, gpt, disk_handle, slot_suffix);
if (!avb_ops)
return VB2_ERROR_ANDROID_MEMORY_ALLOC;
avb_flags = AVB_SLOT_VERIFY_FLAGS_NONE;
if (!need_verification)
avb_flags |= AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR;
result = avb_slot_verify(avb_ops, boot_partitions, slot_suffix, avb_flags,
AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
&verify_data);
/* Ignore verification errors in developer mode */
if (!need_verification) {
switch (result) {
case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
result = AVB_SLOT_VERIFY_RESULT_OK;
break;
default:
break;
}
}
/* Map AVB return code into VB2 code */
rv = vb2_map_libavb_errors(result);
if (rv != VB2_SUCCESS)
goto out;
/* Check "misc" partition for boot type */
enum vb2_boot_command boot_command = bcb_command(avb_ops);
bool recovery_boot = gki_is_recovery_boot(boot_command);
/*
* Before booting we need to rearrange buffers with partition data, which includes:
* - save bootconfig in separate buffer, so depthcharge can modify it
* - remove unused ramdisks depending on boot type (normal/recovery)
* - concatenate ramdisks from vendor_boot & init_boot partitions
*/
rv = rearrange_partitions(avb_ops, params, recovery_boot);
if (rv)
goto out;
/*
* TODO(b/335901799): Add support for marking verifiedbootstate yellow
*/
int chars = snprintf(params->vboot_cmdline_buffer, params->vboot_cmdline_size,
"%s %s=%s %s=%s %s=%s", verify_data->cmdline,
VERIFIED_BOOT_PROPERTY_NAME,
need_verification ? "green" : "orange",
SLOT_SUFFIX_BOOT_PROPERTY_NAME, slot_suffix,
ANDROID_FORCE_NORMAL_BOOT_PROPERTY_NAME, recovery_boot ? "0" : "1"
);
if (chars < 0 || chars >= params->vboot_cmdline_size) {
VB2_DEBUG("ERROR: Command line doesn't fit provided buffer: %s\n",
verify_data->cmdline);
rv = VB2_ERROR_ANDROID_CMDLINE_BUF_TOO_SMALL;
}
out:
/* No need for slot data */
if (verify_data != NULL)
avb_slot_verify_data_free(verify_data);
vboot_avb_ops_free(avb_ops);
return rv;
}
OSZAR »