Hi Mattijs,
On Sun, Oct 11, 2026 at 4:42 PM Igor Opaniuk
<[email protected]> wrote:
>
> Android's fastboot client changes the device lock state with
> "fastboot flashing lock|unlock" and asks "flashing get_unlock_ability"
> first. U-Boot has the state in the AVB TA (avb_write_lock_state(), used by
> the "avb lock/unlock" shell commands) but no fastboot front end for it, so
> a device could only be unlocked from the serial console.
>
> Add the three commands and the "unlocked" variable behind
> CONFIG_FASTBOOT_CMD_FLASHING. As AVB requires, both transitions erase
> userdata (and metadata, which holds the file based encryption keys) before
> the state changes and are refused when userdata cannot be erased; the TA
> resets the rollback indexes on a state change. get_unlock_ability always
> reports 1 since U-Boot does not evaluate the "OEM unlocking" setting
> Android keeps in the frp partition.
>
> Tested on QEMU virt with OP-TEE over "fastboot tcp": unlock and lock
> erase the partitions and flip the state reported by getvar unlocked and
> "avb is_unlocked".
>
This exact patch ("avb: device lock state, root key provisioning and
in-place verification")
fails to compile in every configuration that has fastboot but not
FASTBOOT_CMD_FLASHING.
The extended CI job run just caught that. I’ll address it in v2.
> Signed-off-by: Igor Opaniuk <[email protected]>
> ---
> drivers/fastboot/Kconfig | 11 ++++
> drivers/fastboot/fb_command.c | 115
> ++++++++++++++++++++++++++++++++++++++++++
> drivers/fastboot/fb_getvar.c | 34 +++++++++++++
> include/fastboot.h | 3 ++
> 4 files changed, 163 insertions(+)
>
> diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig
> index 1887f78bcec..b5ca8c8dcb5 100644
> --- a/drivers/fastboot/Kconfig
> +++ b/drivers/fastboot/Kconfig
> @@ -248,6 +248,17 @@ config FASTBOOT_MBR_NAME
> specified on the "fastboot flash" command line matches the value
> defined here. The default target name for updating MBR is "mbr".
>
> +config FASTBOOT_CMD_FLASHING
> + bool "Enable the 'flashing lock/unlock/get_unlock_ability' commands"
> + depends on FASTBOOT_FLASH_MMC && AVB_VERIFY && OPTEE_TA_AVB
> + help
> + Add the Android "flashing lock", "flashing unlock" and "flashing
> + get_unlock_ability" commands and the "unlocked" variable. The device
> + lock state is the one AVB enforces, kept by the OP-TEE AVB TA. As
> + required by AVB, the userdata (and metadata) partition is erased
> + before the state changes in either direction, and the TA resets the
> + rollback indexes on a state change.
> +
> config FASTBOOT_CMD_OEM_FORMAT
> bool "Enable the 'oem format' command"
> depends on FASTBOOT_FLASH_MMC && CMD_GPT
> diff --git a/drivers/fastboot/fb_command.c b/drivers/fastboot/fb_command.c
> index 18d86988f4c..2e5ce981aff 100644
> --- a/drivers/fastboot/fb_command.c
> +++ b/drivers/fastboot/fb_command.c
> @@ -16,6 +16,9 @@
> #include <stdlib.h>
> #include <vsprintf.h>
> #include <linux/printk.h>
> +#if CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING)
> +#include <avb_verify.h>
> +#endif
>
> /**
> * image_size - final fastboot image size
> @@ -47,6 +50,9 @@ static void oem_console(char *, char *);
> static void oem_board(char *, char *);
> static void run_ucmd(char *, char *);
> static void run_acmd(char *, char *);
> +static void flashing_lock(char *, char *);
> +static void flashing_unlock(char *, char *);
> +static void flashing_get_unlock_ability(char *, char *);
>
> static const struct {
> const char *command;
> @@ -128,6 +134,18 @@ static const struct {
> .command = "ACmd",
> .dispatch = CONFIG_IS_ENABLED(FASTBOOT_UUU_SUPPORT,
> (run_acmd), (NULL))
> },
> + [FASTBOOT_COMMAND_FLASHING_LOCK] = {
> + .command = "flashing lock",
> + .dispatch = CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING,
> (flashing_lock), (NULL))
> + },
> + [FASTBOOT_COMMAND_FLASHING_UNLOCK] = {
> + .command = "flashing unlock",
> + .dispatch = CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING,
> (flashing_unlock), (NULL))
> + },
> + [FASTBOOT_COMMAND_FLASHING_GET_UNLOCK_ABILITY] = {
> + .command = "flashing get_unlock_ability",
> + .dispatch = CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING,
> (flashing_get_unlock_ability), (NULL))
> + },
> };
>
> /**
> @@ -589,3 +607,100 @@ static void __maybe_unused oem_board(char
> *cmd_parameter, char *response)
> {
> fastboot_oem_board(cmd_parameter, (void *)fastboot_buf_addr,
> image_size, response);
> }
> +
> +#if CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING)
> +/**
> + * flashing_set_lock() - set the AVB device lock state through the AVB TA
> + *
> + * @locked: new state
> + * @response: fastboot response buffer, filled on failure
> + * Return: 0 on success, -1 otherwise
> + */
> +static int flashing_set_lock(bool locked, char *response)
> +{
> + AvbOps *ops;
> + AvbIOResult rc;
> +
> + ops = avb_ops_alloc(CONFIG_FASTBOOT_FLASH_MMC_DEV);
> + if (!ops) {
> + fastboot_fail("cannot allocate AVB ops", response);
> + return -1;
> + }
> +
> + rc = avb_write_lock_state(ops, locked);
> + avb_ops_free(ops);
> + if (rc != AVB_IO_RESULT_OK) {
> + fastboot_fail("cannot change the device lock state",
> response);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * flashing_wipe_userdata() - erase userdata and metadata
> + *
> + * AVB requires user data to be wiped on every change of the device state,
> + * in either direction, so userdata (and metadata, which holds the file
> + * based encryption keys) are erased before the state changes.
> + *
> + * @response: fastboot response buffer, filled on failure
> + * Return: 0 on success, -1 when userdata could not be erased
> + */
> +static int flashing_wipe_userdata(char *response)
> +{
> + fastboot_mmc_erase("userdata", response);
> + if (!strncmp(response, "FAIL", 4))
> + return -1;
> +
> + /* metadata is optional on older layouts */
> + fastboot_mmc_erase("metadata", response);
> +
> + return 0;
> +}
> +
> +/**
> + * flashing_lock() - lock the device
> + *
> + * Verification errors stop the boot afterwards. The AVB TA resets the
> + * rollback indexes on a state change.
> + */
> +static void flashing_lock(char *cmd_parameter, char *response)
> +{
> + if (flashing_wipe_userdata(response))
> + return;
> +
> + if (flashing_set_lock(true, response))
> + return;
> +
> + fastboot_okay(NULL, response);
> +}
> +
> +/**
> + * flashing_unlock() - unlock the device
> + *
> + * The unlock is refused if userdata cannot be erased.
> + */
> +static void flashing_unlock(char *cmd_parameter, char *response)
> +{
> + if (flashing_wipe_userdata(response))
> + return;
> +
> + if (flashing_set_lock(false, response))
> + return;
> +
> + fastboot_okay(NULL, response);
> +}
> +
> +/**
> + * flashing_get_unlock_ability() - report whether unlocking is allowed
> + *
> + * Android gates this on the "OEM unlocking" developer setting, stored by
> + * the OS in the frp partition; there is no policy for it in U-Boot, so
> + * unlocking is always possible.
> + */
> +static void flashing_get_unlock_ability(char *cmd_parameter, char *response)
> +{
> + fastboot_okay("1", response);
> +}
> +#endif /* FASTBOOT_CMD_FLASHING */
> diff --git a/drivers/fastboot/fb_getvar.c b/drivers/fastboot/fb_getvar.c
> index e8aa0e09aa6..92c8e702a0b 100644
> --- a/drivers/fastboot/fb_getvar.c
> +++ b/drivers/fastboot/fb_getvar.c
> @@ -28,6 +28,7 @@ static void getvar_has_slot(char *var_parameter, char
> *response);
> static void getvar_partition_type(char *part_name, char *response);
> static void getvar_partition_size(char *part_name, char *response);
> static void getvar_is_userspace(char *var_parameter, char *response);
> +static void getvar_unlocked(char *var_parameter, char *response);
>
> static const struct {
> const char *variable;
> @@ -88,6 +89,10 @@ static const struct {
> .dispatch = getvar_partition_size,
> .list = false
> #endif
> + }, {
> + .variable = "unlocked",
> + .dispatch = CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING,
> (getvar_unlocked), (NULL)),
> + .list = CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING)
> }, {
> .variable = "is-userspace",
> .dispatch = getvar_is_userspace,
> @@ -346,3 +351,32 @@ void fastboot_getvar(char *cmd_parameter, char *response)
> fastboot_fail("Variable not implemented", response);
> }
> }
> +
> +#if CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING)
> +#include <avb_verify.h>
> +
> +/* "yes" when the device is unlocked according to the AVB TA, else "no" */
> +static void getvar_unlocked(char *var_parameter, char *response)
> +{
> + AvbOps *ops;
> + bool unlocked = false;
> + AvbIOResult rc;
> +
> + ops = avb_ops_alloc(CONFIG_FASTBOOT_FLASH_MMC_DEV);
> + if (!ops) {
> + fastboot_fail("cannot allocate AVB ops", response);
> + return;
> + }
> + rc = ops->read_is_device_unlocked(ops, &unlocked);
> + avb_ops_free(ops);
> + if (rc != AVB_IO_RESULT_OK) {
> + fastboot_fail("cannot read the device lock state", response);
> + return;
> + }
> + fastboot_okay(unlocked ? "yes" : "no", response);
> +}
> +#else
> +static void __maybe_unused getvar_unlocked(char *var_parameter, char
> *response)
> +{
> +}
> +#endif
> diff --git a/include/fastboot.h b/include/fastboot.h
> index b106d617749..e8b6e293173 100644
> --- a/include/fastboot.h
> +++ b/include/fastboot.h
> @@ -53,6 +53,9 @@ enum {
> FASTBOOT_COMMAND_OEM_BOARD,
> FASTBOOT_COMMAND_ACMD,
> FASTBOOT_COMMAND_UCMD,
> + FASTBOOT_COMMAND_FLASHING_LOCK,
> + FASTBOOT_COMMAND_FLASHING_UNLOCK,
> + FASTBOOT_COMMAND_FLASHING_GET_UNLOCK_ABILITY,
> FASTBOOT_COMMAND_COUNT
> };
>
>
> --
> 2.53.0
>