Re: [PATCH v3] boot: android: Add support for the bootflow of Android 13+

Mattijs Korpershoek <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Hi Valentin,

Thank you for the patch and for contributing to U-Boot.

On Fri, Aug 14, 2026 at 04:07, Valentin Liu <[email protected]> wrote:

> The devices launching Android 13+ were using a new partition
> named init_boot to store generic ramdisk.
>
> In the new bootflow, kernel still be stored in boot image,
> however, the First Stage files in ramdisk were moved to
> init_boot image. We should load it to memory and verify it
> so that the kernel can execute init program to continue booting.

We already have some support for init_boot introduced with
commit 17b1656dcd07 ("abootimg: Add init_boot image support")

Can you explain in more what is missing in U-Boot and why this patch is
needed?

Some boards (such as the Beagle Play) already seem to use this, for
example: board/beagle/beagleplay/beagleplay.env

>
> Link: https://source.android.com/docs/core/architecture/partitions/generic-boot
> Signed-off-by: Valentin Liu <[email protected]>
> ---
> Changes for v2:
> - Resend without functional changes (mistakenly bumped version).
> Changes for v3:
> - Allow booting device with boot header version 4 without an init_boot partition.
> - Refactor AVB verification logic to verify the boot partition first
>   (to prevent downgrade attacks), then dynamically target the AVB
>   requested_partitions based on header_version.

Can you move the AVB verification logic into a separate patch? That
would make things a bit easier to review and reason about.

This way, we can look into the details about downgrade attacks and how
to solve that.

> - Coding style cleanup.
>
>  boot/bootmeth_android.c          | 134 +++++++++++++++++++++++++++++--
>  boot/image-android.c             |  67 ++++++++++++++++
>  cmd/abootimg.c                   |   6 ++
>  doc/develop/bootstd/overview.rst |   3 +
>  include/android_image.h          |   1 +
>  include/image.h                  |  59 ++++++++++++++
>  6 files changed, 265 insertions(+), 5 deletions(-)
>
> diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
> index ec255b072af..aacb7712892 100644
> --- a/boot/bootmeth_android.c
> +++ b/boot/bootmeth_android.c
> @@ -4,6 +4,7 @@
>   *
>   * Copyright (C) 2024 BayLibre, SAS
>   * Written by Mattijs Korpershoek <[email protected]>
> + * Copyright (c) 2026 Valentin Liu <[email protected]>

Please don't add any copyright lines here. I don't think there is a
strict policy about this but in my opinion this should not be updated
each time a new contributor adds some code to this file.

>   */
>  #define LOG_CATEGORY UCLASS_BOOTSTD
>  
> @@ -29,6 +30,7 @@
>  #define BCB_FIELD_COMMAND_SZ 32
>  #define BCB_PART_NAME "misc"
>  #define BOOT_PART_NAME "boot"
> +#define INIT_BOOT_PART_NAME "init_boot"
>  #define VENDOR_BOOT_PART_NAME "vendor_boot"
>  #define SLOT_LEN 2
>  
> @@ -47,6 +49,7 @@ struct android_priv {
>  	char *slot;
>  	u32 header_version;
>  	u32 boot_img_size;
> +	u32 init_boot_img_size;
>  	u32 vendor_boot_img_size;
>  };
>  
> @@ -113,6 +116,51 @@ static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
>  	return 0;
>  }
>  
> +static int scan_init_boot_part(struct udevice *blk, struct android_priv *priv)
> +{
> +	struct blk_desc *desc = dev_get_uclass_plat(blk);
> +	struct disk_partition partition;
> +	char partname[PART_NAME_LEN];
> +	ulong num_blks, bufsz;
> +	char *buf;
> +	int ret;
> +
> +	if (priv->slot)
> +		sprintf(partname, INIT_BOOT_PART_NAME "_%s", priv->slot);
> +	else
> +		sprintf(partname, INIT_BOOT_PART_NAME);
> +
> +	ret = part_get_info_by_name(desc, partname, &partition);
> +	if (ret < 0)
> +		return log_msg_ret("part info", ret);
> +
> +	num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v3), desc->blksz);
> +	bufsz = num_blks * desc->blksz;
> +	buf = malloc(bufsz);
> +	if (!buf)
> +		return log_msg_ret("buf", -ENOMEM);
> +
> +	ret = blk_read(blk, partition.start, num_blks, buf);
> +	if (ret != num_blks) {
> +		free(buf);
> +		return log_msg_ret("part read", -EIO);
> +	}
> +
> +	if (!is_android_init_boot_image_header(buf)) {
> +		free(buf);
> +		return log_msg_ret("header", -ENOENT);
> +	}
> +
> +	if (!android_image_get_init_bootimg_size(buf, &priv->init_boot_img_size)) {
> +		free(buf);
> +		return log_msg_ret("get init bootimg size", -EINVAL);
> +	}
> +
> +	free(buf);
> +
> +	return 0;
> +}
> +
>  static int scan_vendor_boot_part(struct udevice *blk, struct android_priv *priv)
>  {
>  	struct blk_desc *desc = dev_get_uclass_plat(blk);
> @@ -291,6 +339,17 @@ static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
>  		goto free_priv;
>  	}
>  
> +	if (priv->header_version >= 4) {
> +		ret = scan_init_boot_part(bflow->blk, priv);
> +		if (ret < 0) {
> +			/*
> +			 * Android 12 devices do not have the init_boot partition.
> +			 * Some devices upgraded to Android 13 or later from
> +			 * earlier Android versions may also not have one.
> +			 */
> +			log_debug("scan init_boot failed: err=%d\n", ret);
> +		}
> +	}
>  	if (priv->header_version >= 3) {
>  		ret = scan_vendor_boot_part(bflow->blk, priv);
>  		if (ret < 0) {
> @@ -421,11 +480,31 @@ static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
>  	return 0;
>  }
>  
> -static int run_avb_verification(struct bootflow *bflow)
> +/**
> + * run_avb_verification() - Run the Android Verified Boot (AVB) verification process
> + *
> + * Performs AVB slot verification via libavb. When @boot_partition_only is
> + * true, only the "boot" partition is verified; otherwise the full set of
> + * partitions required for the current slot (e.g. vbmeta, boot, system,

Per my understanding, vbmeta, vendor and system are not verified by AVB
(in the U-Boot stage).

vbmeta is used because that's what has the hashes for verifying the
other partitions.

vendor/system is verified whenever the Android kernel is already started
and it's dm-verity that handles this.

Can we rework the comment to only list the partitions that we are
verifying from U-Boot?

> + * vendor, etc.) is verified.
> + *
> + * @bflow: bootflow describing the target being booted
> + * @boot_partition_only: verify only the "boot" partition instead of the
> + *                        full AVB partition chain
> + *
> + * Return: 0 on success, a negative error code if verification fails
> + */
> +static int run_avb_verification(struct bootflow *bflow,
> +				const bool boot_partition_only)
>  {
>  	struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
>  	struct android_priv *priv = bflow->bootmeth_priv;
> -	const char * const requested_partitions[] = {"boot", "vendor_boot", NULL};
> +	static const char * const boot_only_partitions[] = {"boot", NULL};

What's the benefit of making these static? Can't we keep them as is?

> +	static const char * const v3_hdr_partitions[] = {"boot", "vendor_boot", NULL};
> +	static const char * const v4_hdr_partitions[] = {
> +		"boot", "init_boot", "vendor_boot", NULL
> +	};
> +	const char * const *requested_partitions;
>  	struct AvbOps *avb_ops;
>  	AvbSlotVerifyResult result;
>  	AvbSlotVerifyData *out_data = NULL;
> @@ -435,6 +514,22 @@ static int run_avb_verification(struct bootflow *bflow)
>  	bool unlocked = false;
>  	int ret;
>  
> +	if (boot_partition_only) {
> +		requested_partitions = boot_only_partitions;
> +	} else {
> +		if (priv->header_version >= 4) {
> +			ret = scan_init_boot_part(bflow->blk, priv);

Can we avoid reading the partition again?
For example, we already have priv->init_boot_img_size that's available
here to tell us if we have init_boot or not.

> +			if (ret == 0)
> +				requested_partitions = v4_hdr_partitions;
> +			else
> +				requested_partitions = v3_hdr_partitions;
> +		} else if (priv->header_version >= 3) {
> +			requested_partitions = v3_hdr_partitions;
> +		} else {
> +			requested_partitions = boot_only_partitions;
> +		}
> +	}
> +
>  	avb_ops = avb_ops_alloc(desc->devnum);
>  	if (!avb_ops)
>  		return log_msg_ret("avb ops", -ENOMEM);
> @@ -504,9 +599,11 @@ static int run_avb_verification(struct bootflow *bflow)
>  	return ret;
>  }
>  #else
> -static int run_avb_verification(struct bootflow *bflow)
> +static int run_avb_verification(struct bootflow *bflow,
> +				const bool boot_partition_only)
>  {
>  	int ret;
> +	(void)boot_partition_only;
>  
>  	/* When AVB is unsupported, pass ORANGE state  */
>  	ret = bootflow_cmdline_set_arg(bflow,
> @@ -556,11 +653,13 @@ static int boot_android_normal(struct bootflow *bflow)
>  	struct android_priv *priv = bflow->bootmeth_priv;
>  	int ret;
>  	ulong loadaddr = env_get_hex("loadaddr", 0);
> +	ulong iloadaddr = env_get_hex("init_boot_comp_addr_r", 0);
>  	ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
>  
> -	ret = run_avb_verification(bflow);
> +	/* Check the boot partition first to prevent downgrade attacks. */
> +	ret = run_avb_verification(bflow, true);

Ditto here. Let's move the downgrade attack protection to a separate
(prepatory) patch please.

>  	if (ret < 0)
> -		return log_msg_ret("avb", ret);
> +		return log_msg_ret("boot avb", ret);
>  
>  	/* Read slot once more to decrement counter from BCB */
>  	ret = android_read_slot_from_bcb(bflow, true);
> @@ -572,6 +671,31 @@ static int boot_android_normal(struct bootflow *bflow)
>  	if (ret < 0)
>  		return log_msg_ret("read boot", ret);
>  
> +	ret = run_avb_verification(bflow, false);
> +	if (ret < 0)
> +		return log_msg_ret("avb", ret);
> +
> +	if (priv->header_version >= 4) {
> +		ret = scan_init_boot_part(bflow->blk, priv);
> +		if (ret == 0) {
> +			ret = read_slotted_partition(desc, "init_boot", priv->slot,
> +						     priv->init_boot_img_size,
> +						     iloadaddr);
> +			if (ret < 0)
> +				return log_msg_ret("read init_boot", ret);
> +			set_ainit_bootimg_addr(iloadaddr);
> +		} else if (ret == -1) {
> +			/*
> +			 * Android 12 devices do not have the init_boot partition.
> +			 * Some devices upgraded to Android 13 or later from
> +			 * earlier Android versions may also not have one.
> +			 */
> +			log_debug("init_boot partition is not found");
> +		} else if (ret < -1) {
> +			log_debug("scan init_boot failed: err=%d\n", ret);
> +			return log_msg_ret("read init_boot", ret);
> +		}
> +	}
>  	if (priv->header_version >= 3) {
>  		ret = read_slotted_partition(desc, "vendor_boot", priv->slot,
>  					     priv->vendor_boot_img_size, vloadaddr);
> diff --git a/boot/image-android.c b/boot/image-android.c
> index 7740cae8cb6..05eb7e9b6be 100644
> --- a/boot/image-android.c
> +++ b/boot/image-android.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0+
>  /*
>   * Copyright (c) 2011 Sebastian Andrzej Siewior <[email protected]>
> + * Copyright (c) 2026 Valentin Liu <[email protected]>

Same remark here.

>   */
>  
>  #include <env.h>
> @@ -130,6 +131,28 @@ static void android_boot_image_v3_v4_parse_hdr(const struct andr_boot_img_hdr_v3
>  	data->boot_img_total_size = end - map_to_sysmem(hdr);
>  }
>  
> +static void android_init_boot_image_v4_parse_hdr(const struct andr_boot_img_hdr_v3 *hdr,
> +						 struct andr_image_data *data)
> +{
> +	ulong end;
> +
> +	/*
> +	 * The header takes a full page, the remaining components are aligned
> +	 * on page boundary.
> +	 */
> +	end = (ulong)hdr;
> +	end += ANDR_GKI_PAGE_SIZE;
> +	end += ALIGN(hdr->kernel_size, ANDR_GKI_PAGE_SIZE);
> +	data->ramdisk_ptr = end;
> +	data->ramdisk_size = hdr->ramdisk_size;
> +	data->boot_ramdisk_size = hdr->ramdisk_size;
> +	end += ALIGN(hdr->ramdisk_size, ANDR_GKI_PAGE_SIZE);
> +
> +	end += ALIGN(hdr->signature_size, ANDR_GKI_PAGE_SIZE);
> +
> +	data->init_boot_img_total_size = end - (ulong)hdr;
> +}
> +
>  static void android_vendor_boot_image_v3_v4_parse_hdr(const struct andr_vnd_boot_img_hdr
>  						      *hdr, struct andr_image_data *data,
>  						      bool write_trailer)
> @@ -263,6 +286,27 @@ bool android_image_get_bootimg_size(const void *hdr, u32 *boot_img_size)
>  	return true;
>  }
>  
> +bool android_image_get_init_bootimg_size(const void *hdr, u32 *init_boot_img_size)
> +{
> +	struct andr_image_data data;
> +
> +	if (!hdr || !init_boot_img_size) {
> +		printf("hdr or init_boot_img_size can't be NULL\n");
> +		return false;
> +	}
> +
> +	if (!is_android_init_boot_image_header(hdr)) {
> +		printf("Incorrect init boot image header\n");
> +		return false;
> +	}
> +
> +	android_init_boot_image_v4_parse_hdr(hdr, &data);
> +
> +	*init_boot_img_size = data.init_boot_img_total_size;
> +
> +	return true;
> +}
> +
>  bool android_image_get_vendor_bootimg_size(const void *hdr, u32 *vendor_boot_img_size)
>  {
>  	struct andr_image_data data;
> @@ -326,6 +370,24 @@ bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
>  	return true;
>  }
>  
> +bool android_image_get_data_v4(const void *boot_hdr, const void *vendor_boot_hdr,
> +			       const void *init_boot_hdr, struct andr_image_data *data)
> +{
> +	if (!android_image_get_data(boot_hdr, vendor_boot_hdr, data)) {
> +		printf("An error happened while calling android_image_get_data().\n");

android_image_get_data() already prints out a specific error on each
failure path. This printf() is not needed. Please drop it.

> +		return false;
> +	}
> +
> +	if (!is_android_init_boot_image_header(init_boot_hdr)) {
> +		printf("Incorrect init boot image header\n");
> +		return false;
> +	}
> +
> +	android_init_boot_image_v4_parse_hdr(init_boot_hdr, data);
> +
> +	return true;
> +}
> +
>  static ulong android_image_get_kernel_addr(struct andr_image_data *img_data,
>  					   ulong comp)
>  {
> @@ -466,6 +528,11 @@ bool is_android_vendor_boot_image_header(const void *vendor_boot_img)
>  	return !memcmp(VENDOR_BOOT_MAGIC, vendor_boot_img, ANDR_VENDOR_BOOT_MAGIC_SIZE);
>  }
>  
> +bool is_android_init_boot_image_header(const void *init_boot_img)
> +{
> +	return !memcmp(ANDR_BOOT_MAGIC, init_boot_img, ANDR_BOOT_MAGIC_SIZE);

This is the same body as is_android_boot_image_header(). Can't we just
call that instead?

> +}
> +
>  bool is_android_boot_image_header(const void *hdr)
>  {
>  	return !memcmp(ANDR_BOOT_MAGIC, hdr, ANDR_BOOT_MAGIC_SIZE);
> diff --git a/cmd/abootimg.c b/cmd/abootimg.c
> index eae3e643b60..b32872bba7f 100644
> --- a/cmd/abootimg.c
> +++ b/cmd/abootimg.c
> @@ -2,6 +2,7 @@
>  /*
>   * (C) Copyright 2020
>   * Sam Protsenko <[email protected]>
> + * Copyright (c) 2026 Valentin Liu <[email protected]>

Same here

>   */
>  
>  #include <android_image.h>
> @@ -33,6 +34,11 @@ ulong get_ainit_bootimg_addr(void)
>  	return _ainit_bootimg_addr;
>  }
>  
> +void set_ainit_bootimg_addr(ulong addr)
> +{
> +	_ainit_bootimg_addr = addr;
> +}
> +
>  ulong get_avendor_bootimg_addr(void)
>  {
>  	return _avendor_bootimg_addr;
> diff --git a/doc/develop/bootstd/overview.rst b/doc/develop/bootstd/overview.rst
> index ec9fafa0fa0..f1306ae0a8f 100644
> --- a/doc/develop/bootstd/overview.rst
> +++ b/doc/develop/bootstd/overview.rst
> @@ -293,6 +293,9 @@ script_offset_f
>  script_size_f
>      Size of the script to load, e.g. 0x2000
>  
> +init_boot_comp_addr_r
> +    Address to which to load the init_boot Android image, e.g. 0xd0000000
> +
>  vendor_boot_comp_addr_r
>      Address to which to load the vendor_boot Android image, e.g. 0xe0000000
>  
> diff --git a/include/android_image.h b/include/android_image.h
> index a2d80499ba3..134b5ed74d6 100644
> --- a/include/android_image.h
> +++ b/include/android_image.h
> @@ -357,6 +357,7 @@ struct andr_image_data {
>  	ulong tags_addr;  /* physical addr for kernel tags */
>  	u32 header_version;  /* version of the boot image header */
>  	u32 boot_img_total_size;  /* boot image size */
> +	u32 init_boot_img_total_size;  /* init boot image size */
>  	u32 vendor_boot_img_total_size;  /* vendor boot image size */
>  };
>  
> diff --git a/include/image.h b/include/image.h
> index 6edcb1995bf..b323b5f880e 100644
> --- a/include/image.h
> +++ b/include/image.h
> @@ -2015,6 +2015,30 @@ struct andr_image_data;
>   */
>  bool android_image_get_bootimg_size(const void *hdr, u32 *boot_img_size);
>  
> +/**
> + * android_image_get_init_bootimg_size() - Extract size of Android init_boot image

Shouldn't this be android_image_get_bootimg_size()?

It looks like a copy paste from the function below.

> + *
> + * This is used to extract the size of an Android init_boot image
> + * from init_boot image header.
> + *
> + * @hdr: Pointer to init_boot image header
> + * @init_boot_img_size: On exit returns the size in bytes of the init_boot image
> + * Return: true if succeeded, false otherwise
> + */
> +bool android_image_get_bootimg_size(const void *hdr, u32 *init_boot_img_size);
> +
> +/**
> + * android_image_get_init_bootimg_size() - Extract size of Android init_boot image
> + *
> + * This is used to extract the size of an Android init_boot image
> + * from init_boot image header.
> + *
> + * @hdr: Pointer to init_boot image header
> + * @init_boot_img_size: On exit returns the size in bytes of the init_boot image
> + * Return: true if succeeded, false otherwise
> + */
> +bool android_image_get_init_bootimg_size(const void *hdr, u32 *init_boot_img_size);
> +
>  /**
>   * android_image_get_vendor_bootimg_size() - Extract size of Android vendor-boot image
>   *
> @@ -2041,6 +2065,23 @@ bool android_image_get_vendor_bootimg_size(const void *hdr, u32 *vendor_boot_img
>  bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
>  			    struct andr_image_data *data);
>  
> +/**
> + * android_image_get_data_v4() - Parse Android header version 4 boot images
> + *
> + * This is used to parse boot, vendor-boot and init boot header into
> + * andr_image_data generic structure.
> + * The Android 13 and newer has splited parameters from boot and
> + * vendor_boot images to the init_boot image.
> + *
> + * @boot_hdr: Pointer to boot image header
> + * @vendor_boot_hdr: Pointer to vendor boot image header
> + * @init_boot_hdr: Pointer to init boot image header
> + * @data: Pointer to generic boot format structure
> + * Return: true if succeeded, false otherwise
> + */
> +bool android_image_get_data_v4(const void *boot_hdr, const void *vendor_boot_hdr,
> +			       const void *init_boot_hdr, struct andr_image_data *data);
> +
>  struct andr_boot_img_hdr_v0;
>  
>  /**
> @@ -2167,6 +2208,17 @@ bool android_image_print_dtb_contents(ulong hdr_addr);
>   */
>  bool is_android_boot_image_header(const void *hdr);
>  
> +/**
> + * is_android_init_boot_image_header() - Check the magic of init boot image
> + *
> + * This checks the header of Android init boot image and verifies the
> + * magic is "ANDROID!" (same with the boot image)
> + *
> + * @init_boot_img: Pointer to boot image
> + * Return: non-zero if the magic is correct, zero otherwise
> + */
> +bool is_android_init_boot_image_header(const void *init_boot_img);
> +
>  /**
>   * is_android_vendor_boot_image_header() - Check the magic of vendor boot image
>   *
> @@ -2199,6 +2251,13 @@ void set_abootimg_addr(ulong addr);
>   */
>  ulong get_ainit_bootimg_addr(void);
>  
> +/**
> + * set_ainit_bootimg_addr() - Set Android init boot image address
> + *
> + * Return: no returned results
> + */
> +void set_ainit_bootimg_addr(ulong addr);
> +
>  /**
>   * get_avendor_bootimg_addr() - Get Android vendor boot image address
>   *
> -- 
> 2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.