aboutsummaryrefslogtreecommitdiff
path: root/libavb_ab
diff options
context:
space:
mode:
authorDavid Zeuthen <zeuthen@google.com>2016-11-16 17:58:13 -0500
committerDavid Zeuthen <zeuthen@google.com>2016-11-21 15:30:56 -0500
commit0155e6b158bdc5b3a442f16a5dc124d5dee9c71c (patch)
tree6554794899e94fb8955d32bd18936a3ce914828c /libavb_ab
parent18666abc5d8276a743111e6c3608e66f6c85fb51 (diff)
downloadavb-0155e6b158bdc5b3a442f16a5dc124d5dee9c71c.tar.gz
Enable operations on unlocked devices.
If a device is unlocked the expected behavior is that slots are rejected if, and only if, they are invalid, not if they fail verification. Verification failure includes rollback index comparison failures, signature mismatch, signature made by an unknown key, vbmeta structs without any signature, and so on. Basically the spirit here is that an unlocked device should be able to boot an image built on your local workstation and e.g. signed with your own keys. To easily enable such operations with avb_slot_verify() and avb_ab_flow(), we introduce a boolean |allow_verification_error| parameter in each function. If this is false everything is as before and we'll abort verification as soon as something doesn't verify. On the other hand, if |allow_verification_error| is true then we'll keep going and only bail if e.g. the AVB metadata is invalid, _not_ if it fails verification. This is designed so callers can set |allow_verification_error| to true exactly if the device is unlocked. Callers of avb_slot_verify() where |allow_verification_error| is set to true are guaranteed that AVB_SLOT_VERIFY_RESULT_OK is returned if, and only if, the slot verified correctly. Introduce AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR in addition to AVB_AB_FLOW_RESULT_OK so avb_ab_flow() users can make a distinction whether the image verified or not. The new value can only returned if |allow_verification_error| is set to true. In both cases - avb_slot_verify() and avb_ab_flow() - the bootloader has enough information to determine if the slot to boot cannot be verified. For example, the device can convey to the user that the OS it's about to boot is unverified and request the user to click through. On the other hand if the slot did verify (despite the device being unlocked) the bootloader can nicely avoid such a kludge. Add some new test cases to verify correct operation both if |allow_verification_error| is true or false. Test: New unit tests and all unit tests pass. Test: Tested in UEFI-based bootloader in qemu. Bug: 32949911 Change-Id: I218c8761c201d2e8e4dc73eaebfb1ac2742e0726
Diffstat (limited to 'libavb_ab')
-rw-r--r--libavb_ab/avb_ab_flow.c87
-rw-r--r--libavb_ab/avb_ab_flow.h30
2 files changed, 105 insertions, 12 deletions
diff --git a/libavb_ab/avb_ab_flow.c b/libavb_ab/avb_ab_flow.c
index da8e4ea..6b67c6a 100644
--- a/libavb_ab/avb_ab_flow.c
+++ b/libavb_ab/avb_ab_flow.c
@@ -198,6 +198,7 @@ static AvbIOResult save_metadata_if_changed(AvbABOps* ab_ops,
AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
const char* const* requested_partitions,
+ bool allow_verification_error,
AvbSlotVerifyData** out_data) {
AvbOps* ops = &(ab_ops->ops);
AvbSlotVerifyData* slot_data[2] = {NULL, NULL};
@@ -206,6 +207,7 @@ AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
AvbABData ab_data, ab_data_orig;
size_t slot_index_to_boot, n;
AvbIOResult io_ret;
+ bool saw_and_allowed_verification_error = false;
io_ret = load_metadata(ab_ops, &ab_data, &ab_data_orig);
if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
@@ -220,17 +222,47 @@ AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
for (n = 0; n < 2; n++) {
if (slot_is_bootable(&ab_data.slots[n])) {
AvbSlotVerifyResult verify_result;
- verify_result = avb_slot_verify(ops, requested_partitions,
- slot_suffixes[n], &slot_data[n]);
- if (verify_result != AVB_SLOT_VERIFY_RESULT_OK) {
- if (verify_result == AVB_SLOT_VERIFY_RESULT_ERROR_OOM) {
+ bool set_slot_unbootable = false;
+
+ verify_result =
+ avb_slot_verify(ops, requested_partitions, slot_suffixes[n],
+ allow_verification_error, &slot_data[n]);
+ switch (verify_result) {
+ case AVB_SLOT_VERIFY_RESULT_ERROR_OOM:
ret = AVB_AB_FLOW_RESULT_ERROR_OOM;
goto out;
- }
- if (verify_result == AVB_SLOT_VERIFY_RESULT_ERROR_IO) {
+
+ case AVB_SLOT_VERIFY_RESULT_ERROR_IO:
ret = AVB_AB_FLOW_RESULT_ERROR_IO;
goto out;
- }
+
+ case AVB_SLOT_VERIFY_RESULT_OK:
+ break;
+
+ case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA:
+ /* Even with |allow_verification_error| this is game over. */
+ set_slot_unbootable = true;
+ break;
+
+ /* explicit fallthrough. */
+ case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
+ case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
+ case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
+ if (allow_verification_error) {
+ /* Do nothing since we allow this. */
+ avb_debugv("Allowing slot ", slot_suffixes[n],
+ " which verified "
+ "with result ",
+ avb_slot_verify_result_to_string(verify_result),
+ " because |allow_verification_error| is true.\n", NULL);
+ saw_and_allowed_verification_error = true;
+ } else {
+ set_slot_unbootable = true;
+ }
+ break;
+ }
+
+ if (set_slot_unbootable) {
avb_errorv("Error verifying slot ", slot_suffixes[n], " with result ",
avb_slot_verify_result_to_string(verify_result),
" - setting unbootable.\n", NULL);
@@ -305,7 +337,12 @@ AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
avb_assert(slot_data[slot_index_to_boot] != NULL);
data = slot_data[slot_index_to_boot];
slot_data[slot_index_to_boot] = NULL;
- ret = AVB_AB_FLOW_RESULT_OK;
+ if (saw_and_allowed_verification_error) {
+ avb_assert(allow_verification_error);
+ ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR;
+ } else {
+ ret = AVB_AB_FLOW_RESULT_OK;
+ }
/* ... and decrement tries remaining, if applicable. */
if (!ab_data.slots[slot_index_to_boot].successful_boot &&
@@ -429,3 +466,37 @@ out:
}
return ret;
}
+
+const char* avb_ab_flow_result_to_string(AvbABFlowResult result) {
+ const char* ret = NULL;
+
+ switch (result) {
+ case AVB_AB_FLOW_RESULT_OK:
+ ret = "OK";
+ break;
+
+ case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR:
+ ret = "OK_WITH_VERIFICATION_ERROR";
+ break;
+
+ case AVB_AB_FLOW_RESULT_ERROR_OOM:
+ ret = "ERROR_OOM";
+ break;
+
+ case AVB_AB_FLOW_RESULT_ERROR_IO:
+ ret = "ERROR_IO";
+ break;
+
+ case AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS:
+ ret = "ERROR_NO_BOOTABLE_SLOTS";
+ break;
+ /* Do not add a 'default:' case here because of -Wswitch. */
+ }
+
+ if (ret == NULL) {
+ avb_error("Unknown AvbABFlowResult value.\n");
+ ret = "(unknown)";
+ }
+
+ return ret;
+}
diff --git a/libavb_ab/avb_ab_flow.h b/libavb_ab/avb_ab_flow.h
index 21f680a..dc05a4d 100644
--- a/libavb_ab/avb_ab_flow.h
+++ b/libavb_ab/avb_ab_flow.h
@@ -136,11 +136,15 @@ AvbIOResult avb_ab_data_write(AvbABOps* ab_ops, const AvbABData* data);
*/
typedef enum {
AVB_AB_FLOW_RESULT_OK,
+ AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR,
AVB_AB_FLOW_RESULT_ERROR_OOM,
AVB_AB_FLOW_RESULT_ERROR_IO,
AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS
} AvbABFlowResult;
+/* Get a textual representation of |result|. */
+const char* avb_ab_flow_result_to_string(AvbABFlowResult result);
+
/* High-level function to select a slot to boot. The following
* algorithm is used:
*
@@ -150,9 +154,10 @@ typedef enum {
* avb_ab_data_init() and this reset metadata is returned.
*
* 2. All bootable slots listed in the A/B metadata are verified using
- * avb_slot_verify(). If a slot fails verification, it will be marked
- * as unbootable in the A/B metadata and the metadata will be saved to
- * disk before returning.
+ * avb_slot_verify(). If a slot is invalid or if it fails verification
+ * (and |allow_verification_error| is false, see below), it will be
+ * marked as unbootable in the A/B metadata and the metadata will be
+ * saved to disk before returning.
*
* 3. If there are no bootable slots, the value
* AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS is returned.
@@ -174,6 +179,22 @@ typedef enum {
* |requested_partitions| array only contains a single item for the
* boot partition, 'boot'.
*
+ * If the device is unlocked (and _only_ if it's unlocked), true
+ * should be passed in the |allow_verification_error| parameter. This
+ * will allow considering slots as verified even when
+ * avb_slot_verify() returns
+ * AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+ * AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION, or
+ * AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX for the slot in
+ * question.
+ *
+ * If a slot was selected and it verified then AVB_AB_FLOW_RESULT_OK
+ * is returned.
+ *
+ * If a slot was selected but it didn't verify then
+ * AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR is returned. This can
+ * only happen when |allow_verification_error| is true.
+ *
* If an I/O operation - such as loading/saving metadata or checking
* rollback indexes - fail, the value AVB_AB_FLOW_RESULT_ERROR_IO is
* returned.
@@ -182,10 +203,11 @@ typedef enum {
* returned.
*
* Reasonable behavior for handling AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS
- * is to initiate device recovery (which is device-dependent).
+ * is to initiate device repair (which is device-dependent).
*/
AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
const char* const* requested_partitions,
+ bool allow_verification_error,
AvbSlotVerifyData** out_data);
/* Marks the slot with the given slot number as active. Returns