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".

Signed-off-by: Igor Opaniuk <[email protected]>
---
 drivers/fastboot/Kconfig      |  11 ++++
 drivers/fastboot/fb_command.c | 119 ++++++++++++++++++++++++++++++++++++++++++
 drivers/fastboot/fb_getvar.c  |  34 ++++++++++++
 include/fastboot.h            |   3 ++
 4 files changed, 167 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..9bf012645ef 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,11 @@ static void oem_console(char *, char *);
 static void oem_board(char *, char *);
 static void run_ucmd(char *, char *);
 static void run_acmd(char *, char *);
+#if CONFIG_IS_ENABLED(FASTBOOT_CMD_FLASHING)
+static void flashing_lock(char *, char *);
+static void flashing_unlock(char *, char *);
+static void flashing_get_unlock_ability(char *, char *);
+#endif
 
 static const struct {
        const char *command;
@@ -128,6 +136,20 @@ 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 +611,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

Reply via email to