Allow top-level vbmeta struct to be in 'boot' partition.

If there is no 'vbmeta' partition try to load the top-level vbmeta
struct from the end of 'boot' via a footer.

Two use-cases come to mind

 - bring-up when the partition table doesn't yet mention vbmeta; and
 - upgrades where it's not feasible to change the partition table

Bug: None
Test: New unit tests and all unit tests pass.
Change-Id: Id0c6c0f95ce157ffbeb0692d3c9547f49ab58640
diff --git a/avbtool b/avbtool
index 9850634..6ebbd8c 100755
--- a/avbtool
+++ b/avbtool
@@ -1902,7 +1902,7 @@
 
     Arguments:
       output: File to write the image to.
-      chain_partitions: List of partitions to chain.
+      chain_partitions: List of partitions to chain or None.
       algorithm_name: Name of algorithm to use.
       key_path: Path to key to use or None.
       public_key_metadata_path: Path to public key metadata or None.
@@ -1922,26 +1922,10 @@
     """
 
     descriptors = []
-
-    # Insert chained partition descriptors.
-    if chain_partitions:
-      for cp in chain_partitions:
-        cp_tokens = cp.split(':')
-        if len(cp_tokens) != 3:
-          raise AvbError('Malformed chained partition "{}".'.format(cp))
-        desc = AvbChainPartitionDescriptor()
-        desc.partition_name = cp_tokens[0]
-        desc.rollback_index_location = int(cp_tokens[1])
-        if desc.rollback_index_location < 1:
-          raise AvbError('Rollback index location must be 1 or larger.')
-        file_path = cp_tokens[2]
-        desc.public_key = open(file_path, 'rb').read()
-        descriptors.append(desc)
-
     vbmeta_blob = self._generate_vbmeta_blob(
         algorithm_name, key_path, public_key_metadata_path, descriptors,
-        rollback_index, flags, props, props_from_file, kernel_cmdlines,
-        setup_rootfs_from_kernel,
+        chain_partitions, rollback_index, flags, props, props_from_file,
+        kernel_cmdlines, setup_rootfs_from_kernel,
         include_descriptors_from_image, signing_helper, release_string,
         append_to_release_string)
 
@@ -1951,6 +1935,7 @@
 
   def _generate_vbmeta_blob(self, algorithm_name, key_path,
                             public_key_metadata_path, descriptors,
+                            chain_partitions,
                             rollback_index, flags, props, props_from_file,
                             kernel_cmdlines,
                             setup_rootfs_from_kernel,
@@ -1971,6 +1956,7 @@
       key_path: The path to the .pem file used to sign the blob.
       public_key_metadata_path: Path to public key metadata or None.
       descriptors: A list of descriptors to insert or None.
+      chain_partitions: List of partitions to chain or None.
       rollback_index: The rollback index to use.
       flags: Flags to use in the image.
       props: Properties to insert (List of strings of the form 'key:value').
@@ -1998,11 +1984,28 @@
     except KeyError:
       raise AvbError('Unknown algorithm with name {}'.format(algorithm_name))
 
+    if not descriptors:
+      descriptors = []
+
+    # Insert chained partition descriptors, if any
+    if chain_partitions:
+      for cp in chain_partitions:
+        cp_tokens = cp.split(':')
+        if len(cp_tokens) != 3:
+          raise AvbError('Malformed chained partition "{}".'.format(cp))
+        desc = AvbChainPartitionDescriptor()
+        desc.partition_name = cp_tokens[0]
+        desc.rollback_index_location = int(cp_tokens[1])
+        if desc.rollback_index_location < 1:
+          raise AvbError('Rollback index location must be 1 or larger.')
+        file_path = cp_tokens[2]
+        desc.public_key = open(file_path, 'rb').read()
+        descriptors.append(desc)
+
     # Descriptors.
     encoded_descriptors = bytearray()
-    if descriptors:
-      for desc in descriptors:
-        encoded_descriptors.extend(desc.encode())
+    for desc in descriptors:
+      encoded_descriptors.extend(desc.encode())
 
     # Add properties.
     if props:
@@ -2153,8 +2156,9 @@
     write_rsa_key(output, key)
 
   def add_hash_footer(self, image_filename, partition_size, partition_name,
-                      hash_algorithm, salt, algorithm_name, key_path,
-                      public_key_metadata_path, rollback_index, props,
+                      hash_algorithm, salt, chain_partitions, algorithm_name,
+                      key_path,
+                      public_key_metadata_path, rollback_index, flags, props,
                       props_from_file, kernel_cmdlines,
                       setup_rootfs_from_kernel,
                       include_descriptors_from_image, signing_helper,
@@ -2168,10 +2172,12 @@
       partition_name: Name of partition (without A/B suffix).
       hash_algorithm: Hash algorithm to use.
       salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
+      chain_partitions: List of partitions to chain.
       algorithm_name: Name of algorithm to use.
       key_path: Path to key to use or None.
       public_key_metadata_path: Path to public key metadata or None.
       rollback_index: Rollback index.
+      flags: Flags value to use in the image.
       props: Properties to insert (List of strings of the form 'key:value').
       props_from_file: Properties to insert (List of strings 'key:<path>').
       kernel_cmdlines: Kernel cmdlines to insert (list of strings).
@@ -2250,14 +2256,11 @@
       h_desc.salt = salt
       h_desc.digest = digest
 
-      # Flags are only allowed on top-level vbmeta struct.
-      flags = 0
-
       # Generate the VBMeta footer.
       vbmeta_blob = self._generate_vbmeta_blob(
           algorithm_name, key_path, public_key_metadata_path, [h_desc],
-          rollback_index, flags, props, props_from_file, kernel_cmdlines,
-          setup_rootfs_from_kernel,
+          chain_partitions, rollback_index, flags, props, props_from_file,
+          kernel_cmdlines, setup_rootfs_from_kernel,
           include_descriptors_from_image, signing_helper, release_string,
           append_to_release_string)
 
@@ -2310,8 +2313,9 @@
 
   def add_hashtree_footer(self, image_filename, partition_size, partition_name,
                           generate_fec, fec_num_roots, hash_algorithm,
-                          block_size, salt, algorithm_name, key_path,
-                          public_key_metadata_path, rollback_index,
+                          block_size, salt, chain_partitions, algorithm_name,
+                          key_path,
+                          public_key_metadata_path, rollback_index, flags,
                           props, props_from_file, kernel_cmdlines,
                           setup_rootfs_from_kernel,
                           include_descriptors_from_image,
@@ -2332,10 +2336,12 @@
       hash_algorithm: Hash algorithm to use.
       block_size: Block size to use.
       salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
+      chain_partitions: List of partitions to chain.
       algorithm_name: Name of algorithm to use.
       key_path: Path to key to use or None.
       public_key_metadata_path: Path to public key metadata or None.
       rollback_index: Rollback index.
+      flags: Flags value to use in the image.
       props: Properties to insert (List of strings of the form 'key:value').
       props_from_file: Properties to insert (List of strings 'key:<path>').
       kernel_cmdlines: Kernel cmdlines to insert (list of strings).
@@ -2478,15 +2484,12 @@
         ht_desc.fec_offset = fec_offset
         ht_desc.fec_size = len(fec_data)
 
-      # Flags are only allowed on top-level vbmeta struct.
-      flags = 0
-
       # Generate the VBMeta footer and add padding as needed.
       vbmeta_offset = tree_offset + len_hashtree_and_fec
       vbmeta_blob = self._generate_vbmeta_blob(
           algorithm_name, key_path, public_key_metadata_path, [ht_desc],
-          rollback_index, flags, props, props_from_file, kernel_cmdlines,
-          setup_rootfs_from_kernel,
+          chain_partitions, rollback_index, flags, props, props_from_file,
+          kernel_cmdlines, setup_rootfs_from_kernel,
           include_descriptors_from_image, signing_helper, release_string,
           append_to_release_string)
       padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
@@ -2859,6 +2862,31 @@
                             metavar='IMAGE',
                             action='append',
                             type=argparse.FileType('rb'))
+    # These are only allowed from top-level vbmeta and boot-in-lieu-of-vbmeta.
+    sub_parser.add_argument('--chain_partition',
+                            help='Allow signed integrity-data for partition',
+                            metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
+                            action='append')
+    sub_parser.add_argument('--flags',
+                            help='VBMeta flags',
+                            type=parse_number,
+                            default=0)
+    sub_parser.add_argument('--set_hashtree_disabled_flag',
+                            help='Set the HASHTREE_DISABLED flag',
+                            action='store_true')
+
+  def _fixup_common_args(self, args):
+    """Common fixups needed by subcommands.
+
+    Arguments:
+      args: Arguments to modify.
+
+    Returns:
+      The modified arguments.
+    """
+    if args.set_hashtree_disabled_flag:
+      args.flags |= AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED
+    return args
 
   def run(self, argv):
     """Command-line processor.
@@ -2891,17 +2919,6 @@
                             type=argparse.FileType('wb'),
                             required=True)
     self._add_common_args(sub_parser)
-    sub_parser.add_argument('--chain_partition',
-                            help='Allow signed integrity-data for partition',
-                            metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
-                            action='append')
-    sub_parser.add_argument('--flags',
-                            help='VBMeta flags',
-                            type=parse_number,
-                            default=0)
-    sub_parser.add_argument('--set_hashtree_disabled_flag',
-                            help='Set the HASHTREE_DISABLED flag',
-                            action='store_true')
     sub_parser.set_defaults(func=self.make_vbmeta_image)
 
     sub_parser = subparsers.add_parser('add_hash_footer',
@@ -3104,8 +3121,7 @@
 
   def make_vbmeta_image(self, args):
     """Implements the 'make_vbmeta_image' sub-command."""
-    if args.set_hashtree_disabled_flag:
-      args.flags |= AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED
+    args = self._fixup_common_args(args)
     self.avb.make_vbmeta_image(args.output, args.chain_partition,
                                args.algorithm, args.key,
                                args.public_key_metadata, args.rollback_index,
@@ -3119,11 +3135,13 @@
 
   def add_hash_footer(self, args):
     """Implements the 'add_hash_footer' sub-command."""
+    args = self._fixup_common_args(args)
     self.avb.add_hash_footer(args.image.name, args.partition_size,
                              args.partition_name, args.hash_algorithm,
-                             args.salt, args.algorithm, args.key,
+                             args.salt, args.chain_partition, args.algorithm,
+                             args.key,
                              args.public_key_metadata, args.rollback_index,
-                             args.prop, args.prop_from_file,
+                             args.flags, args.prop, args.prop_from_file,
                              args.kernel_cmdline,
                              args.setup_rootfs_from_kernel,
                              args.include_descriptors_from_image,
@@ -3135,14 +3153,15 @@
 
   def add_hashtree_footer(self, args):
     """Implements the 'add_hashtree_footer' sub-command."""
+    args = self._fixup_common_args(args)
     self.avb.add_hashtree_footer(args.image.name if args.image else None,
                                  args.partition_size,
                                  args.partition_name,
                                  args.generate_fec, args.fec_num_roots,
                                  args.hash_algorithm, args.block_size,
-                                 args.salt, args.algorithm, args.key,
-                                 args.public_key_metadata,
-                                 args.rollback_index, args.prop,
+                                 args.salt, args.chain_partition, args.algorithm,
+                                 args.key, args.public_key_metadata,
+                                 args.rollback_index, args.flags, args.prop,
                                  args.prop_from_file,
                                  args.kernel_cmdline,
                                  args.setup_rootfs_from_kernel,
diff --git a/libavb/avb_slot_verify.c b/libavb/avb_slot_verify.c
index 3423adc..f4b2929 100644
--- a/libavb/avb_slot_verify.c
+++ b/libavb/avb_slot_verify.c
@@ -237,14 +237,20 @@
   const AvbDescriptor** descriptors = NULL;
   size_t num_descriptors;
   size_t n;
-  int is_main_vbmeta;
+  bool is_main_vbmeta;
+  bool is_vbmeta_partition;
   AvbVBMetaData* vbmeta_image_data = NULL;
 
   ret = AVB_SLOT_VERIFY_RESULT_OK;
 
   avb_assert(slot_data != NULL);
 
-  is_main_vbmeta = (avb_strcmp(partition_name, "vbmeta") == 0);
+  /* Since we allow top-level vbmeta in 'boot', use
+   * rollback_index_location to determine whether we're the main
+   * vbmeta struct.
+   */
+  is_main_vbmeta = (rollback_index_location == 0);
+  is_vbmeta_partition = (avb_strcmp(partition_name, "vbmeta") == 0);
 
   if (!avb_validate_utf8((const uint8_t*)partition_name, partition_name_len)) {
     avb_error("Partition name is not valid UTF-8.\n");
@@ -273,7 +279,7 @@
    * struct is in the beginning. Otherwise we have to locate it via a
    * footer.
    */
-  if (is_main_vbmeta) {
+  if (is_vbmeta_partition) {
     vbmeta_offset = 0;
     vbmeta_size = VBMETA_MAX_SIZE;
   } else {
@@ -332,9 +338,32 @@
     ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
     goto out;
   } else if (io_ret != AVB_IO_RESULT_OK) {
-    avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL);
-    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
-    goto out;
+    /* If we're looking for 'vbmeta' but there is no such partition,
+     * go try to get it from the boot partition instead.
+     */
+    if (is_main_vbmeta && io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION &&
+        is_vbmeta_partition) {
+      avb_debugv(full_partition_name,
+                 ": No such partition. Trying 'boot' instead.\n",
+                 NULL);
+      ret = load_and_verify_vbmeta(ops,
+                                   requested_partitions,
+                                   ab_suffix,
+                                   allow_verification_error,
+                                   0 /* toplevel_vbmeta_flags */,
+                                   0 /* rollback_index_location */,
+                                   "boot",
+                                   avb_strlen("boot"),
+                                   NULL /* expected_public_key */,
+                                   0 /* expected_public_key_length */,
+                                   slot_data,
+                                   out_algorithm_type);
+      goto out;
+    } else {
+      avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+      goto out;
+    }
   }
   avb_assert(vbmeta_num_read <= vbmeta_size);
 
@@ -561,6 +590,15 @@
           goto out;
         }
 
+        if (chain_desc.rollback_index_location == 0) {
+          avb_errorv(full_partition_name,
+                     ": Chain partition has invalid "
+                     "rollback_index_location field.\n",
+                     NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
         chain_partition_name = ((const uint8_t*)descriptors[n]) +
                                sizeof(AvbChainPartitionDescriptor);
         chain_public_key = chain_partition_name + chain_desc.partition_name_len;
@@ -707,7 +745,8 @@
  */
 static char* sub_cmdline(AvbOps* ops,
                          const char* cmdline,
-                         const char* ab_suffix) {
+                         const char* ab_suffix,
+                         bool using_boot_for_vbmeta) {
   const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"};
   const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)",
                                         "$(ANDROID_BOOT_PARTUUID)",
@@ -715,6 +754,13 @@
   char* ret = NULL;
   AvbIOResult io_ret;
 
+  /* Special-case for when the top-level vbmeta struct is in the boot
+   * partition.
+   */
+  if (using_boot_for_vbmeta) {
+    part_name_str[2] = "boot";
+  }
+
   /* Replace unique partition GUIDs */
   for (size_t n = 0; n < NUM_GUIDS; n++) {
     char part_name[PART_NAME_MAX_SIZE];
@@ -882,6 +928,7 @@
   AvbSlotVerifyData* slot_data = NULL;
   AvbAlgorithmType algorithm_type = AVB_ALGORITHM_TYPE_NONE;
   AvbIOResult io_ret;
+  bool using_boot_for_vbmeta = false;
 
   if (out_data != NULL) {
     *out_data = NULL;
@@ -909,7 +956,7 @@
                                requested_partitions,
                                ab_suffix,
                                allow_verification_error,
-                               0, /* toplevel_vbmeta_flags */
+                               0 /* toplevel_vbmeta_flags */,
                                0 /* rollback_index_location */,
                                "vbmeta",
                                avb_strlen("vbmeta"),
@@ -921,6 +968,12 @@
     goto fail;
   }
 
+  if (avb_strcmp(slot_data->vbmeta_images[0].partition_name, "vbmeta") != 0) {
+    avb_assert(avb_strcmp(slot_data->vbmeta_images[0].partition_name, "boot") ==
+               0);
+    using_boot_for_vbmeta = true;
+  }
+
   /* If things check out, mangle the kernel command-line as needed. */
   if (result_should_continue(ret)) {
     /* Fill in |ab_suffix| field. */
@@ -949,7 +1002,9 @@
 
     /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */
     if (slot_data->cmdline != NULL) {
-      char* new_cmdline = sub_cmdline(ops, slot_data->cmdline, ab_suffix);
+      char* new_cmdline;
+      new_cmdline = sub_cmdline(
+          ops, slot_data->cmdline, ab_suffix, using_boot_for_vbmeta);
       if (new_cmdline == NULL) {
         ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
         goto fail;
diff --git a/libavb/avb_slot_verify.h b/libavb/avb_slot_verify.h
index c50b847..08b11fc 100644
--- a/libavb/avb_slot_verify.h
+++ b/libavb/avb_slot_verify.h
@@ -108,8 +108,10 @@
  * The VBMeta images that were checked are available in the
  * |vbmeta_images| field. The field |num_vbmeta_images| contains the
  * number of elements in this array. The first element -
- * vbmeta_images[0] - is guaranteed to be from the "vbmeta" partition
- * in the requested slot.
+ * vbmeta_images[0] - is guaranteed to be from the partition with the
+ * top-level vbmeta struct. This is usually the "vbmeta" partition in
+ * the requested slot but if there is no "vbmeta" partition it can
+ * also be the "boot" partition.
  *
  * The partitions loaded and verified from from the slot are
  * accessible in the |loaded_partitions| array. The field
@@ -145,7 +147,8 @@
  *   androidboot.vbmeta.device: This is set to the value
  *   PARTUUID=$(ANDROID_VBMETA_PARTUUID) before substitution so it
  *   will end up pointing to the vbmeta partition for the verified
- *   slot.
+ *   slot. If there is no vbmeta partition it will point to the boot
+ *   partition of the verified slot.
  *
  *   androidboot.vbmeta.avb_version: This is set to the decimal value
  *   of AVB_VERSION_MAJOR followed by a dot followed by the decimal
diff --git a/test/avb_slot_verify_unittest.cc b/test/avb_slot_verify_unittest.cc
index c9398a2..1553173 100644
--- a/test/avb_slot_verify_unittest.cc
+++ b/test/avb_slot_verify_unittest.cc
@@ -1286,4 +1286,286 @@
   CmdlineWithHashtreeVerification(true);
 }
 
+// In the event that there's no vbmeta partition, we treat the vbmeta
+// struct from 'boot' as the top-level partition. Check that this
+// works.
+TEST_F(AvbSlotVerifyTest, NoVBMetaPartition) {
+  const size_t MiB = 1024 * 1024;
+  const size_t boot_size = 6 * MiB;
+  const size_t boot_part_size = 8 * MiB;
+  const size_t system_size = 16 * MiB;
+  const size_t system_part_size = 32 * MiB;
+  const size_t foobar_size = 8 * MiB;
+  const size_t foobar_part_size = 16 * MiB;
+  const size_t bazboo_size = 4 * MiB;
+  const size_t bazboo_part_size = 8 * MiB;
+  base::FilePath boot_path = GenerateImage("boot.img", boot_size);
+  base::FilePath system_path = GenerateImage("system.img", system_size);
+  base::FilePath foobar_path = GenerateImage("foobar.img", foobar_size);
+  base::FilePath bazboo_path = GenerateImage("bazboo.img", bazboo_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                 "--partition_size %d --partition_name system "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\"",
+                 system_path.value().c_str(),
+                 (int)system_part_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\"",
+                 foobar_path.value().c_str(),
+                 (int)foobar_part_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                 "--partition_size %d --partition_name bazboo "
+                 "--algorithm SHA512_RSA4096 "
+                 "--key test/data/testkey_rsa4096.pem "
+                 "--internal_release_string \"\"",
+                 bazboo_path.value().c_str(),
+                 (int)bazboo_part_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  // Explicitly pass "--flags 2147483648" (i.e. 1<<31) to check that
+  // boot.img is treated as top-level. Note the corresponding "Flags:"
+  // field below in the avbtool info_image output.
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name boot "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--include_descriptors_from_image %s "
+                 "--include_descriptors_from_image %s "
+                 "--setup_rootfs_from_kernel %s "
+                 "--chain_partition bazboo:1:%s "
+                 "--flags 2147483648",
+                 boot_path.value().c_str(),
+                 (int)boot_part_size,
+                 system_path.value().c_str(),
+                 foobar_path.value().c_str(),
+                 system_path.value().c_str(),
+                 pk_path.value().c_str());
+
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               8388608 bytes\n"
+      "Original image size:      6291456 bytes\n"
+      "VBMeta offset:            6291456\n"
+      "VBMeta size:              3200 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          2624 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    2147483648\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            6291456 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        boot\n"
+      "      Salt:                  d00df00d\n"
+      "      Digest:                "
+      "4c109399b20e476bab15363bff55740add83e1c1e97e0b132f5c713ddd8c7868\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:          bazboo\n"
+      "      Rollback Index Location: 1\n"
+      "      Public key (sha1):       "
+      "2597c218aae470a130f61162feaae70afd97f011\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 1\n"
+      "      Kernel Cmdline:        'dm=\"1 vroot none ro 1,0 32768 verity 1 "
+      "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) PARTUUID=$(ANDROID_SYSTEM_PARTUUID) "
+      "4096 4096 4096 4096 sha1 c9ffc3bfae5000269a55a56621547fd1fcf819df "
+      "d00df00d 2 restart_on_corruption ignore_zero_blocks\" root=0xfd00'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 2\n"
+      "      Kernel Cmdline:        "
+      "'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'\n"
+      "    Hashtree descriptor:\n"
+      "      Version of dm-verity:  1\n"
+      "      Image Size:            16777216 bytes\n"
+      "      Tree Offset:           16777216\n"
+      "      Tree Size:             135168 bytes\n"
+      "      Data Block Size:       4096 bytes\n"
+      "      Hash Block Size:       4096 bytes\n"
+      "      FEC num roots:         0\n"
+      "      FEC offset:            0\n"
+      "      FEC size:              0 bytes\n"
+      "      Hash Algorithm:        sha1\n"
+      "      Partition Name:        system\n"
+      "      Salt:                  d00df00d\n"
+      "      Root Digest:           c9ffc3bfae5000269a55a56621547fd1fcf819df\n"
+      "    Hashtree descriptor:\n"
+      "      Version of dm-verity:  1\n"
+      "      Image Size:            8388608 bytes\n"
+      "      Tree Offset:           8388608\n"
+      "      Tree Size:             69632 bytes\n"
+      "      Data Block Size:       4096 bytes\n"
+      "      Hash Block Size:       4096 bytes\n"
+      "      FEC num roots:         0\n"
+      "      FEC offset:            0\n"
+      "      FEC size:              0 bytes\n"
+      "      Hash Algorithm:        sha1\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Root Digest:           d52d93c988d336a79abe1c05240ae9a79a9b7d61\n",
+      InfoImage(boot_path));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  // Now check that libavb will fall back to reading from 'boot'
+  // instead of 'vbmeta' when encountering
+  // AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION on trying to read from
+  // 'vbmeta'.
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  // Note 'boot' in the value androidboot.vbmeta.device since we've
+  // read from 'boot' and not 'vbmeta'.
+  EXPECT_EQ(
+      "dm=\"1 vroot none ro 1,0 32768 verity 1 "
+      "PARTUUID=1234-fake-guid-for:system PARTUUID=1234-fake-guid-for:system "
+      "4096 4096 4096 4096 sha1 c9ffc3bfae5000269a55a56621547fd1fcf819df "
+      "d00df00d 2 restart_on_corruption ignore_zero_blocks\" root=0xfd00 "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:boot "
+      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=5312 "
+      "androidboot.vbmeta.digest="
+      "403b4071873ca54187ca2c33e314edf3049ccb6cfadf518d7f6a509fce3d18b3",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+// Check that non-zero flags in chained partition are caught in
+// avb_slot_verify().
+TEST_F(AvbSlotVerifyTest, ChainedPartitionEnforceFlagsZero) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", boot_image_size);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --kernel_cmdline 'cmdline2 in hash footer'"
+                 " --rollback_index 12"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef"
+                 " --flags 1"
+                 " --internal_release_string \"\"",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img",
+      "SHA256_RSA2048",
+      11,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--chain_partition boot:1:%s"
+                         " --kernel_cmdline 'cmdline2 in vbmeta'"
+                         " --internal_release_string \"\"",
+                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+}
+
+// Check that chain descriptors in chained partitions are caught in
+// avb_slot_verify().
+TEST_F(AvbSlotVerifyTest, ChainedPartitionEnforceNoChainPartitions) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", boot_image_size);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --kernel_cmdline 'cmdline2 in hash footer'"
+                 " --rollback_index 12"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef"
+                 " --chain_partition other:2:%s"
+                 " --internal_release_string \"\"",
+                 boot_path.value().c_str(),
+                 boot_partition_size,
+                 pk_path.value().c_str());
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img",
+      "SHA256_RSA2048",
+      11,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--chain_partition boot:1:%s"
+                         " --kernel_cmdline 'cmdline2 in vbmeta'"
+                         " --internal_release_string \"\"",
+                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+}
+
 }  // namespace avb
diff --git a/test/fake_avb_ops.cc b/test/fake_avb_ops.cc
index cdd0850..6e346e1 100644
--- a/test/fake_avb_ops.cc
+++ b/test/fake_avb_ops.cc
@@ -67,7 +67,11 @@
             "Error opening file '%s': %s\n",
             path.value().c_str(),
             strerror(errno));
-    return AVB_IO_RESULT_ERROR_IO;
+    if (errno == ENOENT) {
+      return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+    } else {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
   }
   if (lseek(fd, offset, SEEK_SET) != offset) {
     fprintf(stderr,
@@ -121,7 +125,11 @@
             "Error opening file '%s': %s\n",
             path.value().c_str(),
             strerror(errno));
-    return AVB_IO_RESULT_ERROR_IO;
+    if (errno == ENOENT) {
+      return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+    } else {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
   }
   if (lseek(fd, offset, SEEK_SET) != offset) {
     fprintf(stderr,