Add sample UEFI bootloader using libavb and libavb_ab. am: d9c76c7654 am: dafe35a62c
am: c5929777d7

Change-Id: I990c1f7fbd6513841597083305b79fc9baee4e61
diff --git a/README b/README
index b5156b5..1c63f3f 100644
--- a/README
+++ b/README
@@ -113,6 +113,11 @@
 
    Contains unit tests for abvtool, libavb, libavb_ab, and libavb_atx.
 
+ examples/uefi/
+
+   Contains the source-code for a UEFI-based boot-loader utilizing
+   libavb/ and libavb_ab/.
+
 -- AUDIENCE AND PORTABILITY NOTES
 
 This code is intended to be used in bootloaders in devices running
diff --git a/examples/uefi/Makefile b/examples/uefi/Makefile
new file mode 100644
index 0000000..eae589f
--- /dev/null
+++ b/examples/uefi/Makefile
@@ -0,0 +1,98 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+# TODO(zeuthen): This is not invoked by Android build system and is
+# intended to aid users to locally build the EFI application. It would
+# be nice to add gnu-efi/ to external and rewrite this as an
+# Android.mk file.
+
+EFI_CC      = gcc
+EFI_LD      = ld
+EFI_OBJCOPY = objcopy
+AVB         = ../..
+GNUEFI      = /usr/include/efi
+
+EFI_SRC_FILES = \
+    main.c \
+    uefi_avb_sysdeps.c \
+    uefi_avb_ops.c \
+    uefi_avb_util.c \
+    uefi_avb_boot.c \
+	$(AVB)/libavb/avb_chain_partition_descriptor.c \
+    $(AVB)/libavb/avb_crc32.c \
+    $(AVB)/libavb/avb_crypto.c \
+    $(AVB)/libavb/avb_descriptor.c \
+    $(AVB)/libavb/avb_footer.c \
+    $(AVB)/libavb/avb_hash_descriptor.c \
+    $(AVB)/libavb/avb_hashtree_descriptor.c \
+    $(AVB)/libavb/avb_kernel_cmdline_descriptor.c \
+    $(AVB)/libavb/avb_property_descriptor.c \
+    $(AVB)/libavb/avb_rsa.c \
+    $(AVB)/libavb/avb_sha256.c \
+    $(AVB)/libavb/avb_sha512.c \
+    $(AVB)/libavb/avb_slot_verify.c \
+    $(AVB)/libavb/avb_util.c \
+    $(AVB)/libavb/avb_vbmeta_image.c \
+    $(AVB)/libavb_ab/avb_ab_flow.c
+
+EFI_OBJ_FILES   = $(notdir $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(EFI_SRC_FILES))))
+EFI_TARGET      = avb_bootloader.efi
+EFI_SHARED_OBJ  = $(patsubst %.efi,%.so,$(EFI_TARGET))
+
+EFI_CFLAGS = \
+    -DAVB_COMPILATION -std=gnu99 \
+	-I$(ANDROID_BUILD_TOP)/system/core/mkbootimg/ \
+    -I$(AVB) \
+    -I$(GNUEFI) \
+    -I$(GNUEFI)/x86_64 \
+    -I$(GNUEFI)/protocol \
+    -fno-stack-protector -fpic \
+    -fshort-wchar -mno-red-zone -Wall \
+    -DEFI_FUNCTION_WRAPPER
+
+EFI_LDFLAGS = \
+    -nostdlib -znocombreloc -T /usr/lib/elf_x86_64_efi.lds -shared \
+    -Bsymbolic -L /usr/lib/ /usr/lib/crt0-efi-x86_64.o \
+    /usr/lib/elf_x86_64_efi.lds
+
+EFI_LDLIBS  = -lefi -lgnuefi
+
+EFI_OBJCOPY_SECTIONS    = -j .text -j .sdata -j .data -j .dynamic \
+                            -j .dynsym  -j .rel -j .rela -j .reloc
+
+EFI_OBJCOPY_TARGET      = --target=efi-app-x86_64
+
+all: $(EFI_TARGET)
+
+$(EFI_OBJ_FILES): $(EFI_SRC_FILES)
+	$(EFI_CC) $(EFI_CFLAGS) -c $(EFI_SRC_FILES)
+
+$(EFI_SHARED_OBJ): $(EFI_OBJ_FILES)
+	$(EFI_LD) $(EFI_LDFLAGS) $(EFI_OBJ_FILES) -o $@ $(EFI_LDLIBS)
+
+$(EFI_TARGET): $(EFI_SHARED_OBJ)
+	$(EFI_OBJCOPY) $(EFI_OBJCOPY_SECTIONS) $(EFI_OBJCOPY_TARGET) $^ $@
+
+clean:
+	rm -f  $(EFI_OBJ_FILES) $(EFI_SHARED_OBJ) $(EFI_TARGET) *~
diff --git a/examples/uefi/main.c b/examples/uefi/main.c
new file mode 100644
index 0000000..8d9c0ee
--- /dev/null
+++ b/examples/uefi/main.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <libavb_ab/libavb_ab.h>
+
+#include "uefi_avb_boot.h"
+#include "uefi_avb_ops.h"
+
+EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle,
+                           EFI_SYSTEM_TABLE* SystemTable) {
+  AvbOps* ops;
+  AvbABFlowResult ab_result;
+  AvbSlotVerifyData* slot_data;
+  UEFIAvbBootKernelResult boot_result;
+  const char* requested_partitions[] = {"boot", NULL};
+  bool unlocked = true;
+
+  InitializeLib(ImageHandle, SystemTable);
+
+  avb_print("UEFI AVB-based bootloader\n");
+
+  ops = uefi_avb_ops_new(ImageHandle);
+  if (ops == NULL) {
+    avb_fatal("Error allocating AvbOps.\n");
+  }
+
+  if (ops->read_is_device_unlocked(ops, &unlocked) != AVB_IO_RESULT_OK) {
+    avb_fatal("Error determining whether device is unlocked.\n");
+  }
+  avb_printv("read_is_device_unlocked() ops returned that device is ",
+             unlocked ? "UNLOCKED" : "LOCKED",
+             "\n",
+             NULL);
+
+  ab_result = avb_ab_flow(ops->ab_ops,
+                          requested_partitions,
+                          unlocked /* allow_verification_error */,
+                          &slot_data);
+  avb_printv("avb_ab_flow() returned ",
+             avb_ab_flow_result_to_string(ab_result),
+             "\n",
+             NULL);
+  switch (ab_result) {
+    case AVB_AB_FLOW_RESULT_OK:
+    case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR:
+      avb_printv("slot_suffix: ", slot_data->ab_suffix, "\n", NULL);
+      avb_printv("cmdline:     ", slot_data->cmdline, "\n", NULL);
+      /* Pass 'skip_initramfs' since we're not booting into recovery mode. */
+      boot_result =
+          uefi_avb_boot_kernel(ImageHandle, slot_data, "skip_initramfs");
+      avb_fatalv("uefi_avb_boot_kernel() failed with error ",
+                 uefi_avb_boot_kernel_result_to_string(boot_result),
+                 "\n",
+                 NULL);
+      avb_slot_verify_data_free(slot_data);
+      break;
+    case AVB_AB_FLOW_RESULT_ERROR_OOM:
+      avb_fatal("OOM error while doing A/B select flow.\n");
+      break;
+    case AVB_AB_FLOW_RESULT_ERROR_IO:
+      avb_fatal("I/O error while doing A/B select flow.\n");
+      break;
+    case AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS:
+      avb_fatal("No bootable slots - enter repair mode\n");
+      break;
+  }
+  uefi_avb_ops_free(ops);
+
+  return EFI_SUCCESS;
+}
diff --git a/examples/uefi/uefi_avb_boot.c b/examples/uefi/uefi_avb_boot.c
new file mode 100644
index 0000000..800c9a1
--- /dev/null
+++ b/examples/uefi/uefi_avb_boot.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "bootimg.h"
+
+#include "uefi_avb_boot.h"
+#include "uefi_avb_util.h"
+
+/* See Documentation/x86/boot.txt for this struct and more information
+ * about the boot/handover protocol.
+ */
+
+#define SETUP_MAGIC 0x53726448 /* "HdrS" */
+
+struct SetupHeader {
+  UINT8 boot_sector[0x01f1];
+  UINT8 setup_secs;
+  UINT16 root_flags;
+  UINT32 sys_size;
+  UINT16 ram_size;
+  UINT16 video_mode;
+  UINT16 root_dev;
+  UINT16 signature;
+  UINT16 jump;
+  UINT32 header;
+  UINT16 version;
+  UINT16 su_switch;
+  UINT16 setup_seg;
+  UINT16 start_sys;
+  UINT16 kernel_ver;
+  UINT8 loader_id;
+  UINT8 load_flags;
+  UINT16 movesize;
+  UINT32 code32_start;
+  UINT32 ramdisk_start;
+  UINT32 ramdisk_len;
+  UINT32 bootsect_kludge;
+  UINT16 heap_end;
+  UINT8 ext_loader_ver;
+  UINT8 ext_loader_type;
+  UINT32 cmd_line_ptr;
+  UINT32 ramdisk_max;
+  UINT32 kernel_alignment;
+  UINT8 relocatable_kernel;
+  UINT8 min_alignment;
+  UINT16 xloadflags;
+  UINT32 cmdline_size;
+  UINT32 hardware_subarch;
+  UINT64 hardware_subarch_data;
+  UINT32 payload_offset;
+  UINT32 payload_length;
+  UINT64 setup_data;
+  UINT64 pref_address;
+  UINT32 init_size;
+  UINT32 handover_offset;
+} __attribute__((packed));
+
+#ifdef __x86_64__
+typedef VOID (*handover_f)(VOID* image,
+                           EFI_SYSTEM_TABLE* table,
+                           struct SetupHeader* setup);
+static inline VOID linux_efi_handover(EFI_HANDLE image,
+                                      struct SetupHeader* setup) {
+  handover_f handover;
+
+  asm volatile("cli");
+  handover =
+      (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset);
+  handover(image, ST, setup);
+}
+#else
+typedef VOID (*handover_f)(VOID* image,
+                           EFI_SYSTEM_TABLE* table,
+                           struct SetupHeader* setup)
+    __attribute__((regparm(0)));
+static inline VOID linux_efi_handover(EFI_HANDLE image,
+                                      struct SetupHeader* setup) {
+  handover_f handover;
+
+  handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset);
+  handover(image, ST, setup);
+}
+#endif
+
+static size_t round_up(size_t value, size_t size) {
+  size_t ret = value + size - 1;
+  ret /= size;
+  ret *= size;
+  return ret;
+}
+
+UEFIAvbBootKernelResult uefi_avb_boot_kernel(EFI_HANDLE efi_image_handle,
+                                             AvbSlotVerifyData* slot_data,
+                                             const char* cmdline_extra) {
+  UEFIAvbBootKernelResult ret;
+  const boot_img_hdr* header;
+  EFI_STATUS err;
+  UINT8* kernel_buf = NULL;
+  UINT8* initramfs_buf = NULL;
+  UINT8* cmdline_utf8 = NULL;
+  AvbPartitionData* boot;
+  size_t offset;
+  uint64_t total_size;
+  size_t initramfs_size;
+  size_t cmdline_first_len;
+  size_t cmdline_second_len;
+  size_t cmdline_extra_len;
+  size_t cmdline_utf8_len;
+  struct SetupHeader* image_setup;
+  struct SetupHeader* setup;
+  EFI_PHYSICAL_ADDRESS addr;
+
+  if (slot_data->num_loaded_partitions != 1) {
+    avb_error("No boot partition.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  boot = &slot_data->loaded_partitions[0];
+  if (avb_strcmp(boot->partition_name, "boot") != 0) {
+    avb_errorv(
+        "Unexpected partition name '", boot->partition_name, "'.\n", NULL);
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  header = (const boot_img_hdr*)boot->data;
+
+  /* Check boot image header magic field. */
+  if (avb_memcmp(BOOT_MAGIC, header->magic, BOOT_MAGIC_SIZE)) {
+    avb_error("Wrong boot image header magic.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  /* Sanity check header. */
+  total_size = header->kernel_size;
+  if (!avb_safe_add_to(&total_size, header->ramdisk_size) ||
+      !avb_safe_add_to(&total_size, header->second_size)) {
+    avb_error("Overflow while adding sizes of kernel and initramfs.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+  if (total_size > boot->data_size) {
+    avb_error("Invalid kernel/initramfs sizes.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  /* The kernel has to be in its own specific memory pool. */
+  err = uefi_call_wrapper(BS->AllocatePool,
+                          NUM_ARGS_ALLOCATE_POOL,
+                          EfiLoaderCode,
+                          header->kernel_size,
+                          &kernel_buf);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not allocate kernel buffer.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+    goto out;
+  }
+  avb_memcpy(kernel_buf, boot->data + header->page_size, header->kernel_size);
+
+  /* Ditto for the initrd. */
+  initramfs_buf = NULL;
+  initramfs_size = header->ramdisk_size + header->second_size;
+  if (initramfs_size > 0) {
+    err = uefi_call_wrapper(BS->AllocatePool,
+                            NUM_ARGS_ALLOCATE_POOL,
+                            EfiLoaderCode,
+                            initramfs_size,
+                            &initramfs_buf);
+    if (EFI_ERROR(err)) {
+      avb_error("Could not allocate initrd buffer.\n");
+      ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+      goto out;
+    }
+    /* Concatente the first and second initramfs. */
+    offset = header->page_size;
+    offset += round_up(header->kernel_size, header->page_size);
+    avb_memcpy(initramfs_buf, boot->data + offset, header->ramdisk_size);
+    offset += round_up(header->ramdisk_size, header->page_size);
+    avb_memcpy(initramfs_buf, boot->data + offset, header->second_size);
+  }
+
+  /* Prepare the command-line. */
+  cmdline_first_len = avb_strlen((const char*)header->cmdline);
+  cmdline_second_len = avb_strlen(slot_data->cmdline);
+  cmdline_extra_len = cmdline_extra != NULL ? avb_strlen(cmdline_extra) : 0;
+  if (cmdline_extra_len > 0) {
+    cmdline_extra_len += 1;
+  }
+  cmdline_utf8_len =
+      cmdline_first_len + 1 + cmdline_second_len + 1 + cmdline_extra_len;
+  err = uefi_call_wrapper(BS->AllocatePool,
+                          NUM_ARGS_ALLOCATE_POOL,
+                          EfiLoaderCode,
+                          cmdline_utf8_len,
+                          &cmdline_utf8);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not allocate kernel cmdline.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+    goto out;
+  }
+  offset = 0;
+  avb_memcpy(cmdline_utf8, header->cmdline, cmdline_first_len);
+  offset += cmdline_first_len;
+  cmdline_utf8[offset] = ' ';
+  offset += 1;
+  avb_memcpy(cmdline_utf8 + offset, slot_data->cmdline, cmdline_second_len);
+  offset += cmdline_second_len;
+  if (cmdline_extra_len > 0) {
+    cmdline_utf8[offset] = ' ';
+    avb_memcpy(cmdline_utf8 + offset + 1, cmdline_extra, cmdline_extra_len - 1);
+    offset += cmdline_extra_len;
+  }
+  cmdline_utf8[offset] = '\0';
+  offset += 1;
+  avb_assert(offset == cmdline_utf8_len);
+
+  /* Now set up the EFI handover. */
+  image_setup = (struct SetupHeader*)kernel_buf;
+  if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) {
+    avb_error("Wrong kernel header magic.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
+    goto out;
+  }
+
+  if (image_setup->version < 0x20b) {
+    avb_error("Wrong version.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
+    goto out;
+  }
+
+  if (!image_setup->relocatable_kernel) {
+    avb_error("Kernel is not relocatable.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
+    goto out;
+  }
+
+  addr = 0x3fffffff;
+  err = uefi_call_wrapper(BS->AllocatePages,
+                          4,
+                          AllocateMaxAddress,
+                          EfiLoaderData,
+                          EFI_SIZE_TO_PAGES(0x4000),
+                          &addr);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not allocate setup buffer.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+    goto out;
+  }
+  setup = (struct SetupHeader*)(UINTN)addr;
+  avb_memset(setup, '\0', 0x4000);
+  avb_memcpy(setup, image_setup, sizeof(struct SetupHeader));
+  setup->loader_id = 0xff;
+  setup->code32_start =
+      ((uintptr_t)kernel_buf) + (image_setup->setup_secs + 1) * 512;
+  setup->cmd_line_ptr = (uintptr_t)cmdline_utf8;
+
+  setup->ramdisk_start = (uintptr_t)initramfs_buf;
+  setup->ramdisk_len = (uintptr_t)initramfs_size;
+
+  /* Jump to the kernel. */
+  linux_efi_handover(efi_image_handle, setup);
+
+  ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL;
+out:
+  return ret;
+}
+
+const char* uefi_avb_boot_kernel_result_to_string(
+    UEFIAvbBootKernelResult result) {
+  const char* ret = NULL;
+
+  switch (result) {
+    case UEFI_AVB_BOOT_KERNEL_RESULT_OK:
+      ret = "OK";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM:
+      ret = "ERROR_OEM";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_IO:
+      ret = "ERROR_IO";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT:
+      ret = "ERROR_PARTITION_INVALID_FORMAT";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT:
+      ret = "ERROR_KERNEL_INVALID_FORMAT";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL:
+      ret = "ERROR_START_KERNEL";
+      break;
+      /* Do not add a 'default:' case here because of -Wswitch. */
+  }
+
+  if (ret == NULL) {
+    avb_error("Unknown UEFIAvbBootKernelResult value.\n");
+    ret = "(unknown)";
+  }
+
+  return ret;
+}
diff --git a/examples/uefi/uefi_avb_boot.h b/examples/uefi/uefi_avb_boot.h
new file mode 100644
index 0000000..d2b8b38
--- /dev/null
+++ b/examples/uefi/uefi_avb_boot.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef UEFI_AVB_BOOT_H_
+#define UEFI_AVB_BOOT_H_
+
+#include <efi.h>
+#include <libavb/libavb.h>
+
+/* Return codes used in uefi_avb_boot_kernel().
+ *
+ * Use uefi_avb_boot_kernel_result_to_string to get a textual
+ * representation usable for error/debug output.
+ */
+typedef enum {
+  UEFI_AVB_BOOT_KERNEL_RESULT_OK,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_IO,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL
+} UEFIAvbBootKernelResult;
+
+/* Jumps to the Linux kernel embedded in the Android boot image which
+ * has been loaded in |slot_data|. This also supports setting up the
+ * initramfs from the boot image. The command-line to be passed to the
+ * kernel will be the concatenation of
+ *
+ *  1. the cmdline in the Android boot image; and
+ *  2. the cmdline in |slot_data|; and
+ *  3. |cmdline_extra| if not NULL
+ *
+ * in that order with spaces inserted as necessary.
+ *
+ * This function only returns if an error occurs.
+ */
+UEFIAvbBootKernelResult uefi_avb_boot_kernel(EFI_HANDLE efi_image_handle,
+                                             AvbSlotVerifyData* slot_data,
+                                             const char* cmdline_extra);
+
+/* Get a textual representation of |result|. */
+const char* uefi_avb_boot_kernel_result_to_string(
+    UEFIAvbBootKernelResult result);
+
+#endif /* UEFI_AVB_BOOT_H_ */
diff --git a/examples/uefi/uefi_avb_ops.c b/examples/uefi/uefi_avb_ops.c
new file mode 100644
index 0000000..3f22d03
--- /dev/null
+++ b/examples/uefi/uefi_avb_ops.c
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <libavb_ab/libavb_ab.h>
+
+#include "uefi_avb_ops.h"
+#include "uefi_avb_util.h"
+
+#include <efi.h>
+#include <efilib.h>
+
+/* GPT related constants. */
+#define GPT_REVISION 0x00010000
+#define GPT_MAGIC "EFI PART"
+#define GPT_MIN_SIZE 92
+#define GPT_ENTRIES_LBA 2
+#define AVB_BLOCK_SIZE 512
+#define ENTRIES_PER_BLOCK 4
+#define ENTRY_NAME_LEN 36
+#define MAX_GPT_ENTRIES 128
+
+typedef struct {
+  uint8_t signature[8];
+  uint32_t revision;
+  uint32_t header_size;
+  uint32_t header_crc32;
+  uint32_t reserved;
+  uint64_t header_lba;
+  uint64_t alternate_header_lba;
+  uint64_t first_usable_lba;
+  uint64_t last_usable_lba;
+  uint8_t disk_guid[16];
+  uint64_t entry_lba;
+  uint32_t entry_count;
+  uint32_t entry_size;
+  uint32_t entry_crc32;
+  uint8_t reserved2[420];
+} GPTHeader;
+
+typedef struct {
+  uint8_t type_GUID[16];
+  uint8_t unique_GUID[16];
+  uint64_t first_lba;
+  uint64_t last_lba;
+  uint64_t flags;
+  uint16_t name[ENTRY_NAME_LEN];
+} GPTEntry;
+
+static EFI_STATUS find_partition_entry_by_name(IN EFI_BLOCK_IO* block_io,
+                                               const char* partition_name,
+                                               GPTEntry** entry_buf) {
+  EFI_STATUS err;
+  GPTHeader* gpt_header = NULL;
+  GPTEntry all_gpt_entries[MAX_GPT_ENTRIES];
+  uint16_t* partition_name_ucs2 = NULL;
+  size_t partition_name_bytes;
+  size_t partition_name_ucs2_capacity;
+  size_t partition_name_ucs2_len;
+
+  gpt_header = (GPTHeader*)avb_malloc(sizeof(GPTHeader));
+  if (gpt_header == NULL) {
+    avb_error("Could not allocate for GPT header\n");
+    return EFI_NOT_FOUND;
+  }
+
+  *entry_buf = (GPTEntry*)avb_malloc(sizeof(GPTEntry) * ENTRIES_PER_BLOCK);
+  if (entry_buf == NULL) {
+    avb_error("Could not allocate for partition entry\n");
+    avb_free(gpt_header);
+    return EFI_NOT_FOUND;
+  }
+
+  err = uefi_call_wrapper(block_io->ReadBlocks,
+                          NUM_ARGS_READ_BLOCKS,
+                          block_io,
+                          block_io->Media->MediaId,
+                          1,
+                          sizeof(GPTHeader),
+                          gpt_header);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not ReadBlocks for gpt header\n");
+    avb_free(gpt_header);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+
+  partition_name_bytes = avb_strlen(partition_name);
+  partition_name_ucs2_capacity = sizeof(uint16_t) * (partition_name_bytes + 1);
+  partition_name_ucs2 = avb_calloc(partition_name_ucs2_capacity);
+  if (partition_name_ucs2 == NULL) {
+    avb_error("Could not allocate for ucs2 partition name\n");
+    avb_free(gpt_header);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+  if (!uefi_avb_utf8_to_ucs2((const uint8_t*)partition_name,
+                             partition_name_bytes,
+                             partition_name_ucs2,
+                             partition_name_ucs2_capacity,
+                             NULL)) {
+    avb_error("Could not convert partition name to UCS-2\n");
+    avb_free(gpt_header);
+    avb_free(partition_name_ucs2);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+  partition_name_ucs2_len = StrLen(partition_name_ucs2);
+
+  /* Block-aligned bytes for entries. */
+  UINTN entries_num_bytes =
+      block_io->Media->BlockSize * (MAX_GPT_ENTRIES / ENTRIES_PER_BLOCK);
+
+  err = uefi_call_wrapper(block_io->ReadBlocks,
+                          NUM_ARGS_READ_BLOCKS,
+                          block_io,
+                          block_io->Media->MediaId,
+                          GPT_ENTRIES_LBA,
+                          entries_num_bytes,
+                          &all_gpt_entries);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not ReadBlocks for GPT header\n");
+    avb_free(gpt_header);
+    avb_free(partition_name_ucs2);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+
+  /* Find matching partition name. */
+  for (int n = 0; n < gpt_header->entry_count; n++) {
+    if ((partition_name_ucs2_len == StrLen(all_gpt_entries[n].name)) &&
+        avb_memcmp(all_gpt_entries[n].name,
+                   partition_name_ucs2,
+                   partition_name_ucs2_len * 2) == 0) {
+      avb_memcpy((*entry_buf), &all_gpt_entries[n], sizeof(GPTEntry));
+      avb_free(partition_name_ucs2);
+      avb_free(gpt_header);
+      return EFI_SUCCESS;
+    }
+  }
+
+  avb_free(partition_name_ucs2);
+  avb_free(gpt_header);
+  avb_free(*entry_buf);
+  *entry_buf = NULL;
+  return EFI_NOT_FOUND;
+}
+
+static AvbIOResult read_from_partition(AvbOps* ops,
+                                       const char* partition_name,
+                                       int64_t offset_from_partition,
+                                       size_t num_bytes,
+                                       void* buf,
+                                       size_t* out_num_read) {
+  EFI_STATUS err;
+  GPTEntry* partition_entry;
+  uint64_t partition_size;
+  UEFIAvbOpsData* data = (UEFIAvbOpsData*)ops->user_data;
+
+  avb_assert(partition_name != NULL);
+  avb_assert(buf != NULL);
+  avb_assert(out_num_read != NULL);
+
+  err = find_partition_entry_by_name(
+      data->block_io, partition_name, &partition_entry);
+  if (EFI_ERROR(err)) {
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  partition_size = (partition_entry->last_lba - partition_entry->first_lba) *
+                   data->block_io->Media->BlockSize;
+
+  if (offset_from_partition < 0) {
+    if ((-offset_from_partition) > partition_size) {
+      avb_error("Offset outside range.\n");
+      avb_free(partition_entry);
+      return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+    }
+    offset_from_partition = partition_size - (-offset_from_partition);
+  }
+
+  /* Check if num_bytes goes beyond partition end. If so, don't read beyond
+   * this boundary -- do a partial I/O instead.
+   */
+  if (num_bytes > partition_size - offset_from_partition)
+    *out_num_read = partition_size - offset_from_partition;
+  else
+    *out_num_read = num_bytes;
+
+  err = uefi_call_wrapper(
+      data->disk_io->ReadDisk,
+      5,
+      data->disk_io,
+      data->block_io->Media->MediaId,
+      (partition_entry->first_lba * data->block_io->Media->BlockSize) +
+          offset_from_partition,
+      *out_num_read,
+      buf);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not read from Disk.\n");
+    *out_num_read = 0;
+    avb_free(partition_entry);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  avb_free(partition_entry);
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult write_to_partition(AvbOps* ops,
+                                      const char* partition_name,
+                                      int64_t offset_from_partition,
+                                      size_t num_bytes,
+                                      const void* buf) {
+  EFI_STATUS err;
+  GPTEntry* partition_entry;
+  uint64_t partition_size;
+  UEFIAvbOpsData* data = (UEFIAvbOpsData*)ops->user_data;
+
+  avb_assert(partition_name != NULL);
+  avb_assert(buf != NULL);
+
+  err = find_partition_entry_by_name(
+      data->block_io, partition_name, &partition_entry);
+  if (EFI_ERROR(err)) {
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  partition_size = (partition_entry->last_lba - partition_entry->first_lba) *
+                   data->block_io->Media->BlockSize;
+
+  if (offset_from_partition < 0) {
+    if ((-offset_from_partition) > partition_size) {
+      avb_error("Offset outside range.\n");
+      avb_free(partition_entry);
+      return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+    }
+    offset_from_partition = partition_size - (-offset_from_partition);
+  }
+
+  /* Check if num_bytes goes beyond partition end. If so, error out -- no
+   * partial I/O.
+   */
+  if (num_bytes > partition_size - offset_from_partition) {
+    avb_error("Cannot write beyond partition boundary.\n");
+    avb_free(partition_entry);
+    return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+  }
+
+  err = uefi_call_wrapper(
+      data->disk_io->WriteDisk,
+      5,
+      data->disk_io,
+      data->block_io->Media->MediaId,
+      (partition_entry->first_lba * data->block_io->Media->BlockSize) +
+          offset_from_partition,
+      num_bytes,
+      buf);
+
+  if (EFI_ERROR(err)) {
+    avb_error("Could not write to Disk.\n");
+    avb_free(partition_entry);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  avb_free(partition_entry);
+  return AVB_IO_RESULT_OK;
+}
+
+/* Helper method to get the parent path to the current |walker| path
+ * given the initial path, |init|. Resulting path is stored in |next|.
+ * Caller is responsible for freeing |next|. Stores allocated bytes
+ * for |next| in |out_bytes|. Returns EFI_SUCCESS on success.
+ */
+static EFI_STATUS walk_path(IN EFI_DEVICE_PATH* init,
+                            IN EFI_DEVICE_PATH* walker,
+                            OUT EFI_DEVICE_PATH** next,
+                            OUT UINTN* out_bytes) {
+  /* Number of bytes from initial path to current walker. */
+  UINTN walker_bytes = (uint8_t*)NextDevicePathNode(walker) - (uint8_t*)init;
+  *out_bytes = sizeof(EFI_DEVICE_PATH) + walker_bytes;
+
+  *next = (EFI_DEVICE_PATH*)avb_malloc(*out_bytes);
+  if (*next == NULL) {
+    *out_bytes = 0;
+    return EFI_NOT_FOUND;
+  }
+
+  /* Copy in the previous paths. */
+  avb_memcpy((*next), init, walker_bytes);
+  /* Copy in the new ending of the path. */
+  avb_memcpy(
+      (uint8_t*)(*next) + walker_bytes, EndDevicePath, sizeof(EFI_DEVICE_PATH));
+  return EFI_SUCCESS;
+}
+
+/* Helper method to validate a GPT header, |gpth|.
+ *
+ * @return EFI_STATUS EFI_SUCCESS on success.
+ */
+static EFI_STATUS validate_gpt(const IN GPTHeader* gpth) {
+  if (avb_memcmp(gpth->signature, GPT_MAGIC, sizeof(gpth->signature)) != 0) {
+    avb_error("GPT signature does not match.\n");
+    return EFI_NOT_FOUND;
+  }
+  /* Make sure GPT header bytes are within minimun and block size. */
+  if (gpth->header_size < GPT_MIN_SIZE) {
+    avb_error("GPT header too small.\n");
+    return EFI_NOT_FOUND;
+  }
+  if (gpth->header_size > AVB_BLOCK_SIZE) {
+    avb_error("GPT header too big.\n");
+    return EFI_NOT_FOUND;
+  }
+
+  GPTHeader gpth_tmp = {{0}};
+  avb_memcpy(&gpth_tmp, gpth, sizeof(GPTHeader));
+  uint32_t gpt_header_crc = gpth_tmp.header_crc32;
+  gpth_tmp.header_crc32 = 0;
+  uint32_t gpt_header_crc_calc =
+      CalculateCrc((uint8_t*)&gpth_tmp, gpth_tmp.header_size);
+
+  if (gpt_header_crc != gpt_header_crc_calc) {
+    avb_error("GPT header crc invalid.\n");
+    return EFI_NOT_FOUND;
+  }
+
+  if (gpth->revision != GPT_REVISION) {
+    avb_error("GPT header wrong revision.\n");
+    return EFI_NOT_FOUND;
+  }
+
+  return EFI_SUCCESS;
+}
+
+/* Queries |disk_handle| for a |block_io| device and the corresponding
+ * path, |block_path|.  The |block_io| device is found by iteratively
+ * querying parent devices and checking for a GPT Header.  This
+ * ensures the resulting |block_io| device is the top level block
+ * device having access to partition entries. Returns EFI_STATUS
+ * EFI_NOT_FOUND on failure, EFI_SUCCESS otherwise.
+ */
+static EFI_STATUS get_disk_block_io(IN EFI_HANDLE* block_handle,
+                                    OUT EFI_BLOCK_IO** block_io,
+                                    OUT EFI_DISK_IO** disk_io,
+                                    OUT EFI_DEVICE_PATH** io_path) {
+  EFI_STATUS err;
+  EFI_HANDLE disk_handle;
+  UINTN path_bytes;
+  EFI_DEVICE_PATH* disk_path;
+  EFI_DEVICE_PATH* walker_path;
+  EFI_DEVICE_PATH* init_path;
+  GPTHeader gpt_header = {{0}};
+  init_path = DevicePathFromHandle(block_handle);
+
+  if (!init_path) {
+    return EFI_NOT_FOUND;
+  }
+
+  walker_path = init_path;
+  while (!IsDevicePathEnd(walker_path)) {
+    walker_path = NextDevicePathNode(walker_path);
+
+    err = walk_path(init_path, walker_path, &(*io_path), &path_bytes);
+    if (EFI_ERROR(err)) {
+      avb_error("Cannot walk device path.\n");
+      return EFI_NOT_FOUND;
+    }
+
+    disk_path = (EFI_DEVICE_PATH*)avb_malloc(path_bytes);
+    avb_memcpy(disk_path, *io_path, path_bytes);
+    err = uefi_call_wrapper(BS->LocateDevicePath,
+                            NUM_ARGS_LOCATE_DEVICE_PATH,
+                            &BlockIoProtocol,
+                            &(*io_path),
+                            &block_handle);
+    if (EFI_ERROR(err)) {
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+    err = uefi_call_wrapper(BS->LocateDevicePath,
+                            NUM_ARGS_LOCATE_DEVICE_PATH,
+                            &DiskIoProtocol,
+                            &disk_path,
+                            &disk_handle);
+    if (EFI_ERROR(err)) {
+      avb_error("LocateDevicePath, DISK_IO_PROTOCOL.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    /* Handle Block and Disk I/O. Attempt to get handle on device,
+     * must be Block/Disk Io type.
+     */
+    err = uefi_call_wrapper(BS->HandleProtocol,
+                            NUM_ARGS_HANDLE_PROTOCOL,
+                            block_handle,
+                            &BlockIoProtocol,
+                            (VOID**)&(*block_io));
+    if (EFI_ERROR(err)) {
+      avb_error("Cannot get handle on block device.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+    err = uefi_call_wrapper(BS->HandleProtocol,
+                            NUM_ARGS_HANDLE_PROTOCOL,
+                            disk_handle,
+                            &DiskIoProtocol,
+                            (VOID**)&(*disk_io));
+    if (EFI_ERROR(err)) {
+      avb_error("Cannot get handle on disk device.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    if ((*block_io)->Media->LogicalPartition ||
+        !(*block_io)->Media->MediaPresent) {
+      avb_error("Logical partion or No Media Present, continue...\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    err = uefi_call_wrapper((*block_io)->ReadBlocks,
+                            NUM_ARGS_READ_BLOCKS,
+                            (*block_io),
+                            (*block_io)->Media->MediaId,
+                            1,
+                            sizeof(GPTHeader),
+                            &gpt_header);
+
+    if (EFI_ERROR(err)) {
+      avb_error("ReadBlocks, Block Media error.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    err = validate_gpt(&gpt_header);
+    if (EFI_ERROR(err)) {
+      avb_error("Invalid GPTHeader\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    return EFI_SUCCESS;
+  }
+
+  (*block_io) = NULL;
+  return EFI_NOT_FOUND;
+}
+
+static AvbIOResult validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_key_is_trusted) {
+  /* For now we just allow any key. */
+  if (out_key_is_trusted != NULL) {
+    *out_key_is_trusted = true;
+  }
+  avb_debug("TODO: implement validate_vbmeta_public_key().\n");
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult read_rollback_index(AvbOps* ops,
+                                       size_t rollback_index_slot,
+                                       uint64_t* out_rollback_index) {
+  /* For now we always return 0 as the stored rollback index. */
+  avb_debug("TODO: implement read_rollback_index().\n");
+  if (out_rollback_index != NULL) {
+    *out_rollback_index = 0;
+  }
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult write_rollback_index(AvbOps* ops,
+                                        size_t rollback_index_slot,
+                                        uint64_t rollback_index) {
+  /* For now this is a no-op. */
+  avb_debug("TODO: implement write_rollback_index().\n");
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult read_is_device_unlocked(AvbOps* ops, bool* out_is_unlocked) {
+  /* For now we always return that the device is unlocked. */
+  avb_debug("TODO: implement read_is_device_unlocked().\n");
+  *out_is_unlocked = true;
+  return AVB_IO_RESULT_OK;
+}
+
+static void set_hex(char* buf, uint8_t value) {
+  char hex_digits[17] = "0123456789abcdef";
+  buf[0] = hex_digits[value >> 4];
+  buf[1] = hex_digits[value & 0x0f];
+}
+
+static AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                                 const char* partition,
+                                                 char* guid_buf,
+                                                 size_t guid_buf_size) {
+  EFI_STATUS err;
+  GPTEntry* partition_entry;
+  UEFIAvbOpsData* data = (UEFIAvbOpsData*)ops->user_data;
+
+  avb_assert(partition != NULL);
+  avb_assert(guid_buf != NULL);
+
+  err =
+      find_partition_entry_by_name(data->block_io, partition, &partition_entry);
+  if (EFI_ERROR(err)) {
+    avb_error("Error getting unique GUID for partition.\n");
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  if (guid_buf_size < 37) {
+    avb_error("GUID buffer size too small.\n");
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  /* The GUID encoding is somewhat peculiar in terms of byte order. It
+   * is what it is.
+   */
+  set_hex(guid_buf + 0, partition_entry->unique_GUID[3]);
+  set_hex(guid_buf + 2, partition_entry->unique_GUID[2]);
+  set_hex(guid_buf + 4, partition_entry->unique_GUID[1]);
+  set_hex(guid_buf + 6, partition_entry->unique_GUID[0]);
+  guid_buf[8] = '-';
+  set_hex(guid_buf + 9, partition_entry->unique_GUID[5]);
+  set_hex(guid_buf + 11, partition_entry->unique_GUID[4]);
+  guid_buf[13] = '-';
+  set_hex(guid_buf + 14, partition_entry->unique_GUID[7]);
+  set_hex(guid_buf + 16, partition_entry->unique_GUID[6]);
+  guid_buf[18] = '-';
+  set_hex(guid_buf + 19, partition_entry->unique_GUID[8]);
+  set_hex(guid_buf + 21, partition_entry->unique_GUID[9]);
+  guid_buf[23] = '-';
+  set_hex(guid_buf + 24, partition_entry->unique_GUID[10]);
+  set_hex(guid_buf + 26, partition_entry->unique_GUID[11]);
+  set_hex(guid_buf + 28, partition_entry->unique_GUID[12]);
+  set_hex(guid_buf + 30, partition_entry->unique_GUID[13]);
+  set_hex(guid_buf + 32, partition_entry->unique_GUID[14]);
+  set_hex(guid_buf + 34, partition_entry->unique_GUID[15]);
+  guid_buf[36] = '\0';
+  return AVB_IO_RESULT_OK;
+}
+
+AvbOps* uefi_avb_ops_new(EFI_HANDLE app_image) {
+  UEFIAvbOpsData* data;
+  EFI_STATUS err;
+  EFI_LOADED_IMAGE* loaded_app_image = NULL;
+  EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL;
+
+  data = avb_calloc(sizeof(UEFIAvbOpsData));
+  data->ops.user_data = data;
+
+  data->efi_image_handle = app_image;
+  err = uefi_call_wrapper(BS->HandleProtocol,
+                          NUM_ARGS_HANDLE_PROTOCOL,
+                          app_image,
+                          &loaded_image_protocol,
+                          (VOID**)&loaded_app_image);
+  if (EFI_ERROR(err)) {
+    avb_error("HandleProtocol, LOADED_IMAGE_PROTOCOL.\n");
+    return 0;
+  }
+
+  /* Get parent device disk and block I/O. */
+  err = get_disk_block_io(loaded_app_image->DeviceHandle,
+                          &data->block_io,
+                          &data->disk_io,
+                          &data->path);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not acquire block or disk device handle.\n");
+    return 0;
+  }
+
+  data->ops.ab_ops = &data->ab_ops;
+  data->ops.read_from_partition = read_from_partition;
+  data->ops.write_to_partition = write_to_partition;
+  data->ops.validate_vbmeta_public_key = validate_vbmeta_public_key;
+  data->ops.read_rollback_index = read_rollback_index;
+  data->ops.write_rollback_index = write_rollback_index;
+  data->ops.read_is_device_unlocked = read_is_device_unlocked;
+  data->ops.get_unique_guid_for_partition = get_unique_guid_for_partition;
+
+  data->ab_ops.ops = &data->ops;
+  data->ab_ops.read_ab_metadata = avb_ab_data_read;
+  data->ab_ops.write_ab_metadata = avb_ab_data_write;
+
+  return &data->ops;
+}
+
+void uefi_avb_ops_free(AvbOps* ops) {
+  UEFIAvbOpsData* data = ops->user_data;
+  avb_free(data);
+}
diff --git a/examples/uefi/uefi_avb_ops.h b/examples/uefi/uefi_avb_ops.h
new file mode 100644
index 0000000..80d13ee
--- /dev/null
+++ b/examples/uefi/uefi_avb_ops.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef UEFI_AVB_OPS_H_
+#define UEFI_AVB_OPS_H_
+
+#include <efi.h>
+#include <libavb_ab/libavb_ab.h>
+
+/* The |user_data| member of AvbOps points to a struct of this type. */
+typedef struct UEFIAvbOpsData {
+  AvbOps ops;
+  AvbABOps ab_ops;
+
+  EFI_HANDLE efi_image_handle;
+  EFI_DEVICE_PATH* path;
+  EFI_BLOCK_IO* block_io;
+  EFI_DISK_IO* disk_io;
+} UEFIAvbOpsData;
+
+/* Returns an AvbOps for use with UEFI. */
+AvbOps* uefi_avb_ops_new(EFI_HANDLE app_image);
+
+/* Frees the AvbOps allocated with uefi_avb_ops_new(). */
+void uefi_avb_ops_free(AvbOps* ops);
+
+#endif /* UEFI_AVB_OPS_H_ */
diff --git a/examples/uefi/uefi_avb_sysdeps.c b/examples/uefi/uefi_avb_sysdeps.c
new file mode 100644
index 0000000..4a41c4f
--- /dev/null
+++ b/examples/uefi/uefi_avb_sysdeps.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+#include <efistdarg.h>
+
+#include <libavb/libavb.h>
+
+#include "uefi_avb_util.h"
+
+int avb_memcmp(const void* src1, const void* src2, size_t n) {
+  return (int)CompareMem((VOID*)src1, (VOID*)src2, (UINTN)n);
+}
+
+int avb_strcmp(const char* s1, const char* s2) {
+  return (int)strcmpa((CHAR8*)s1, (CHAR8*)s2);
+}
+
+void* avb_memcpy(void* dest, const void* src, size_t n) {
+  CopyMem(dest, (VOID*)src, (UINTN)n);
+  return dest;
+}
+
+void* avb_memset(void* dest, const int c, size_t n) {
+  SetMem(dest, (UINTN)n, (UINT8)c);
+  return dest;
+}
+
+void avb_print(const char* message) {
+  size_t utf8_num_bytes = avb_strlen(message) + 1;
+  size_t max_ucs2_bytes = utf8_num_bytes * 2;
+  uint16_t* message_ucs2 = (uint16_t*)avb_calloc(max_ucs2_bytes);
+  if (message_ucs2 == NULL) {
+    return;
+  }
+  if (uefi_avb_utf8_to_ucs2((const uint8_t*)message,
+                            utf8_num_bytes,
+                            message_ucs2,
+                            max_ucs2_bytes,
+                            NULL)) {
+    Print(message_ucs2);
+  }
+  avb_free(message_ucs2);
+}
+
+void avb_printv(const char* message, ...) {
+  va_list ap;
+
+  va_start(ap, message);
+  do {
+    avb_print(message);
+    message = va_arg(ap, const char*);
+  } while (message != NULL);
+  va_end(ap);
+}
+
+void avb_abort(void) {
+  avb_print("\nABORTING...\n");
+  uefi_call_wrapper(BS->Stall, 1, 5 * 1000 * 1000);
+  uefi_call_wrapper(BS->Exit, 4, NULL, EFI_NOT_FOUND, 0, NULL);
+  while (true) {
+    ;
+  }
+}
+
+void* avb_malloc_(size_t size) {
+  EFI_STATUS err;
+  void* x;
+
+  err = uefi_call_wrapper(
+      BS->AllocatePool, 3, EfiBootServicesData, (UINTN)size, &x);
+  if (EFI_ERROR(err)) {
+    return NULL;
+  }
+
+  return x;
+}
+
+void avb_free(void* ptr) {
+  EFI_STATUS err;
+  err = uefi_call_wrapper(BS->FreePool, 1, ptr);
+
+  if (EFI_ERROR(err)) {
+    Print(L"Warning: Bad avb_free: %r\n", err);
+    uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
+  }
+}
+
+size_t avb_strlen(const char* str) {
+  return strlena((CHAR8*)str);
+}
diff --git a/examples/uefi/uefi_avb_util.c b/examples/uefi/uefi_avb_util.c
new file mode 100644
index 0000000..1d59af7
--- /dev/null
+++ b/examples/uefi/uefi_avb_util.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "uefi_avb_util.h"
+
+bool uefi_avb_utf8_to_ucs2(const uint8_t* utf8_data,
+                           size_t utf8_num_bytes,
+                           uint16_t* ucs2_data,
+                           size_t ucs2_data_capacity_num_bytes,
+                           size_t* out_ucs2_data_num_bytes) {
+  uint32_t i8 = 0;
+  uint32_t i2 = 0;
+
+  do {
+    if (i2 >= ucs2_data_capacity_num_bytes) {
+      break;
+    }
+
+    // cannot represent this character, too long
+    if ((utf8_data[i8] & 0xF8) == 0xF0) {
+      return false;
+    } else if ((utf8_data[i8] & 0xF0) == 0xE0) {
+      ucs2_data[i2] = (uint16_t)((uint16_t)utf8_data[i8] << 12) |
+                      (uint16_t)(((uint16_t)utf8_data[i8 + 1] << 6) & 0x0FC0) |
+                      (uint16_t)((uint16_t)utf8_data[i8 + 2] & 0x003F);
+      i8 += 3;
+    } else if ((utf8_data[i8] & 0xE0) == 0XC0) {
+      ucs2_data[i2] = (uint16_t)(((uint16_t)utf8_data[i8] << 6) & 0x07C0) |
+                      (uint16_t)((uint16_t)utf8_data[i8 + 1] & 0x003F);
+      i8 += 2;
+    } else if (!(utf8_data[i8] >> 7)) {
+      ucs2_data[i2] = (uint16_t)((uint16_t)utf8_data[i8] & 0x00FF);
+      i8++;
+    } else {
+      // invalid utf-8
+      return false;
+    }
+    i2++;
+  } while (i8 < utf8_num_bytes);
+
+  if (out_ucs2_data_num_bytes != NULL) {
+    *out_ucs2_data_num_bytes = i2 * 2;
+  }
+  return true;
+}
diff --git a/examples/uefi/uefi_avb_util.h b/examples/uefi/uefi_avb_util.h
new file mode 100644
index 0000000..6a70557
--- /dev/null
+++ b/examples/uefi/uefi_avb_util.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// NOTE: See avb_sysdeps.h
+
+#ifndef UEFI_AVB_UTIL_H_
+#define UEFI_AVB_UTIL_H_
+
+#include <libavb/libavb.h>
+
+/* Converts UTF-8 to UCS-2. Returns |false| if invalid UTF-8 or if a
+ * rune cannot be represented as UCS-2.
+ */
+bool uefi_avb_utf8_to_ucs2(const uint8_t* utf8_data,
+                           size_t utf8_num_bytes,
+                           uint16_t* ucs2_data,
+                           size_t ucs2_data_capacity_num_bytes,
+                           size_t* out_ucs2_data_num_bytes);
+
+#endif /* UEFI_AVB_UTIL_H_ */