Implement support for on-device persistent digests. am: fd0ba0d491 am: 37c19a98c7
am: aa350af3d0

Change-Id: I54494537b88dff7550bf22733772a0f45edcd8da
diff --git a/README.md b/README.md
index 0ad0a0b..bbcf96c 100644
--- a/README.md
+++ b/README.md
@@ -22,11 +22,14 @@
     + [System Dependencies](#System-Dependencies)
     + [Locked and Unlocked mode](#Locked-and-Unlocked-mode)
     + [Tamper-evident Storage](#Tamper_evident-Storage)
+    + [Named Persistent Values](#Named-Persistent-Values)
+    + [Persistent Digests](#Persistent-Digests)
     + [Updating Stored Rollback Indexes](#Updating-Stored-Rollback-Indexes)
     + [Recommended Bootflow](#Recommended-Bootflow)
     + [Handling dm-verity Errors](#Handling-dm_verity-Errors)
     + [Android Specific Integration](#Android-Specific-Integration)
-    + [Device Specific Notes](Device-Specific-Notes)
+    + [Device Specific Notes](#Device-Specific-Notes)
+* [Version History](#Version-History)
 
 # What is it?
 
@@ -121,6 +124,12 @@
 Note how the rollback indexes differ between slots - for slot A the
 rollback indexes are `[42, 101]` and for slot B they are `[43, 103]`.
 
+In version 1.1 or later, avbtool supports `--do_not_use_ab` for
+`add_hash_footer` and `add_hashtree_footer` operations. This makes it
+possible to work with a partition that does not use A/B and should
+never have the prefix. This corresponds to the
+`AVB_HASH[TREE]_DESCRIPTOR_FLAGS_DO_NOT_USE_AB` flags.
+
 # Tools and Libraries
 
 This section contains information about the tools and libraries
@@ -335,7 +344,9 @@
         [--signing_helper_with_files /path/to/external/signer_with_files]          \
         [--print_required_libavb_version]                                          \
         [--append_to_release_string STR]                                           \
-        [--calc_max_image_size]
+        [--calc_max_image_size]                                                    \
+        [--do_not_use_ab]                                                          \
+        [--use_persistent_digest]
 
 An integrity footer containing the root digest and salt for a hashtree
 for a partition can be added to an existing image as follows. The
@@ -356,7 +367,9 @@
         [--signing_helper_with_files /path/to/external/signer_with_files]          \
         [--print_required_libavb_version]                                          \
         [--append_to_release_string STR]                                           \
-        [--calc_max_image_size]
+        [--calc_max_image_size]                                                    \
+        [--do_not_use_ab]                                                          \
+        [--use_persistent_digest]
 
 The size of an image with integrity footers can be changed using the
 `resize_image` command:
@@ -522,7 +535,7 @@
 `validate_vbmeta_public_key()` operation when verifying a slot.
 
 Some devices may support the end-user configuring the root of trust to use, see
-the [Device Specific Notes](Device-Specific-Notes) section for details.
+the [Device Specific Notes](#Device-Specific-Notes) section for details.
 
 To prevent rollback attacks, the rollback index should be increased on
 a regular basis. The rollback index can be set with the
@@ -639,9 +652,9 @@
 overwritten.
 
 Tamper-evident storage must be used for stored rollback indexes, keys
-used for verification, and device state (whether the device is LOCKED
-or UNLOCKED). If tampering has been detected the corresponding
-`AvbOps` operation should fail by e.g. returning
+used for verification, device state (whether the device is LOCKED or
+UNLOCKED), and named persistent values. If tampering has been detected
+the corresponding `AvbOps` operation should fail by e.g. returning
 `AVB_IO_RESULT_ERROR_IO`. It is especially important that verification
 keys cannot be tampered with since they represent the root-of-trust.
 
@@ -651,6 +664,36 @@
 possible to set or clear a key while the device is in the UNLOCKED
 state.
 
+## Named Persistent Values
+
+AVB 1.1 introduces support for named persistent values which must be
+tamper evident and allows AVB to store arbitrary key-value pairs.
+Integrators may limit support for these values to a set of fixed
+well-known names, a maximum value size, and / or a maximum number of
+values.
+
+## Persistent Digests
+
+Using a persistent digest for a partition means the digest (or root
+digest in the case of a hashtree) is not stored in the descriptor but
+is stored in a named persistent value. This allows configuration data
+which may differ from device to device to be verified by AVB. It must
+not be possible to modify the persistent digest when the device is in
+the LOCKED state.
+
+To specify that a descriptor should use a persistent digest, use the
+`--use_persistent_digest` option for the `add_hash_footer` or
+`add_hashtree_footer` avbtool operations. Then, during verification of
+the descriptor, AVB will look for the digest in the named persistent
+value `avb.persistent_digest.$(partition_name)` instead of in the
+descriptor itself.
+
+For hashtree descriptors using a persistent digest, the digest value
+will be available for substitution into kernel command line descriptors
+using a token of the form `$(AVB_FOO_ROOT_DIGEST)` where 'FOO' is the
+uppercase partition name, in this case for the partition named 'foo'.
+The token will be replaced by the digest in hexadecimal form.
+
 ## Updating Stored Rollback Indexes
 
 In order for Rollback Protection to work the bootloader will need to
@@ -860,3 +903,18 @@
 
 When booting an image signed with a custom key, a yellow screen will be shown as
 part of the boot process to remind the user that the custom key is in use.
+
+# Version History
+
+### Version 1.1
+
+Version 1.1 adds support for the following:
+
+* A 32-bit `flags` element is added to hash and hashtree descriptors.
+* Support for partitions which don't use [A/B](#A_B-Support).
+* Tamper-evident [named persistent values](#Named-Persistent-Values).
+* [Persistent digests](#Persistent-Digests) for hash or hashtree descriptors.
+
+### Version 1.0
+
+All features not explicitly listed under a later version are supported by 1.0.
diff --git a/avbtool b/avbtool
index b742466..14914ce 100755
--- a/avbtool
+++ b/avbtool
@@ -38,9 +38,13 @@
 
 # Keep in sync with libavb/avb_version.h.
 AVB_VERSION_MAJOR = 1
-AVB_VERSION_MINOR = 0
+AVB_VERSION_MINOR = 1
 AVB_VERSION_SUB = 0
 
+# Keep in sync with libavb/avb_footer.h.
+AVB_FOOTER_VERSION_MAJOR = 1
+AVB_FOOTER_VERSION_MINOR = 0
+
 AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1
 
 
@@ -1195,11 +1199,12 @@
     partition_name: Partition name.
     salt: Salt used.
     root_digest: Root digest.
+    flags: Descriptor flags (see avb_hashtree_descriptor.h).
   """
 
   TAG = 1
-  RESERVED = 64
-  SIZE = 116 + RESERVED
+  RESERVED = 60
+  SIZE = 120 + RESERVED
   FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
                    'L'  # dm-verity version used
                    'Q'  # image size (bytes)
@@ -1213,7 +1218,8 @@
                    '32s'  # hash algorithm used
                    'L'  # partition name (bytes)
                    'L'  # salt length (bytes)
-                   'L' +  # root digest length (bytes)
+                   'L'  # root digest length (bytes)
+                   'L' +  # flags
                    str(RESERVED) + 's')  # reserved
 
   def __init__(self, data=None):
@@ -1233,8 +1239,8 @@
        self.tree_offset, self.tree_size, self.data_block_size,
        self.hash_block_size, self.fec_num_roots, self.fec_offset, self.fec_size,
        self.hash_algorithm, partition_name_len, salt_len,
-       root_digest_len, _) = struct.unpack(self.FORMAT_STRING,
-                                           data[0:self.SIZE])
+       root_digest_len, self.flags, _) = struct.unpack(self.FORMAT_STRING,
+                                                       data[0:self.SIZE])
       expected_size = round_to_multiple(
           self.SIZE - 16 + partition_name_len + salt_len + root_digest_len, 8)
       if tag != self.TAG or num_bytes_following != expected_size:
@@ -1252,7 +1258,8 @@
       o += salt_len
       self.root_digest = data[(self.SIZE + o):(self.SIZE + o + root_digest_len)]
       if root_digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
-        raise LookupError('root_digest_len doesn\'t match hash algorithm')
+        if root_digest_len != 0:
+          raise LookupError('root_digest_len doesn\'t match hash algorithm')
 
     else:
       self.dm_verity_version = 0
@@ -1268,6 +1275,7 @@
       self.partition_name = ''
       self.salt = bytearray()
       self.root_digest = bytearray()
+      self.flags = 0
 
   def print_desc(self, o):
     """Print the descriptor.
@@ -1293,6 +1301,7 @@
         'hex')))
     o.write('      Root Digest:           {}\n'.format(str(
         self.root_digest).encode('hex')))
+    o.write('      Flags:                 {}\n'.format(self.flags))
 
   def encode(self):
     """Serializes the descriptor.
@@ -1311,7 +1320,7 @@
                        self.hash_block_size, self.fec_num_roots,
                        self.fec_offset, self.fec_size, self.hash_algorithm,
                        len(encoded_name), len(self.salt), len(self.root_digest),
-                       self.RESERVED*'\0')
+                       self.flags, self.RESERVED*'\0')
     padding = struct.pack(str(padding_size) + 'x')
     ret = desc + encoded_name + self.salt + self.root_digest + padding
     return bytearray(ret)
@@ -1341,8 +1350,8 @@
                                                 digest_padding,
                                                 hash_level_offsets,
                                                 tree_size)
-    # The root digest must match...
-    if root_digest != self.root_digest:
+    # The root digest must match unless it is not embedded in the descriptor.
+    if len(self.root_digest) != 0 and root_digest != self.root_digest:
       sys.stderr.write('hashtree of {} does not match descriptor\n'.
                        format(image_filename))
       return False
@@ -1374,17 +1383,19 @@
     partition_name: Partition name.
     salt: Salt used.
     digest: The hash value of salt and data combined.
+    flags: The descriptor flags (see avb_hash_descriptor.h).
   """
 
   TAG = 2
-  RESERVED = 64
-  SIZE = 68 + RESERVED
+  RESERVED = 60
+  SIZE = 72 + RESERVED
   FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
                    'Q'  # image size (bytes)
                    '32s'  # hash algorithm used
                    'L'  # partition name (bytes)
                    'L'  # salt length (bytes)
-                   'L' +  # digest length (bytes)
+                   'L'  # digest length (bytes)
+                   'L' +  # flags
                    str(RESERVED) + 's')  # reserved
 
   def __init__(self, data=None):
@@ -1402,7 +1413,8 @@
     if data:
       (tag, num_bytes_following, self.image_size, self.hash_algorithm,
        partition_name_len, salt_len,
-       digest_len, _) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+       digest_len, self.flags, _) = struct.unpack(self.FORMAT_STRING,
+                                                  data[0:self.SIZE])
       expected_size = round_to_multiple(
           self.SIZE - 16 + partition_name_len + salt_len + digest_len, 8)
       if tag != self.TAG or num_bytes_following != expected_size:
@@ -1419,7 +1431,8 @@
       o += salt_len
       self.digest = data[(self.SIZE + o):(self.SIZE + o + digest_len)]
       if digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
-        raise LookupError('digest_len doesn\'t match hash algorithm')
+        if digest_len != 0:
+          raise LookupError('digest_len doesn\'t match hash algorithm')
 
     else:
       self.image_size = 0
@@ -1427,6 +1440,7 @@
       self.partition_name = ''
       self.salt = bytearray()
       self.digest = bytearray()
+      self.flags = 0
 
   def print_desc(self, o):
     """Print the descriptor.
@@ -1442,6 +1456,7 @@
         'hex')))
     o.write('      Digest:                {}\n'.format(str(self.digest).encode(
         'hex')))
+    o.write('      Flags:                 {}\n'.format(self.flags))
 
   def encode(self):
     """Serializes the descriptor.
@@ -1456,7 +1471,8 @@
     padding_size = nbf_with_padding - num_bytes_following
     desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
                        self.image_size, self.hash_algorithm, len(encoded_name),
-                       len(self.salt), len(self.digest), self.RESERVED*'\0')
+                       len(self.salt), len(self.digest), self.flags,
+                       self.RESERVED*'\0')
     padding = struct.pack(str(padding_size) + 'x')
     ret = desc + encoded_name + self.salt + self.digest + padding
     return bytearray(ret)
@@ -1480,7 +1496,8 @@
     ha.update(self.salt)
     ha.update(data)
     digest = ha.digest()
-    if digest != self.digest:
+    # The digest must match unless there is no digest in the descriptor.
+    if len(self.digest) != 0 and digest != self.digest:
       sys.stderr.write('{} digest of {} does not match digest in descriptor\n'.
                        format(self.hash_algorithm, image_filename))
       return False
@@ -1754,8 +1771,8 @@
   MAGIC = 'AVBf'
   SIZE = 64
   RESERVED = 28
-  FOOTER_VERSION_MAJOR = 1
-  FOOTER_VERSION_MINOR = 0
+  FOOTER_VERSION_MAJOR = AVB_FOOTER_VERSION_MAJOR
+  FOOTER_VERSION_MINOR = AVB_FOOTER_VERSION_MINOR
   FORMAT_STRING = ('!4s2L'  # magic, 2 x version.
                    'Q'  # Original image size.
                    'Q'  # Offset of VBMeta blob.
@@ -1889,7 +1906,7 @@
       minor: The minor version of libavb that has support for the feature.
     """
     self.required_libavb_version_minor = (
-        min(self.required_libavb_version_minor, minor))
+        max(self.required_libavb_version_minor, minor))
 
   def save(self, output):
     """Serializes the header (256 bytes) to disk.
@@ -2384,10 +2401,19 @@
     """
 
     # If we're asked to calculate minimum required libavb version, we're done.
-    #
-    # NOTE: When we get to 1.1 and later this will get more complicated.
     if print_required_libavb_version:
-      print '1.0'
+      if include_descriptors_from_image:
+        # Use the bump logic in AvbVBMetaHeader to calculate the max required
+        # version of all included descriptors.
+        tmp_header = AvbVBMetaHeader()
+        for image in include_descriptors_from_image:
+          (_, image_header, _, _) = self._parse_image(ImageHandler(image.name))
+          tmp_header.bump_required_libavb_version_minor(
+              image_header.required_libavb_version_minor)
+        print '1.{}'.format(tmp_header.required_libavb_version_minor)
+      else:
+        # Descriptors aside, all vbmeta features are supported in 1.0.
+        print '1.0'
       return
 
     if not output:
@@ -2401,7 +2427,7 @@
         kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup,
         include_descriptors_from_image, signing_helper,
         signing_helper_with_files, release_string,
-        append_to_release_string)
+        append_to_release_string, 0)
 
     # Write entire vbmeta blob (header, authentication, auxiliary).
     output.seek(0)
@@ -2421,7 +2447,8 @@
                             ht_desc_to_setup,
                             include_descriptors_from_image, signing_helper,
                             signing_helper_with_files,
-                            release_string, append_to_release_string):
+                            release_string, append_to_release_string,
+                            required_libavb_version_minor):
     """Generates a VBMeta blob.
 
     This blob contains the header (struct AvbVBMetaHeader), the
@@ -2453,6 +2480,7 @@
       signing_helper_with_files: Same as signing_helper but uses files instead.
       release_string: None or avbtool release string.
       append_to_release_string: None or string to append.
+      required_libavb_version_minor: Use at least this required minor version.
 
     Returns:
       A bytearray() with the VBMeta blob.
@@ -2471,6 +2499,9 @@
     if not descriptors:
       descriptors = []
 
+    h = AvbVBMetaHeader()
+    h.bump_required_libavb_version_minor(required_libavb_version_minor)
+
     # Insert chained partition descriptors, if any
     if chain_partitions:
       used_locations = {}
@@ -2548,7 +2579,11 @@
     if include_descriptors_from_image:
       for image in include_descriptors_from_image:
         image_handler = ImageHandler(image.name)
-        (_, _, image_descriptors, _) = self._parse_image(image_handler)
+        (_, image_vbmeta_header, image_descriptors, _) = self._parse_image(
+            image_handler)
+        # Bump the required libavb version to support all included descriptors.
+        h.bump_required_libavb_version_minor(
+            image_vbmeta_header.required_libavb_version_minor)
         for desc in image_descriptors:
           encoded_descriptors.extend(desc.encode())
 
@@ -2569,8 +2604,6 @@
         raise AvbError('Key is wrong size for algorithm {}'.format(
             algorithm_name))
 
-    h = AvbVBMetaHeader()
-
     # Override release string, if requested.
     if isinstance(release_string, (str, unicode)):
       h.release_string = release_string
@@ -2743,7 +2776,8 @@
                       signing_helper, signing_helper_with_files,
                       release_string, append_to_release_string,
                       output_vbmeta_image, do_not_append_vbmeta_image,
-                      print_required_libavb_version):
+                      print_required_libavb_version, use_persistent_digest,
+                      do_not_use_ab):
     """Implementation of the add_hash_footer on unsparse images.
 
     Arguments:
@@ -2775,16 +2809,20 @@
       output_vbmeta_image: If not None, also write vbmeta struct to this file.
       do_not_append_vbmeta_image: If True, don't append vbmeta struct.
       print_required_libavb_version: True to only print required libavb version.
+      use_persistent_digest: Use a persistent digest on device.
+      do_not_use_ab: This partition does not use A/B.
 
     Raises:
       AvbError: If an argument is incorrect.
     """
 
+    required_libavb_version_minor = 0
+    if use_persistent_digest or do_not_use_ab:
+      required_libavb_version_minor = 1
+
     # If we're asked to calculate minimum required libavb version, we're done.
-    #
-    # NOTE: When we get to 1.1 and later this will get more complicated.
     if print_required_libavb_version:
-      print '1.0'
+      print '1.{}'.format(required_libavb_version_minor)
       return
 
     # First, calculate the maximum image size such that an image
@@ -2860,7 +2898,11 @@
       h_desc.hash_algorithm = hash_algorithm
       h_desc.partition_name = partition_name
       h_desc.salt = salt
-      h_desc.digest = digest
+      h_desc.flags = 0
+      if do_not_use_ab:
+        h_desc.flags |= 1  # AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB
+      if not use_persistent_digest:
+        h_desc.digest = digest
 
       # Generate the VBMeta footer.
       ht_desc_to_setup = None
@@ -2870,7 +2912,7 @@
           kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup,
           include_descriptors_from_image, signing_helper,
           signing_helper_with_files, release_string,
-          append_to_release_string)
+          append_to_release_string, required_libavb_version_minor)
 
       # Write vbmeta blob, if requested.
       if output_vbmeta_image:
@@ -2934,7 +2976,8 @@
                           signing_helper_with_files,
                           release_string, append_to_release_string,
                           output_vbmeta_image, do_not_append_vbmeta_image,
-                          print_required_libavb_version):
+                          print_required_libavb_version,
+                          use_persistent_root_digest, do_not_use_ab):
     """Implements the 'add_hashtree_footer' command.
 
     See https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for
@@ -2974,16 +3017,20 @@
       output_vbmeta_image: If not None, also write vbmeta struct to this file.
       do_not_append_vbmeta_image: If True, don't append vbmeta struct.
       print_required_libavb_version: True to only print required libavb version.
+      use_persistent_root_digest: Use a persistent root digest on device.
+      do_not_use_ab: The partition does not use A/B.
 
     Raises:
       AvbError: If an argument is incorrect.
     """
 
+    required_libavb_version_minor = 0
+    if use_persistent_root_digest or do_not_use_ab:
+      required_libavb_version_minor = 1
+
     # If we're asked to calculate minimum required libavb version, we're done.
-    #
-    # NOTE: When we get to 1.1 and later this will get more complicated.
     if print_required_libavb_version:
-      print '1.0'
+      print '1.{}'.format(required_libavb_version_minor)
       return
 
     digest_size = len(hashlib.new(name=hash_algorithm).digest())
@@ -3090,7 +3137,10 @@
       ht_desc.hash_algorithm = hash_algorithm
       ht_desc.partition_name = partition_name
       ht_desc.salt = salt
-      ht_desc.root_digest = root_digest
+      if do_not_use_ab:
+        ht_desc.flags |= 1  # AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB
+      if not use_persistent_root_digest:
+        ht_desc.root_digest = root_digest
 
       # Write the hash tree
       padding_needed = (round_to_multiple(len(hash_tree), image.block_size) -
@@ -3125,7 +3175,7 @@
           kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup,
           include_descriptors_from_image, signing_helper,
           signing_helper_with_files, release_string,
-          append_to_release_string)
+          append_to_release_string, required_libavb_version_minor)
       padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
                         len(vbmeta_blob))
       vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
@@ -3517,6 +3567,25 @@
                             help='Set the HASHTREE_DISABLED flag',
                             action='store_true')
 
+  def _add_common_footer_args(self, sub_parser):
+    """Adds arguments used by add_*_footer sub-commands.
+
+    Arguments:
+      sub_parser: The parser to add arguments to.
+    """
+    sub_parser.add_argument('--use_persistent_digest',
+                            help='Use a persistent digest on device instead of '
+                                 'storing the digest in the descriptor. This '
+                                 'cannot be used with A/B so must be combined '
+                                 'with --do_not_use_ab when an A/B suffix is '
+                                 'expected at runtime.',
+                            action='store_true')
+    sub_parser.add_argument('--do_not_use_ab',
+                            help='The partition does not use A/B even when an '
+                                 'A/B suffix is present. This must not be used '
+                                 'for vbmeta or chained partitions.',
+                            action='store_true')
+
   def _fixup_common_args(self, args):
     """Common fixups needed by subcommands.
 
@@ -3598,6 +3667,7 @@
                                   'to the image'),
                             action='store_true')
     self._add_common_args(sub_parser)
+    self._add_common_footer_args(sub_parser)
     sub_parser.set_defaults(func=self.add_hash_footer)
 
     sub_parser = subparsers.add_parser('append_vbmeta_image',
@@ -3670,6 +3740,7 @@
                             action='store_true',
                             help='Adds kernel cmdline for setting up rootfs')
     self._add_common_args(sub_parser)
+    self._add_common_footer_args(sub_parser)
     sub_parser.set_defaults(func=self.add_hashtree_footer)
 
     sub_parser = subparsers.add_parser('erase_footer',
@@ -3869,7 +3940,9 @@
                              args.append_to_release_string,
                              args.output_vbmeta_image,
                              args.do_not_append_vbmeta_image,
-                             args.print_required_libavb_version)
+                             args.print_required_libavb_version,
+                             args.use_persistent_digest,
+                             args.do_not_use_ab)
 
   def add_hashtree_footer(self, args):
     """Implements the 'add_hashtree_footer' sub-command."""
@@ -3900,7 +3973,9 @@
                                  args.append_to_release_string,
                                  args.output_vbmeta_image,
                                  args.do_not_append_vbmeta_image,
-                                 args.print_required_libavb_version)
+                                 args.print_required_libavb_version,
+                                 args.use_persistent_digest,
+                                 args.do_not_use_ab)
 
   def erase_footer(self, args):
     """Implements the 'erase_footer' sub-command."""
diff --git a/libavb/avb_cmdline.c b/libavb/avb_cmdline.c
index 3df7a30..426f909 100644
--- a/libavb/avb_cmdline.c
+++ b/libavb/avb_cmdline.c
@@ -33,14 +33,18 @@
  * values. Returns NULL on OOM, otherwise the cmdline with values
  * replaced.
  */
-char* avb_sub_cmdline(AvbOps* ops, const char* cmdline, const char* ab_suffix,
-                      bool using_boot_for_vbmeta) {
+char* avb_sub_cmdline(AvbOps* ops,
+                      const char* cmdline,
+                      const char* ab_suffix,
+                      bool using_boot_for_vbmeta,
+                      const AvbCmdlineSubstList* additional_substitutions) {
   const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"};
   const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)",
                                         "$(ANDROID_BOOT_PARTUUID)",
                                         "$(ANDROID_VBMETA_PARTUUID)"};
   char* ret = NULL;
   AvbIOResult io_ret;
+  size_t n;
 
   /* Special-case for when the top-level vbmeta struct is in the boot
    * partition.
@@ -50,7 +54,7 @@
   }
 
   /* Replace unique partition GUIDs */
-  for (size_t n = 0; n < NUM_GUIDS; n++) {
+  for (n = 0; n < NUM_GUIDS; n++) {
     char part_name[AVB_PART_NAME_MAX_SIZE];
     char guid_buf[37];
 
@@ -67,7 +71,7 @@
     io_ret = ops->get_unique_guid_for_partition(
         ops, part_name, guid_buf, sizeof guid_buf);
     if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
-      return NULL;
+      goto fail;
     } else if (io_ret != AVB_IO_RESULT_OK) {
       avb_error("Error getting unique GUID for partition.\n");
       goto fail;
@@ -85,6 +89,22 @@
     }
   }
 
+  avb_assert(ret != NULL);
+
+  /* Replace any additional substitutions. */
+  if (additional_substitutions != NULL) {
+    for (n = 0; n < additional_substitutions->size; ++n) {
+      char* new_ret = avb_replace(ret,
+                                  additional_substitutions->tokens[n],
+                                  additional_substitutions->values[n]);
+      avb_free(ret);
+      ret = new_ret;
+      if (ret == NULL) {
+        goto fail;
+      }
+    }
+  }
+
   return ret;
 
 fail:
@@ -185,22 +205,11 @@
                               const char* key,
                               const uint8_t* data,
                               size_t data_len) {
-  char hex_digits[17] = "0123456789abcdef";
-  char* hex_data;
   int ret;
-  size_t n;
-
-  hex_data = avb_malloc(data_len * 2 + 1);
+  char* hex_data = avb_bin2hex(data, data_len);
   if (hex_data == NULL) {
     return 0;
   }
-
-  for (n = 0; n < data_len; n++) {
-    hex_data[n * 2] = hex_digits[data[n] >> 4];
-    hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f];
-  }
-  hex_data[n * 2] = '\0';
-
   ret = cmdline_append_option(slot_data, key, hex_data);
   avb_free(hex_data);
   return ret;
@@ -368,3 +377,68 @@
   return ret;
 }
 
+AvbCmdlineSubstList* avb_new_cmdline_subst_list() {
+  return (AvbCmdlineSubstList*)avb_calloc(sizeof(AvbCmdlineSubstList));
+}
+
+void avb_free_cmdline_subst_list(AvbCmdlineSubstList* cmdline_subst) {
+  size_t i;
+  for (i = 0; i < cmdline_subst->size; ++i) {
+    avb_free(cmdline_subst->tokens[i]);
+    avb_free(cmdline_subst->values[i]);
+  }
+  cmdline_subst->size = 0;
+  avb_free(cmdline_subst);
+}
+
+AvbSlotVerifyResult avb_add_root_digest_substitution(
+    const char* part_name,
+    const uint8_t* digest,
+    size_t digest_size,
+    AvbCmdlineSubstList* out_cmdline_subst) {
+  const char* kDigestSubPrefix = "$(AVB_";
+  const char* kDigestSubSuffix = "_ROOT_DIGEST)";
+  size_t part_name_len = avb_strlen(part_name);
+  size_t list_index = out_cmdline_subst->size;
+
+  avb_assert(part_name_len < AVB_PART_NAME_MAX_SIZE);
+  avb_assert(digest_size <= AVB_SHA512_DIGEST_SIZE);
+  if (part_name_len >= AVB_PART_NAME_MAX_SIZE ||
+      digest_size > AVB_SHA512_DIGEST_SIZE) {
+    return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+  }
+
+  if (out_cmdline_subst->size >= AVB_MAX_NUM_CMDLINE_SUBST) {
+    /* The list is full. Currently dynamic growth of this list is not supported.
+     */
+    return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+  }
+
+  /* Construct the token to replace in the command line based on the partition
+   * name. For partition 'foo', this will be '$(AVB_FOO_ROOT_DIGEST)'.
+   */
+  out_cmdline_subst->tokens[list_index] =
+      avb_strdupv(kDigestSubPrefix, part_name, kDigestSubSuffix, NULL);
+  if (out_cmdline_subst->tokens[list_index] == NULL) {
+    goto fail;
+  }
+  avb_uppercase(out_cmdline_subst->tokens[list_index]);
+
+  /* The digest value is hex encoded when inserted in the command line. */
+  out_cmdline_subst->values[list_index] = avb_bin2hex(digest, digest_size);
+  if (out_cmdline_subst->values[list_index] == NULL) {
+    goto fail;
+  }
+
+  out_cmdline_subst->size++;
+  return AVB_SLOT_VERIFY_RESULT_OK;
+
+fail:
+  if (out_cmdline_subst->tokens[list_index]) {
+    avb_free(out_cmdline_subst->tokens[list_index]);
+  }
+  if (out_cmdline_subst->values[list_index]) {
+    avb_free(out_cmdline_subst->values[list_index]);
+  }
+  return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+}
diff --git a/libavb/avb_cmdline.h b/libavb/avb_cmdline.h
index 648e202..996535d 100644
--- a/libavb/avb_cmdline.h
+++ b/libavb/avb_cmdline.h
@@ -41,12 +41,24 @@
  */
 #define AVB_PART_NAME_MAX_SIZE 32
 
+#define AVB_MAX_NUM_CMDLINE_SUBST 10
+
+/* Holds information about command-line substitutions. */
+typedef struct AvbCmdlineSubstList {
+  size_t size;
+  char* tokens[AVB_MAX_NUM_CMDLINE_SUBST];
+  char* values[AVB_MAX_NUM_CMDLINE_SUBST];
+} AvbCmdlineSubstList;
+
 /* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with
  * values. Returns NULL on OOM, otherwise the cmdline with values
  * replaced.
  */
-char* avb_sub_cmdline(AvbOps* ops, const char* cmdline, const char* ab_suffix,
-                      bool using_boot_for_vbmeta);
+char* avb_sub_cmdline(AvbOps* ops,
+                      const char* cmdline,
+                      const char* ab_suffix,
+                      bool using_boot_for_vbmeta,
+                      const AvbCmdlineSubstList* additional_substitutions);
 
 AvbSlotVerifyResult avb_append_options(
     AvbOps* ops,
@@ -55,4 +67,24 @@
     AvbAlgorithmType algorithm_type,
     AvbHashtreeErrorMode hashtree_error_mode);
 
+/* Allocates and initializes a new command line substitution list. Free with
+ * |avb_free_cmdline_subst_list|.
+ */
+AvbCmdlineSubstList* avb_new_cmdline_subst_list(void);
+
+/* Use this instead of |avb_free| to deallocate a AvbCmdlineSubstList. */
+void avb_free_cmdline_subst_list(AvbCmdlineSubstList* cmdline_subst);
+
+/* Adds a hashtree root digest to be substituted in $(AVB_*_ROOT_DIGEST)
+ * variables. The partition name differentiates the variable. For example, if
+ * |part_name| is "foo" then $(AVB_FOO_ROOT_DIGEST) will be substituted with the
+ * hex encoding of the digest. The substitution will be added to
+ * |out_cmdline_subst|. Returns AVB_SLOT_VERIFY_RESULT_OK on success.
+ */
+AvbSlotVerifyResult avb_add_root_digest_substitution(
+    const char* part_name,
+    const uint8_t* digest,
+    size_t digest_size,
+    AvbCmdlineSubstList* out_cmdline_subst);
+
 #endif
diff --git a/libavb/avb_crypto.h b/libavb/avb_crypto.h
index 7e8d7e2..0903baa 100644
--- a/libavb/avb_crypto.h
+++ b/libavb/avb_crypto.h
@@ -44,6 +44,9 @@
 /* Size of a RSA-8192 signature. */
 #define AVB_RSA8192_NUM_BYTES 1024
 
+/* Size in bytes of a SHA-1 digest. */
+#define AVB_SHA1_DIGEST_SIZE 20
+
 /* Size in bytes of a SHA-256 digest. */
 #define AVB_SHA256_DIGEST_SIZE 32
 
diff --git a/libavb/avb_hash_descriptor.c b/libavb/avb_hash_descriptor.c
index 2e427de..3a6b8c8 100644
--- a/libavb/avb_hash_descriptor.c
+++ b/libavb/avb_hash_descriptor.c
@@ -44,6 +44,7 @@
   dest->partition_name_len = avb_be32toh(dest->partition_name_len);
   dest->salt_len = avb_be32toh(dest->salt_len);
   dest->digest_len = avb_be32toh(dest->digest_len);
+  dest->flags = avb_be32toh(dest->flags);
 
   /* Check that partition_name, salt, and digest are fully contained. */
   expected_size = sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor);
diff --git a/libavb/avb_hash_descriptor.h b/libavb/avb_hash_descriptor.h
index 2668118..9ee8997 100644
--- a/libavb/avb_hash_descriptor.h
+++ b/libavb/avb_hash_descriptor.h
@@ -35,6 +35,16 @@
 extern "C" {
 #endif
 
+/* Flags for hash descriptors.
+ *
+ * AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB: Do not apply the default A/B
+ *   partition logic to this partition. This is intentionally a negative boolean
+ *   because A/B should be both the default and most used in practice.
+ */
+typedef enum {
+  AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = (1 << 0),
+} AvbHashDescriptorFlags;
+
 /* A descriptor containing information about hash for an image.
  *
  * This descriptor is typically used for boot partitions to verify the
@@ -46,6 +56,10 @@
  *
  * The |reserved| field is for future expansion and must be set to NUL
  * bytes.
+ *
+ * Changes in v1.1:
+ *   - flags field is added which supports AVB_HASH_DESCRIPTOR_FLAGS_USE_AB
+ *   - digest_len may be zero, which indicates the use of a persistent digest
  */
 typedef struct AvbHashDescriptor {
   AvbDescriptor parent_descriptor;
@@ -54,7 +68,8 @@
   uint32_t partition_name_len;
   uint32_t salt_len;
   uint32_t digest_len;
-  uint8_t reserved[64];
+  uint32_t flags;
+  uint8_t reserved[60];
 } AVB_ATTR_PACKED AvbHashDescriptor;
 
 /* Copies |src| to |dest| and validates, byte-swapping fields in the
diff --git a/libavb/avb_hashtree_descriptor.c b/libavb/avb_hashtree_descriptor.c
index b961e47..0822458 100644
--- a/libavb/avb_hashtree_descriptor.c
+++ b/libavb/avb_hashtree_descriptor.c
@@ -52,6 +52,7 @@
   dest->partition_name_len = avb_be32toh(dest->partition_name_len);
   dest->salt_len = avb_be32toh(dest->salt_len);
   dest->root_digest_len = avb_be32toh(dest->root_digest_len);
+  dest->flags = avb_be32toh(dest->flags);
 
   /* Check that partition_name, salt, and root_digest are fully contained. */
   expected_size = sizeof(AvbHashtreeDescriptor) - sizeof(AvbDescriptor);
diff --git a/libavb/avb_hashtree_descriptor.h b/libavb/avb_hashtree_descriptor.h
index a5aafbf..d0f7e2c 100644
--- a/libavb/avb_hashtree_descriptor.h
+++ b/libavb/avb_hashtree_descriptor.h
@@ -35,6 +35,16 @@
 extern "C" {
 #endif
 
+/* Flags for hashtree descriptors.
+ *
+ * AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB: Do not apply the default A/B
+ *   partition logic to this partition. This is intentionally a negative boolean
+ *   because A/B should be both the default and most used in practice.
+ */
+typedef enum {
+  AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = (1 << 0),
+} AvbHashtreeDescriptorFlags;
+
 /* A descriptor containing information about a dm-verity hashtree.
  *
  * Hash-trees are used to verify large partitions typically containing
@@ -48,6 +58,10 @@
  *
  * The |reserved| field is for future expansion and must be set to NUL
  * bytes.
+ *
+ * Changes in v1.1:
+ *   - flags field is added which supports AVB_HASHTREE_DESCRIPTOR_FLAGS_USE_AB
+ *   - digest_len may be zero, which indicates the use of a persistent digest
  */
 typedef struct AvbHashtreeDescriptor {
   AvbDescriptor parent_descriptor;
@@ -64,7 +78,8 @@
   uint32_t partition_name_len;
   uint32_t salt_len;
   uint32_t root_digest_len;
-  uint8_t reserved[64];
+  uint32_t flags;
+  uint8_t reserved[60];
 } AVB_ATTR_PACKED AvbHashtreeDescriptor;
 
 /* Copies |src| to |dest| and validates, byte-swapping fields in the
diff --git a/libavb/avb_ops.h b/libavb/avb_ops.h
index bfc21fd..77f7ec3 100644
--- a/libavb/avb_ops.h
+++ b/libavb/avb_ops.h
@@ -35,6 +35,9 @@
 extern "C" {
 #endif
 
+/* Well-known names of named persistent values. */
+#define AVB_NPV_PERSISTENT_DIGEST_PREFIX "avb.persistent_digest."
+
 /* Return codes used for I/O operations.
  *
  * AVB_IO_RESULT_OK is returned if the requested operation was
@@ -51,13 +54,25 @@
  * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION is returned if the
  * range of bytes requested to be read or written is outside the range
  * of the partition.
+ *
+ * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE is returned if a named persistent value
+ * does not exist.
+ *
+ * AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE is returned if a named persistent
+ * value size is not supported or does not match the expected size.
+ *
+ * AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE is returned if a buffer is too small
+ * for the requested operation.
  */
 typedef enum {
   AVB_IO_RESULT_OK,
   AVB_IO_RESULT_ERROR_OOM,
   AVB_IO_RESULT_ERROR_IO,
   AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION,
-  AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION
+  AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION,
+  AVB_IO_RESULT_ERROR_NO_SUCH_VALUE,
+  AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE,
+  AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE,
 } AvbIOResult;
 
 struct AvbOps;
@@ -240,6 +255,53 @@
   AvbIOResult (*get_size_of_partition)(AvbOps* ops,
                                        const char* partition,
                                        uint64_t* out_size_num_bytes);
+
+  /* Reads a persistent value corresponding to the given |name|. The value is
+   * returned in |out_buffer| which must point to |buffer_size| bytes. On
+   * success |out_num_bytes_read| contains the number of bytes read into
+   * |out_buffer|. If AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE is returned,
+   * |out_num_bytes_read| contains the number of bytes that would have been read
+   * which can be used to allocate a buffer.
+   *
+   * The |buffer_size| may be zero and the |out_buffer| may be NULL, but if
+   * |out_buffer| is NULL then |buffer_size| *must* be zero.
+   *
+   * Returns AVB_IO_RESULT_OK on success, otherwise an error code.
+   *
+   * If the value does not exist, is not supported, or is not populated, returns
+   * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE. If |buffer_size| is smaller than the
+   * size of the stored value, returns AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE.
+   *
+   * This operation is currently only used to support persistent digests. If a
+   * device does not use persistent digests this function pointer can be set to
+   * NULL.
+   */
+  AvbIOResult (*read_persistent_value)(AvbOps* ops,
+                                       const char* name,
+                                       size_t buffer_size,
+                                       uint8_t* out_buffer,
+                                       size_t* out_num_bytes_read);
+
+  /* Writes a persistent value corresponding to the given |name|. The value is
+   * supplied in |value| which must point to |value_size| bytes. Any existing
+   * value with the same name is overwritten. If |value_size| is zero, future
+   * calls to |read_persistent_value| will return
+   * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE.
+   *
+   * Returns AVB_IO_RESULT_OK on success, otherwise an error code.
+   *
+   * If the value |name| is not supported, returns
+   * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE. If the |value_size| is not supported,
+   * returns AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE.
+   *
+   * This operation is currently only used to support persistent digests. If a
+   * device does not use persistent digests this function pointer can be set to
+   * NULL.
+   */
+  AvbIOResult (*write_persistent_value)(AvbOps* ops,
+                                        const char* name,
+                                        size_t value_size,
+                                        const uint8_t* value);
 };
 
 #ifdef __cplusplus
diff --git a/libavb/avb_slot_verify.c b/libavb/avb_slot_verify.c
index f8c941c..3e6b04c 100644
--- a/libavb/avb_slot_verify.c
+++ b/libavb/avb_slot_verify.c
@@ -27,6 +27,7 @@
 #include "avb_cmdline.h"
 #include "avb_footer.h"
 #include "avb_hash_descriptor.h"
+#include "avb_hashtree_descriptor.h"
 #include "avb_kernel_cmdline_descriptor.h"
 #include "avb_sha.h"
 #include "avb_util.h"
@@ -65,10 +66,11 @@
   return false;
 }
 
-static AvbSlotVerifyResult load_full_partition(
-    AvbOps* ops, const char* part_name,
-    uint64_t image_size, uint8_t** out_image_buf,
-    bool* out_image_preloaded) {
+static AvbSlotVerifyResult load_full_partition(AvbOps* ops,
+                                               const char* part_name,
+                                               uint64_t image_size,
+                                               uint8_t** out_image_buf,
+                                               bool* out_image_preloaded) {
   size_t part_num_read;
   AvbIOResult io_ret;
 
@@ -110,9 +112,12 @@
       return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
     }
 
-    io_ret = ops->read_from_partition(
-        ops, part_name, 0 /* offset */, image_size, *out_image_buf,
-        &part_num_read);
+    io_ret = ops->read_from_partition(ops,
+                                      part_name,
+                                      0 /* offset */,
+                                      image_size,
+                                      *out_image_buf,
+                                      &part_num_read);
     if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
       return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
     } else if (io_ret != AVB_IO_RESULT_OK) {
@@ -128,6 +133,47 @@
   return AVB_SLOT_VERIFY_RESULT_OK;
 }
 
+static AvbSlotVerifyResult read_persistent_digest(AvbOps* ops,
+                                                  const char* part_name,
+                                                  size_t expected_digest_size,
+                                                  uint8_t* out_digest) {
+  char* persistent_value_name = NULL;
+  AvbIOResult io_ret = AVB_IO_RESULT_OK;
+  size_t stored_digest_size = 0;
+
+  if (ops->read_persistent_value == NULL) {
+    avb_errorv(part_name, ": Persistent values are not implemented.\n", NULL);
+    return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+  }
+  persistent_value_name =
+      avb_strdupv(AVB_NPV_PERSISTENT_DIGEST_PREFIX, part_name, NULL);
+  if (persistent_value_name == NULL) {
+    return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+  }
+  io_ret = ops->read_persistent_value(ops,
+                                      persistent_value_name,
+                                      expected_digest_size,
+                                      out_digest,
+                                      &stored_digest_size);
+  avb_free(persistent_value_name);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+  } else if (io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_VALUE) {
+    avb_errorv(part_name, ": Persistent digest does not exist.\n", NULL);
+    return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+  } else if (io_ret == AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE ||
+             io_ret == AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE ||
+             expected_digest_size != stored_digest_size) {
+    avb_errorv(
+        part_name, ": Persistent digest is not of expected size.\n", NULL);
+    return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    avb_errorv(part_name, ": Error reading persistent digest.\n", NULL);
+    return AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+  }
+  return AVB_SLOT_VERIFY_RESULT_OK;
+}
+
 static AvbSlotVerifyResult load_and_verify_hash_partition(
     AvbOps* ops,
     const char* const* requested_partitions,
@@ -148,6 +194,9 @@
   size_t digest_len;
   const char* found;
   uint64_t image_size;
+  size_t expected_digest_len = 0;
+  uint8_t expected_digest_buf[AVB_SHA512_DIGEST_SIZE];
+  const uint8_t* expected_digest = NULL;
 
   if (!avb_hash_descriptor_validate_and_byteswap(
           (const AvbHashDescriptor*)descriptor, &hash_desc)) {
@@ -177,15 +226,35 @@
     goto out;
   }
 
-  if (!avb_str_concat(part_name,
-                      sizeof part_name,
-                      (const char*)desc_partition_name,
-                      hash_desc.partition_name_len,
-                      ab_suffix,
-                      avb_strlen(ab_suffix))) {
-    avb_error("Partition name and suffix does not fit.\n");
+  if ((hash_desc.flags & AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB) != 0) {
+    /* No ab_suffix, just copy the partition name as is. */
+    if (hash_desc.partition_name_len >= AVB_PART_NAME_MAX_SIZE) {
+      avb_error("Partition name does not fit.\n");
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+    avb_memcpy(part_name, desc_partition_name, hash_desc.partition_name_len);
+    part_name[hash_desc.partition_name_len] = '\0';
+  } else if (hash_desc.digest_len == 0 && avb_strlen(ab_suffix) != 0) {
+    /* No ab_suffix allowed for partitions without a digest in the descriptor
+     * because these partitions hold data unique to this device and are not
+     * updated using an A/B scheme.
+     */
+    avb_error("Cannot use A/B with a persistent digest.\n");
     ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
     goto out;
+  } else {
+    /* Add ab_suffix to the partition name. */
+    if (!avb_str_concat(part_name,
+                        sizeof part_name,
+                        (const char*)desc_partition_name,
+                        hash_desc.partition_name_len,
+                        ab_suffix,
+                        avb_strlen(ab_suffix))) {
+      avb_error("Partition name and suffix does not fit.\n");
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
   }
 
   /* If we're allowing verification errors then hash_desc.image_size
@@ -244,14 +313,31 @@
     goto out;
   }
 
-  if (digest_len != hash_desc.digest_len) {
+  if (hash_desc.digest_len == 0) {
+    // Expect a match to a persistent digest.
+    avb_debugv(part_name, ": No digest, using persistent digest.\n", NULL);
+    expected_digest_len = digest_len;
+    expected_digest = expected_digest_buf;
+    avb_assert(expected_digest_len <= sizeof(expected_digest_buf));
+    ret =
+        read_persistent_digest(ops, part_name, digest_len, expected_digest_buf);
+    if (ret != AVB_SLOT_VERIFY_RESULT_OK) {
+      goto out;
+    }
+  } else {
+    // Expect a match to the digest in the descriptor.
+    expected_digest_len = hash_desc.digest_len;
+    expected_digest = desc_digest;
+  }
+
+  if (digest_len != expected_digest_len) {
     avb_errorv(
         part_name, ": Digest in descriptor not of expected size.\n", NULL);
     ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
     goto out;
   }
 
-  if (avb_safe_memcmp(digest, desc_digest, digest_len) != 0) {
+  if (avb_safe_memcmp(digest, expected_digest, digest_len) != 0) {
     avb_errorv(part_name,
                ": Hash of data does not match digest in descriptor.\n",
                NULL);
@@ -352,7 +438,7 @@
       goto out;
     }
     loaded_partition->data_size = image_size;
-    loaded_partition->data = image_buf;  /* Transferring the owner. */
+    loaded_partition->data = image_buf; /* Transferring the owner. */
     loaded_partition->preloaded = image_preloaded;
     image_buf = NULL;
     image_preloaded = false;
@@ -382,7 +468,8 @@
     const uint8_t* expected_public_key,
     size_t expected_public_key_length,
     AvbSlotVerifyData* slot_data,
-    AvbAlgorithmType* out_algorithm_type) {
+    AvbAlgorithmType* out_algorithm_type,
+    AvbCmdlineSubstList* out_additional_cmdline_subst) {
   char full_partition_name[AVB_PART_NAME_MAX_SIZE];
   AvbSlotVerifyResult ret;
   AvbIOResult io_ret;
@@ -518,7 +605,8 @@
                                    NULL /* expected_public_key */,
                                    0 /* expected_public_key_length */,
                                    slot_data,
-                                   out_algorithm_type);
+                                   out_algorithm_type,
+                                   out_additional_cmdline_subst);
       goto out;
     } else {
       avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL);
@@ -714,7 +802,8 @@
    *   checks that it matches what's in the hash descriptor.
    *
    * - hashtree descriptor: Do nothing since verification happens
-   *   on-the-fly from within the OS.
+   *   on-the-fly from within the OS. (Unless the descriptor uses a
+   *   persistent digest, in which case we need to find it).
    *
    * - chained partition descriptor: Load the footer, load the vbmeta
    *   image, verify vbmeta image (includes rollback checks, hash
@@ -785,18 +874,20 @@
                                sizeof(AvbChainPartitionDescriptor);
         chain_public_key = chain_partition_name + chain_desc.partition_name_len;
 
-        sub_ret = load_and_verify_vbmeta(ops,
-                                         requested_partitions,
-                                         ab_suffix,
-                                         allow_verification_error,
-                                         toplevel_vbmeta_flags,
-                                         chain_desc.rollback_index_location,
-                                         (const char*)chain_partition_name,
-                                         chain_desc.partition_name_len,
-                                         chain_public_key,
-                                         chain_desc.public_key_len,
-                                         slot_data,
-                                         NULL /* out_algorithm_type */);
+        sub_ret =
+            load_and_verify_vbmeta(ops,
+                                   requested_partitions,
+                                   ab_suffix,
+                                   allow_verification_error,
+                                   toplevel_vbmeta_flags,
+                                   chain_desc.rollback_index_location,
+                                   (const char*)chain_partition_name,
+                                   chain_desc.partition_name_len,
+                                   chain_public_key,
+                                   chain_desc.public_key_len,
+                                   slot_data,
+                                   NULL, /* out_algorithm_type */
+                                   NULL /* out_additional_cmdline_subst */);
         if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
           ret = sub_ret;
           if (!result_should_continue(ret)) {
@@ -882,9 +973,90 @@
         }
       } break;
 
-      /* Explicit fall-through */
+      case AVB_DESCRIPTOR_TAG_HASHTREE: {
+        AvbHashtreeDescriptor hashtree_desc;
+
+        if (!avb_hashtree_descriptor_validate_and_byteswap(
+                (AvbHashtreeDescriptor*)descriptors[n], &hashtree_desc)) {
+          avb_errorv(
+              full_partition_name, ": Hashtree descriptor is invalid.\n", NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        /* We only need to continue when there is no digest in the descriptor.
+         * This is because the only processing here is to find the digest and
+         * make it available on the kernel command line.
+         */
+        if (hashtree_desc.root_digest_len == 0) {
+          char part_name[AVB_PART_NAME_MAX_SIZE];
+          size_t digest_len = 0;
+          uint8_t digest_buf[AVB_SHA512_DIGEST_SIZE];
+          const uint8_t* desc_partition_name =
+              ((const uint8_t*)descriptors[n]) + sizeof(AvbHashtreeDescriptor);
+
+          if (!avb_validate_utf8(desc_partition_name,
+                                 hashtree_desc.partition_name_len)) {
+            avb_error("Partition name is not valid UTF-8.\n");
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+            goto out;
+          }
+
+          /* No ab_suffix for partitions without a digest in the descriptor
+           * because these partitions hold data unique to this device and are
+           * not updated using an A/B scheme.
+           */
+          if ((hashtree_desc.flags &
+               AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB) == 0 &&
+              avb_strlen(ab_suffix) != 0) {
+            avb_error("Cannot use A/B with a persistent root digest.\n");
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+            goto out;
+          }
+          if (hashtree_desc.partition_name_len >= AVB_PART_NAME_MAX_SIZE) {
+            avb_error("Partition name does not fit.\n");
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+            goto out;
+          }
+          avb_memcpy(
+              part_name, desc_partition_name, hashtree_desc.partition_name_len);
+          part_name[hashtree_desc.partition_name_len] = '\0';
+
+          /* Determine the expected digest size from the hash algorithm. */
+          if (avb_strcmp((const char*)hashtree_desc.hash_algorithm, "sha1") ==
+              0) {
+            digest_len = AVB_SHA1_DIGEST_SIZE;
+          } else if (avb_strcmp((const char*)hashtree_desc.hash_algorithm,
+                                "sha256") == 0) {
+            digest_len = AVB_SHA256_DIGEST_SIZE;
+          } else if (avb_strcmp((const char*)hashtree_desc.hash_algorithm,
+                                "sha512") == 0) {
+            digest_len = AVB_SHA512_DIGEST_SIZE;
+          } else {
+            avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL);
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+            goto out;
+          }
+
+          ret = read_persistent_digest(ops, part_name, digest_len, digest_buf);
+          if (ret != AVB_SLOT_VERIFY_RESULT_OK) {
+            goto out;
+          }
+
+          if (out_additional_cmdline_subst) {
+            ret =
+                avb_add_root_digest_substitution(part_name,
+                                                 digest_buf,
+                                                 digest_len,
+                                                 out_additional_cmdline_subst);
+            if (ret != AVB_SLOT_VERIFY_RESULT_OK) {
+              goto out;
+            }
+          }
+        }
+      } break;
+
       case AVB_DESCRIPTOR_TAG_PROPERTY:
-      case AVB_DESCRIPTOR_TAG_HASHTREE:
         /* Do nothing. */
         break;
     }
@@ -932,6 +1104,7 @@
   AvbVBMetaImageHeader toplevel_vbmeta;
   bool allow_verification_error =
       (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR);
+  AvbCmdlineSubstList* additional_cmdline_subst = NULL;
 
   /* Fail early if we're missing the AvbOps needed for slot verification.
    *
@@ -976,6 +1149,12 @@
     goto fail;
   }
 
+  additional_cmdline_subst = avb_new_cmdline_subst_list();
+  if (additional_cmdline_subst == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto fail;
+  }
+
   ret = load_and_verify_vbmeta(ops,
                                requested_partitions,
                                ab_suffix,
@@ -987,7 +1166,8 @@
                                NULL /* expected_public_key */,
                                0 /* expected_public_key_length */,
                                slot_data,
-                               &algorithm_type);
+                               &algorithm_type,
+                               additional_cmdline_subst);
   if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) {
     goto fail;
   }
@@ -1032,9 +1212,11 @@
       /* Add options - any failure in avb_append_options() is either an
        * I/O or OOM error.
        */
-      AvbSlotVerifyResult sub_ret = avb_append_options(
-          ops, slot_data, &toplevel_vbmeta, algorithm_type,
-          hashtree_error_mode);
+      AvbSlotVerifyResult sub_ret = avb_append_options(ops,
+                                                       slot_data,
+                                                       &toplevel_vbmeta,
+                                                       algorithm_type,
+                                                       hashtree_error_mode);
       if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
         ret = sub_ret;
         goto fail;
@@ -1044,8 +1226,11 @@
     /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */
     if (slot_data->cmdline != NULL) {
       char* new_cmdline;
-      new_cmdline = avb_sub_cmdline(
-          ops, slot_data->cmdline, ab_suffix, using_boot_for_vbmeta);
+      new_cmdline = avb_sub_cmdline(ops,
+                                    slot_data->cmdline,
+                                    ab_suffix,
+                                    using_boot_for_vbmeta,
+                                    additional_cmdline_subst);
       if (new_cmdline != slot_data->cmdline) {
         if (new_cmdline == NULL) {
           ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
@@ -1063,6 +1248,9 @@
     }
   }
 
+  avb_free_cmdline_subst_list(additional_cmdline_subst);
+  additional_cmdline_subst = NULL;
+
   if (!allow_verification_error) {
     avb_assert(ret == AVB_SLOT_VERIFY_RESULT_OK);
   }
@@ -1073,6 +1261,9 @@
   if (slot_data != NULL) {
     avb_slot_verify_data_free(slot_data);
   }
+  if (additional_cmdline_subst != NULL) {
+    avb_free_cmdline_subst_list(additional_cmdline_subst);
+  }
   return ret;
 }
 
diff --git a/libavb/avb_util.c b/libavb/avb_util.c
index fafde01..c04c79a 100644
--- a/libavb/avb_util.c
+++ b/libavb/avb_util.c
@@ -401,3 +401,30 @@
   }
   return str;
 }
+
+void avb_uppercase(char* str) {
+  size_t i;
+  for (i = 0; str[i] != '\0'; ++i) {
+    if (str[i] <= 0x7A && str[i] >= 0x61) {
+      str[i] -= 0x20;
+    }
+  }
+}
+
+char* avb_bin2hex(const uint8_t* data, size_t data_len) {
+  const char hex_digits[17] = "0123456789abcdef";
+  char* hex_data;
+  size_t n;
+
+  hex_data = avb_malloc(data_len * 2 + 1);
+  if (hex_data == NULL) {
+    return NULL;
+  }
+
+  for (n = 0; n < data_len; n++) {
+    hex_data[n * 2] = hex_digits[data[n] >> 4];
+    hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f];
+  }
+  hex_data[n * 2] = '\0';
+  return hex_data;
+}
diff --git a/libavb/avb_util.h b/libavb/avb_util.h
index 07c3258..be1b3c9 100644
--- a/libavb/avb_util.h
+++ b/libavb/avb_util.h
@@ -270,6 +270,16 @@
  */
 const char* avb_basename(const char* str);
 
+/* Converts any ascii lowercase characters in |str| to uppercase in-place.
+ * |str| must be NUL-terminated and valid UTF-8.
+ */
+void avb_uppercase(char* str);
+
+/* Converts |data_len| bytes of |data| to hex and returns the result. Returns
+ * NULL on OOM. Caller must free the returned string with avb_free.
+ */
+char* avb_bin2hex(const uint8_t* data, size_t data_len);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libavb/avb_version.h b/libavb/avb_version.h
index 9d92970..ce43136 100644
--- a/libavb/avb_version.h
+++ b/libavb/avb_version.h
@@ -37,7 +37,7 @@
 
 /* The version number of AVB - keep in sync with avbtool. */
 #define AVB_VERSION_MAJOR 1
-#define AVB_VERSION_MINOR 0
+#define AVB_VERSION_MINOR 1
 #define AVB_VERSION_SUB 0
 
 /* Returns a NUL-terminated string for the libavb version in use.  The
diff --git a/test/avb_atx_validate_unittest.cc b/test/avb_atx_validate_unittest.cc
index 0299b51..c32ecf8 100644
--- a/test/avb_atx_validate_unittest.cc
+++ b/test/avb_atx_validate_unittest.cc
@@ -173,6 +173,21 @@
     return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
   }
 
+  AvbIOResult read_persistent_value(const char* name,
+                                    size_t buffer_size,
+                                    uint8_t* out_buffer,
+                                    size_t* out_num_bytes_read) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
+  }
+
+  AvbIOResult write_persistent_value(const char* name,
+                                     size_t value_size,
+                                     const uint8_t* value) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
+  }
+
   AvbIOResult read_permanent_attributes(
       AvbAtxPermanentAttributes* attributes) override {
     if (fail_read_permanent_attributes_) {
diff --git a/test/avb_slot_verify_unittest.cc b/test/avb_slot_verify_unittest.cc
index 86d756e..9584439 100644
--- a/test/avb_slot_verify_unittest.cc
+++ b/test/avb_slot_verify_unittest.cc
@@ -33,12 +33,14 @@
 
 namespace avb {
 
-class AvbSlotVerifyTest : public BaseAvbToolTest {
+class AvbSlotVerifyTest : public BaseAvbToolTest,
+                          public FakeAvbOpsDelegateWithDefaults {
  public:
   AvbSlotVerifyTest() {}
 
   virtual void SetUp() override {
     BaseAvbToolTest::SetUp();
+    ops_.set_delegate(this);
     ops_.set_partition_dir(testdir_);
     ops_.set_stored_rollback_indexes({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
     ops_.set_stored_is_device_unlocked(false);
@@ -47,8 +49,6 @@
   void CmdlineWithHashtreeVerification(bool hashtree_verification_on);
   void CmdlineWithChainedHashtreeVerification(bool hashtree_verification_on);
   void VerificationDisabled(bool use_avbctl, bool preload);
-
-  FakeAvbOps ops_;
 };
 
 TEST_F(AvbSlotVerifyTest, Basic) {
@@ -73,7 +73,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
       "androidboot.vbmeta.digest="
@@ -106,7 +106,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha512 androidboot.vbmeta.size=1152 "
       "androidboot.vbmeta.digest="
@@ -142,7 +142,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=unlocked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
       "androidboot.vbmeta.digest="
@@ -506,6 +506,7 @@
       "      Salt:                  deadbeef\n"
       "      Digest:                "
       "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "      Flags:                 0\n"
       "    Kernel Cmdline descriptor:\n"
       "      Flags:                 0\n"
       "      Kernel Cmdline:        'cmdline in hash footer "
@@ -562,7 +563,7 @@
       "cmdline in vbmeta 1234-fake-guid-for:boot_a cmdline in hash footer "
       "1234-fake-guid-for:system_a "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
       "androidboot.vbmeta.digest="
@@ -807,6 +808,7 @@
       "      Salt:                  deadbeef\n"
       "      Digest:                "
       "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "      Flags:                 0\n"
       "    Kernel Cmdline descriptor:\n"
       "      Flags:                 0\n"
       "      Kernel Cmdline:        'cmdline2 in hash footer'\n",
@@ -883,7 +885,7 @@
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
       "androidboot.vbmeta.digest="
@@ -1219,7 +1221,7 @@
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
       "androidboot.vbmeta.digest="
@@ -1290,13 +1292,15 @@
       "      Salt:                  deadbeef\n"
       "      Digest:                "
       "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "      Flags:                 0\n"
       "    Hash descriptor:\n"
       "      Image Size:            10485760 bytes\n"
       "      Hash Algorithm:        sha256\n"
       "      Partition Name:        bar\n"
       "      Salt:                  deadbeef\n"
       "      Digest:                "
-      "baea4bbd261d0edf4d1fe5e6e5a36976c291eeba66b6a46fa81dba691327a727\n",
+      "baea4bbd261d0edf4d1fe5e6e5a36976c291eeba66b6a46fa81dba691327a727\n"
+      "      Flags:                 0\n",
       InfoImage(vbmeta_image_path_));
 
   ops_.set_expected_public_key(
@@ -1401,13 +1405,15 @@
       "      Salt:                  deadbeef\n"
       "      Digest:                "
       "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "      Flags:                 0\n"
       "    Hash descriptor:\n"
       "      Image Size:            10485760 bytes\n"
       "      Hash Algorithm:        sha256\n"
       "      Partition Name:        bar\n"
       "      Salt:                  deadbeef\n"
       "      Digest:                "
-      "baea4bbd261d0edf4d1fe5e6e5a36976c291eeba66b6a46fa81dba691327a727\n",
+      "baea4bbd261d0edf4d1fe5e6e5a36976c291eeba66b6a46fa81dba691327a727\n"
+      "      Flags:                 0\n",
       InfoImage(vbmeta_image_path_));
 
   ops_.set_expected_public_key(
@@ -1464,7 +1470,7 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2688 "
       "androidboot.vbmeta.digest="
@@ -1572,7 +1578,7 @@
         "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
         "should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-        "androidboot.vbmeta.avb_version=1.0 "
+        "androidboot.vbmeta.avb_version=1.1 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1536 "
         "androidboot.vbmeta.digest="
@@ -1586,7 +1592,7 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-        "androidboot.vbmeta.avb_version=1.0 "
+        "androidboot.vbmeta.avb_version=1.1 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1536 "
         "androidboot.vbmeta.digest="
@@ -1665,6 +1671,7 @@
       "      Partition Name:        foobar\n"
       "      Salt:                  d00df00d\n"
       "      Root Digest:           e811611467dcd6e8dc4324e45f706c2bdd51db67\n"
+      "      Flags:                 0\n"
       "    Kernel Cmdline descriptor:\n"
       "      Flags:                 1\n"
       "      Kernel Cmdline:        'dm=\"1 vroot none ro 1,0 2056 verity 1 "
@@ -1747,7 +1754,7 @@
         "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
         "should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-        "androidboot.vbmeta.avb_version=1.0 "
+        "androidboot.vbmeta.avb_version=1.1 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3456 "
         "androidboot.vbmeta.digest="
@@ -1761,7 +1768,7 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
-        "androidboot.vbmeta.avb_version=1.0 "
+        "androidboot.vbmeta.avb_version=1.1 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3456 "
         "androidboot.vbmeta.digest="
@@ -1780,8 +1787,8 @@
   CmdlineWithChainedHashtreeVerification(true);
 }
 
-void AvbSlotVerifyTest::VerificationDisabled(
-    bool use_avbctl, bool preload_boot) {
+void AvbSlotVerifyTest::VerificationDisabled(bool use_avbctl,
+                                             bool preload_boot) {
   const size_t boot_part_size = 32 * 1024 * 1024;
   const size_t dtbo_part_size = 4 * 1024 * 1024;
   const size_t rootfs_size = 1028 * 1024;
@@ -2046,6 +2053,7 @@
       "      Salt:                  d00df00d\n"
       "      Digest:                "
       "4c109399b20e476bab15363bff55740add83e1c1e97e0b132f5c713ddd8c7868\n"
+      "      Flags:                 0\n"
       "    Chain Partition descriptor:\n"
       "      Partition Name:          bazboo\n"
       "      Rollback Index Location: 1\n"
@@ -2075,6 +2083,7 @@
       "      Partition Name:        system\n"
       "      Salt:                  d00df00d\n"
       "      Root Digest:           c9ffc3bfae5000269a55a56621547fd1fcf819df\n"
+      "      Flags:                 0\n"
       "    Hashtree descriptor:\n"
       "      Version of dm-verity:  1\n"
       "      Image Size:            8388608 bytes\n"
@@ -2088,7 +2097,8 @@
       "      Hash Algorithm:        sha1\n"
       "      Partition Name:        foobar\n"
       "      Salt:                  d00df00d\n"
-      "      Root Digest:           d52d93c988d336a79abe1c05240ae9a79a9b7d61\n",
+      "      Root Digest:           d52d93c988d336a79abe1c05240ae9a79a9b7d61\n"
+      "      Flags:                 0\n",
       InfoImage(boot_path));
 
   ops_.set_expected_public_key(
@@ -2116,7 +2126,7 @@
       "4096 4096 4096 4096 sha1 c9ffc3bfae5000269a55a56621547fd1fcf819df "
       "d00df00d 2 restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:boot "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=5312 "
       "androidboot.vbmeta.digest="
@@ -2287,7 +2297,7 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1664 "
@@ -2317,7 +2327,7 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1664 "
@@ -2346,7 +2356,7 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "ignore_zero_blocks ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1664 "
@@ -2387,7 +2397,7 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "ignore_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
-      "androidboot.vbmeta.avb_version=1.0 "
+      "androidboot.vbmeta.avb_version=1.1 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1664 "
@@ -2426,7 +2436,7 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
-        "androidboot.vbmeta.avb_version=1.0 "
+        "androidboot.vbmeta.avb_version=1.1 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 "
         "androidboot.vbmeta.size=1664 "
@@ -2438,4 +2448,466 @@
   }
 }
 
+class AvbSlotVerifyTestWithPersistentDigest : public AvbSlotVerifyTest {
+ protected:
+  void SetupWithHashDescriptor(bool do_not_use_ab = true) {
+    const size_t factory_partition_size = 16 * 1024 * 1024;
+    const size_t factory_image_size = 5 * 1024 * 1024;
+    base::FilePath factory_path =
+        GenerateImage("factory.img", factory_image_size);
+
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hash_footer"
+                   " --image %s"
+                   " --rollback_index 0"
+                   " --partition_name factory"
+                   " --partition_size %zd"
+                   " --salt deadbeef"
+                   " --internal_release_string \"\""
+                   " --use_persistent_digest %s",
+                   factory_path.value().c_str(),
+                   factory_partition_size,
+                   do_not_use_ab ? "--do_not_use_ab" : "");
+
+    GenerateVBMetaImage(
+        "vbmeta_a.img",
+        "SHA256_RSA2048",
+        0,
+        base::FilePath("test/data/testkey_rsa2048.pem"),
+        base::StringPrintf("--internal_release_string \"\" "
+                           "--include_descriptors_from_image %s ",
+                           factory_path.value().c_str()));
+
+    EXPECT_EQ(base::StringPrintf("Minimum libavb version:   1.1\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     320 bytes\n"
+                                 "Auxiliary Block:          704 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Flags:                    0\n"
+                                 "Release String:           ''\n"
+                                 "Descriptors:\n"
+                                 "    Hash descriptor:\n"
+                                 "      Image Size:            5242880 bytes\n"
+                                 "      Hash Algorithm:        sha256\n"
+                                 "      Partition Name:        factory\n"
+                                 "      Salt:                  deadbeef\n"
+                                 "      Digest:                \n"
+                                 "      Flags:                 %d\n",
+                                 do_not_use_ab ? 1 : 0),
+              InfoImage(vbmeta_image_path_));
+
+    ops_.set_expected_public_key(
+        PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+  }
+
+  void SetupWithHashtreeDescriptor(bool do_not_use_ab = true) {
+    const size_t factory_partition_size = 16 * 1024 * 1024;
+    const size_t factory_image_size = 5 * 1024 * 1024;
+    base::FilePath factory_path =
+        GenerateImage("factory.img", factory_image_size);
+
+    EXPECT_COMMAND(
+        0,
+        "./avbtool add_hashtree_footer"
+        " --image %s"
+        " --rollback_index 0"
+        " --partition_name factory"
+        " --partition_size %zd"
+        " --salt deadbeef"
+        " --hash_algorithm %s"
+        " --internal_release_string \"\""
+        " --kernel_cmdline "
+        "'androidboot.vbmeta.root_digest.factory=$(AVB_FACTORY_ROOT_DIGEST)'"
+        " --use_persistent_digest %s",
+        factory_path.value().c_str(),
+        factory_partition_size,
+        verity_hash_algorithm_.c_str(),
+        do_not_use_ab ? "--do_not_use_ab" : "");
+
+    GenerateVBMetaImage(
+        "vbmeta_a.img",
+        "SHA256_RSA2048",
+        0,
+        base::FilePath("test/data/testkey_rsa2048.pem"),
+        base::StringPrintf("--internal_release_string \"\" "
+                           "--include_descriptors_from_image %s ",
+                           factory_path.value().c_str()));
+
+    int expected_tree_size =
+        (verity_hash_algorithm_ == "sha512") ? 86016 : 45056;
+    int expected_fec_offset =
+        (verity_hash_algorithm_ == "sha512") ? 5328896 : 5287936;
+    EXPECT_EQ(base::StringPrintf("Minimum libavb version:   1.1\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     320 bytes\n"
+                                 "Auxiliary Block:          832 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Flags:                    0\n"
+                                 "Release String:           ''\n"
+                                 "Descriptors:\n"
+                                 "    Hashtree descriptor:\n"
+                                 "      Version of dm-verity:  1\n"
+                                 "      Image Size:            5242880 bytes\n"
+                                 "      Tree Offset:           5242880\n"
+                                 "      Tree Size:             %d bytes\n"
+                                 "      Data Block Size:       4096 bytes\n"
+                                 "      Hash Block Size:       4096 bytes\n"
+                                 "      FEC num roots:         2\n"
+                                 "      FEC offset:            %d\n"
+                                 "      FEC size:              49152 bytes\n"
+                                 "      Hash Algorithm:        %s\n"
+                                 "      Partition Name:        factory\n"
+                                 "      Salt:                  deadbeef\n"
+                                 "      Root Digest:           \n"
+                                 "      Flags:                 %d\n"
+                                 "    Kernel Cmdline descriptor:\n"
+                                 "      Flags:                 0\n"
+                                 "      Kernel Cmdline:        "
+                                 "'androidboot.vbmeta.root_digest.factory=$("
+                                 "AVB_FACTORY_ROOT_DIGEST)'\n",
+                                 expected_tree_size,
+                                 expected_fec_offset,
+                                 verity_hash_algorithm_.c_str(),
+                                 do_not_use_ab ? 1 : 0),
+              InfoImage(vbmeta_image_path_));
+
+    ops_.set_expected_public_key(
+        PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+  }
+
+  void Verify(bool expect_success) {
+    AvbSlotVerifyData* slot_data = NULL;
+    const char* requested_partitions[] = {"factory", NULL};
+    AvbSlotVerifyResult result =
+        avb_slot_verify(ops_.avb_ops(),
+                        requested_partitions,
+                        "_a",
+                        AVB_SLOT_VERIFY_FLAGS_NONE,
+                        AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
+                        &slot_data);
+    if (expect_success) {
+      ASSERT_EQ(AVB_SLOT_VERIFY_RESULT_OK, result);
+      ASSERT_NE(nullptr, slot_data);
+      last_cmdline_ = slot_data->cmdline;
+      avb_slot_verify_data_free(slot_data);
+    } else {
+      EXPECT_NE(AVB_SLOT_VERIFY_RESULT_OK, result);
+      EXPECT_EQ(nullptr, slot_data);
+      if (expected_error_code_ != AVB_SLOT_VERIFY_RESULT_OK) {
+        EXPECT_EQ(expected_error_code_, result);
+      }
+    }
+  }
+
+  std::string last_cmdline_;
+  std::string verity_hash_algorithm_{"sha1"};
+  AvbSlotVerifyResult expected_error_code_{AVB_SLOT_VERIFY_RESULT_OK};
+
+ public:
+  // Persistent digests always use AVB_NPV_PERSISTENT_DIGEST_PREFIX followed by
+  // the partition name.
+  const char* kPersistentValueName = "avb.persistent_digest.factory";
+  // The digest for the hash descriptor which matches the factory contents.
+  const uint8_t kDigest[AVB_SHA256_DIGEST_SIZE] = {
+      0x18, 0x4c, 0xb3, 0x62, 0x43, 0xad, 0xb8, 0xb8, 0x7d, 0x2d, 0x8c,
+      0x48, 0x02, 0xde, 0x32, 0x12, 0x5f, 0xe2, 0x94, 0xec, 0x46, 0x75,
+      0x3d, 0x73, 0x21, 0x44, 0xee, 0x65, 0xdf, 0x68, 0xa2, 0x3d};
+};
+
+TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic) {
+  SetupWithHashDescriptor();
+  // Store the expected image digest as a persistent value.
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA256_DIGEST_SIZE, kDigest);
+  Verify(true /* expect_success */);
+  EXPECT_EQ(
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.avb_version=1.1 "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 "
+      "androidboot.vbmeta.size=1280 "
+      "androidboot.vbmeta.digest="
+      "604268b04d4a971d2d727c79a70b2ea7f6a0e42ccbdead1983acbf015061ce6b "
+      "androidboot.vbmeta.invalidate_on_error=yes "
+      "androidboot.veritymode=enforcing",
+      last_cmdline_);
+}
+
+TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_WithAB) {
+  SetupWithHashDescriptor(false /* do_not_use_ab */);
+  // Store the expected image digest as a persistent value.
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA256_DIGEST_SIZE, kDigest);
+  Verify(false /* expect_success */);
+}
+
+class AvbSlotVerifyTestWithPersistentDigest_InvalidDigestLength
+    : public AvbSlotVerifyTestWithPersistentDigest,
+      public ::testing::WithParamInterface<size_t> {};
+
+TEST_P(AvbSlotVerifyTestWithPersistentDigest_InvalidDigestLength, Param) {
+  SetupWithHashDescriptor();
+  // Store a digest value with the given length.
+  ops_.write_persistent_value(kPersistentValueName, GetParam(), kDigest);
+  Verify(false /* expect_success */);
+}
+
+// Test a bunch of invalid digest length values.
+INSTANTIATE_TEST_CASE_P(
+    P,
+    AvbSlotVerifyTestWithPersistentDigest_InvalidDigestLength,
+    ::testing::Values(AVB_SHA256_DIGEST_SIZE + 1,
+                      AVB_SHA256_DIGEST_SIZE - 1,
+                      0,
+                      AVB_SHA512_DIGEST_SIZE));
+
+class AvbSlotVerifyTestWithPersistentDigest_InvalidPersistentValueName
+    : public AvbSlotVerifyTestWithPersistentDigest,
+      public ::testing::WithParamInterface<const char*> {};
+
+TEST_P(AvbSlotVerifyTestWithPersistentDigest_InvalidPersistentValueName,
+       Param) {
+  SetupWithHashDescriptor();
+  ops_.write_persistent_value(GetParam(), AVB_SHA256_DIGEST_SIZE, kDigest);
+  Verify(false /* expect_success */);
+}
+
+// Test a bunch of invalid persistent value names.
+INSTANTIATE_TEST_CASE_P(
+    P,
+    AvbSlotVerifyTestWithPersistentDigest_InvalidPersistentValueName,
+    ::testing::Values(
+        "",
+        "AVBPD_factory0",
+        "AVBPD_factor",
+        "loooooooooooooooooooooooooooooooooooooooooooooongvalue"));
+
+class AvbSlotVerifyTestWithPersistentDigest_ReadDigestFailure
+    : public AvbSlotVerifyTestWithPersistentDigest,
+      public ::testing::WithParamInterface<AvbIOResult> {
+  // FakeAvbOpsDelegate override.
+  AvbIOResult read_persistent_value(const char* name,
+                                    size_t buffer_size,
+                                    uint8_t* out_buffer,
+                                    size_t* out_num_bytes_read) override {
+    return GetParam();
+  }
+};
+
+TEST_P(AvbSlotVerifyTestWithPersistentDigest_ReadDigestFailure, Param) {
+  SetupWithHashDescriptor();
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA256_DIGEST_SIZE, kDigest);
+  switch (GetParam()) {
+    case AVB_IO_RESULT_ERROR_OOM:
+      expected_error_code_ = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      break;
+    // Fall through.
+    case AVB_IO_RESULT_ERROR_NO_SUCH_VALUE:
+    case AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE:
+    case AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE:
+      expected_error_code_ = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      break;
+    default:
+      break;
+  }
+  Verify(false /* expect_success */);
+}
+
+// Test a bunch of error codes.
+INSTANTIATE_TEST_CASE_P(
+    P,
+    AvbSlotVerifyTestWithPersistentDigest_ReadDigestFailure,
+    ::testing::Values(AVB_IO_RESULT_ERROR_OOM,
+                      AVB_IO_RESULT_ERROR_IO,
+                      AVB_IO_RESULT_ERROR_NO_SUCH_VALUE,
+                      AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE,
+                      AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE));
+
+TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_Sha1) {
+  verity_hash_algorithm_ = "sha1";
+  SetupWithHashtreeDescriptor();
+  // Store an arbitrary image digest.
+  uint8_t fake_digest[]{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA1_DIGEST_SIZE, fake_digest);
+  Verify(true /* expect_success */);
+  EXPECT_EQ(
+      "androidboot.vbmeta.root_digest.factory="
+      // Note: Here appear the bytes used in write_persistent_value above.
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.avb_version=1.1 "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 "
+      "androidboot.vbmeta.size=1408 "
+      "androidboot.vbmeta.digest="
+      "bdeff592f85f34a6ae40919e311273a10027f3877daa9c8c1be8e685947abb3d "
+      "androidboot.vbmeta.invalidate_on_error=yes "
+      "androidboot.veritymode=enforcing",
+      last_cmdline_);
+}
+
+TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_Sha256) {
+  verity_hash_algorithm_ = "sha256";
+  SetupWithHashtreeDescriptor();
+  // Store an arbitrary image digest.
+  uint8_t fake_digest[]{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA256_DIGEST_SIZE, fake_digest);
+  Verify(true /* expect_success */);
+  EXPECT_EQ(
+      "androidboot.vbmeta.root_digest.factory="
+      // Note: Here appear the bytes used in write_persistent_value above.
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.avb_version=1.1 "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 "
+      "androidboot.vbmeta.size=1408 "
+      "androidboot.vbmeta.digest="
+      "03d287a0a126ed3fce48d6d8907612559e1485d29e201ede5838d65c5cc4bec2 "
+      "androidboot.vbmeta.invalidate_on_error=yes "
+      "androidboot.veritymode=enforcing",
+      last_cmdline_);
+}
+
+TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_Sha512) {
+  verity_hash_algorithm_ = "sha512";
+  SetupWithHashtreeDescriptor();
+  // Store an arbitrary image digest.
+  uint8_t fake_digest[]{
+      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA512_DIGEST_SIZE, fake_digest);
+  Verify(true /* expect_success */);
+  EXPECT_EQ(
+      "androidboot.vbmeta.root_digest.factory="
+      // Note: Here appear the bytes used in write_persistent_value above.
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.avb_version=1.1 "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 "
+      "androidboot.vbmeta.size=1408 "
+      "androidboot.vbmeta.digest="
+      "931b10c7c8e7ab270437a4481b7d8d5c9757a3df190b7df3b6f93bf0289b9911 "
+      "androidboot.vbmeta.invalidate_on_error=yes "
+      "androidboot.veritymode=enforcing",
+      last_cmdline_);
+}
+
+TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_WithAB) {
+  verity_hash_algorithm_ = "sha1";
+  SetupWithHashtreeDescriptor(false /* do_not_use_ab */);
+  // Store an arbitrary image digest.
+  uint8_t fake_digest[]{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+                        0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA1_DIGEST_SIZE, fake_digest);
+  Verify(false /* expect_success */);
+}
+
+class AvbSlotVerifyTestWithPersistentDigest_Hashtree_InvalidDigestLength
+    : public AvbSlotVerifyTestWithPersistentDigest,
+      public ::testing::WithParamInterface<size_t> {};
+
+TEST_P(AvbSlotVerifyTestWithPersistentDigest_Hashtree_InvalidDigestLength,
+       Param) {
+  SetupWithHashtreeDescriptor();
+  // Store a digest value with the given length.
+  ops_.write_persistent_value(kPersistentValueName, GetParam(), kDigest);
+  Verify(false /* expect_success */);
+}
+
+// Test a bunch of invalid digest length values.
+INSTANTIATE_TEST_CASE_P(
+    P,
+    AvbSlotVerifyTestWithPersistentDigest_Hashtree_InvalidDigestLength,
+    ::testing::Values(AVB_SHA1_DIGEST_SIZE + 1,
+                      AVB_SHA1_DIGEST_SIZE - 1,
+                      0,
+                      AVB_SHA256_DIGEST_SIZE,
+                      AVB_SHA512_DIGEST_SIZE));
+
+class AvbSlotVerifyTestWithPersistentDigest_Hashtree_InvalidPersistentValueName
+    : public AvbSlotVerifyTestWithPersistentDigest,
+      public ::testing::WithParamInterface<const char*> {};
+
+TEST_P(
+    AvbSlotVerifyTestWithPersistentDigest_Hashtree_InvalidPersistentValueName,
+    Param) {
+  SetupWithHashtreeDescriptor();
+  ops_.write_persistent_value(GetParam(), AVB_SHA256_DIGEST_SIZE, kDigest);
+  Verify(false /* expect_success */);
+}
+
+// Test a bunch of invalid persistent value names.
+INSTANTIATE_TEST_CASE_P(
+    P,
+    AvbSlotVerifyTestWithPersistentDigest_Hashtree_InvalidPersistentValueName,
+    ::testing::Values(
+        "",
+        "avb.persistent_digest.factory0",
+        "avb.persistent_digest.factor",
+        "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"
+        "oooooooooooooooooooooooooooooooooooooooooooooooooooongvalue"));
+
+class AvbSlotVerifyTestWithPersistentDigest_Hashtree_ReadDigestFailure
+    : public AvbSlotVerifyTestWithPersistentDigest,
+      public ::testing::WithParamInterface<AvbIOResult> {
+  // FakeAvbOpsDelegate override.
+  AvbIOResult read_persistent_value(const char* name,
+                                    size_t buffer_size,
+                                    uint8_t* out_buffer,
+                                    size_t* out_num_bytes_read) override {
+    return GetParam();
+  }
+};
+
+TEST_P(AvbSlotVerifyTestWithPersistentDigest_Hashtree_ReadDigestFailure,
+       Param) {
+  SetupWithHashtreeDescriptor();
+  ops_.write_persistent_value(
+      kPersistentValueName, AVB_SHA256_DIGEST_SIZE, kDigest);
+  switch (GetParam()) {
+    case AVB_IO_RESULT_ERROR_OOM:
+      expected_error_code_ = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      break;
+    // Fall through.
+    case AVB_IO_RESULT_ERROR_NO_SUCH_VALUE:
+    case AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE:
+    case AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE:
+      expected_error_code_ = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      break;
+    default:
+      break;
+  }
+  Verify(false /* expect_success */);
+}
+
+// Test a bunch of error codes.
+INSTANTIATE_TEST_CASE_P(
+    P,
+    AvbSlotVerifyTestWithPersistentDigest_Hashtree_ReadDigestFailure,
+    ::testing::Values(AVB_IO_RESULT_ERROR_OOM,
+                      AVB_IO_RESULT_ERROR_IO,
+                      AVB_IO_RESULT_ERROR_NO_SUCH_VALUE,
+                      AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE,
+                      AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE));
+
 }  // namespace avb
diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc
index a2561bd..fe7d3ba 100644
--- a/test/avbtool_unittest.cc
+++ b/test/avbtool_unittest.cc
@@ -409,7 +409,8 @@
       "      Salt:                  d00df00d\n"
       "      Digest:                "
       "9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f"
-      "5e4ed357fbcf58d88d9\n",
+      "5e4ed357fbcf58d88d9\n"
+      "      Flags:                 0\n",
       partition_size,
       sparse_image ? " (Sparse)" : "");
 }
@@ -486,7 +487,8 @@
         "      Salt:                  d00df00d\n"
         "      Digest:                "
         "9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f"
-        "5e4ed357fbcf58d88d9\n",
+        "5e4ed357fbcf58d88d9\n"
+        "      Flags:                 0\n",
         InfoImage(ext_vbmeta_path));
   }
 
@@ -690,7 +692,8 @@
       "      Image Size:            10354688 bytes\n"
       "      Hash Algorithm:        sha256\n"
       "      Partition Name:        foobar\n"
-      "      Salt:                  d00df00d\n",
+      "      Salt:                  d00df00d\n"
+      "      Flags:                 0\n",
       info);
 
   EXPECT_COMMAND(0,
@@ -738,6 +741,134 @@
                  partition_size);
 }
 
+TEST_F(AvbToolTest, AddHashFooterWithPersistentDigest) {
+  size_t partition_size = 1024 * 1024;
+  base::FilePath path = GenerateImage("digest_location", 1024);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--use_persistent_digest",
+                 path.value().c_str(),
+                 (int)partition_size);
+  // There are two important bits specific to these flags:
+  //   Minimum libavb version = 1.1
+  //   Hash descriptor -> Digest = (empty)
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               1048576 bytes\n"
+      "Original image size:      1024 bytes\n"
+      "VBMeta offset:            4096\n"
+      "VBMeta size:              1280 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.1\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          704 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            1024 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Digest:                \n"
+      "      Flags:                 0\n",
+      InfoImage(path));
+}
+
+TEST_F(AvbToolTest, AddHashFooterWithNoAB) {
+  size_t partition_size = 1024 * 1024;
+  base::FilePath path = GenerateImage("digest_location", 1024);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--do_not_use_ab",
+                 path.value().c_str(),
+                 (int)partition_size);
+  // There are two important bits specific to these flags:
+  //   Minimum libavb version = 1.1
+  //   Hash descriptor -> Flags = 1
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               1048576 bytes\n"
+      "Original image size:      1024 bytes\n"
+      "VBMeta offset:            4096\n"
+      "VBMeta size:              1280 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.1\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          704 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            1024 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Digest:                "
+      "91386fea3e251ad0c2cb6859e4f4772f37fdb69f17d46636ddc9e7fbfd3bf3d0\n"
+      "      Flags:                 1\n",
+      InfoImage(path));
+}
+
+TEST_F(AvbToolTest, AddHashFooterWithPersistentDigestAndNoAB) {
+  size_t partition_size = 1024 * 1024;
+  base::FilePath path = GenerateImage("digest_location", 1024);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--use_persistent_digest --do_not_use_ab",
+                 path.value().c_str(),
+                 (int)partition_size);
+  // There are three important bits specific to these flags:
+  //   Minimum libavb version = 1.1
+  //   Hash descriptor -> Digest = (empty)
+  //   Hash descriptor -> Flags = 1
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               1048576 bytes\n"
+      "Original image size:      1024 bytes\n"
+      "VBMeta offset:            4096\n"
+      "VBMeta size:              1280 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.1\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          704 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            1024 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Digest:                \n"
+      "      Flags:                 1\n",
+      InfoImage(path));
+}
+
 void AvbToolTest::AddHashtreeFooterTest(bool sparse_image) {
   const size_t rootfs_size = 1028 * 1024;
   const size_t partition_size = 1536 * 1024;
@@ -810,7 +941,8 @@
                                  "      Partition Name:        foobar\n"
                                  "      Salt:                  d00df00d\n"
                                  "      Root Digest:           "
-                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n",
+                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n"
+                                 "      Flags:                 0\n",
                                  sparse_image ? " (Sparse)" : ""),
               InfoImage(rootfs_path));
 
@@ -838,7 +970,8 @@
         "      Partition Name:        foobar\n"
         "      Salt:                  d00df00d\n"
         "      Root Digest:           "
-        "e811611467dcd6e8dc4324e45f706c2bdd51db67\n",
+        "e811611467dcd6e8dc4324e45f706c2bdd51db67\n"
+        "      Flags:                 0\n",
         InfoImage(ext_vbmeta_path));
   }
 
@@ -1080,7 +1213,8 @@
                                  "      Partition Name:        foobar\n"
                                  "      Salt:                  d00df00d\n"
                                  "      Root Digest:           "
-                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n",
+                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n"
+                                 "      Flags:                 0\n",
                                  sparse_image ? " (Sparse)" : ""),
               InfoImage(rootfs_path));
   }
@@ -1289,6 +1423,158 @@
                  partition_size);
 }
 
+TEST_F(AvbToolTest, AddHashtreeFooterWithPersistentDigest) {
+  size_t partition_size = 10 * 1024 * 1024;
+  base::FilePath path = GenerateImage("digest_location", partition_size / 2);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--use_persistent_digest",
+                 path.value().c_str(),
+                 (int)partition_size);
+  // There are two important bits here specific to --use_persistent_digest:
+  //   Minimum libavb version = 1.1
+  //   Hashtree descriptor -> Root Digest = (empty)
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               10485760 bytes\n"
+      "Original image size:      5242880 bytes\n"
+      "VBMeta offset:            5337088\n"
+      "VBMeta size:              1344 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.1\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          768 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hashtree descriptor:\n"
+      "      Version of dm-verity:  1\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Tree Offset:           5242880\n"
+      "      Tree Size:             45056 bytes\n"
+      "      Data Block Size:       4096 bytes\n"
+      "      Hash Block Size:       4096 bytes\n"
+      "      FEC num roots:         2\n"
+      "      FEC offset:            5287936\n"
+      "      FEC size:              49152 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Root Digest:           \n"
+      "      Flags:                 0\n",
+      InfoImage(path));
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterWithNoAB) {
+  size_t partition_size = 10 * 1024 * 1024;
+  base::FilePath path = GenerateImage("digest_location", partition_size / 2);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--do_not_use_ab",
+                 path.value().c_str(),
+                 (int)partition_size);
+  // There are two important bits here we're expecting with --do_not_use_ab:
+  //   Minimum libavb version = 1.1
+  //   Hashtree descriptor -> Flags = 1
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               10485760 bytes\n"
+      "Original image size:      5242880 bytes\n"
+      "VBMeta offset:            5337088\n"
+      "VBMeta size:              1344 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.1\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          768 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hashtree descriptor:\n"
+      "      Version of dm-verity:  1\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Tree Offset:           5242880\n"
+      "      Tree Size:             45056 bytes\n"
+      "      Data Block Size:       4096 bytes\n"
+      "      Hash Block Size:       4096 bytes\n"
+      "      FEC num roots:         2\n"
+      "      FEC offset:            5287936\n"
+      "      FEC size:              49152 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Root Digest:           "
+      "d0e31526f5a3f8e3f59acf726bd31ae7861ee78f9baa9195356bf479c6f9119d\n"
+      "      Flags:                 1\n",
+      InfoImage(path));
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterWithPersistentDigestAndNoAB) {
+  size_t partition_size = 10 * 1024 * 1024;
+  base::FilePath path = GenerateImage("digest_location", partition_size / 2);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem "
+                 "--internal_release_string \"\" "
+                 "--use_persistent_digest --do_not_use_ab",
+                 path.value().c_str(),
+                 (int)partition_size);
+  // There are three important bits specific to these flags:
+  //   Minimum libavb version = 1.1
+  //   Hashtree descriptor -> Root Digest = (empty)
+  //   Hashtree descriptor -> Flags = 1
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               10485760 bytes\n"
+      "Original image size:      5242880 bytes\n"
+      "VBMeta offset:            5337088\n"
+      "VBMeta size:              1344 bytes\n"
+      "--\n"
+      "Minimum libavb version:   1.1\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          768 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Release String:           ''\n"
+      "Descriptors:\n"
+      "    Hashtree descriptor:\n"
+      "      Version of dm-verity:  1\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Tree Offset:           5242880\n"
+      "      Tree Size:             45056 bytes\n"
+      "      Data Block Size:       4096 bytes\n"
+      "      Hash Block Size:       4096 bytes\n"
+      "      FEC num roots:         2\n"
+      "      FEC offset:            5287936\n"
+      "      FEC size:              49152 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Root Digest:           \n"
+      "      Flags:                 1\n",
+      InfoImage(path));
+}
+
 TEST_F(AvbToolTest, KernelCmdlineDescriptor) {
   base::FilePath vbmeta_path =
       testdir_.Append("vbmeta_kernel_cmdline_desc.bin");
@@ -1374,9 +1660,11 @@
 
   base::FilePath ext_vbmeta_path = testdir_.Append("ext_vbmeta.bin");
   base::FilePath image_path = testdir_.Append("kernel.bin");
-  EXPECT_EQ(image_size, static_cast<const size_t>(
-      base::WriteFile(image_path, reinterpret_cast<const char*>(image.data()),
-                      image.size())));
+  EXPECT_EQ(image_size,
+            static_cast<const size_t>(
+                base::WriteFile(image_path,
+                                reinterpret_cast<const char*>(image.data()),
+                                image.size())));
   EXPECT_COMMAND(0,
                  "./avbtool add_hash_footer --salt d00df00d "
                  "--hash_algorithm sha256 --image %s "
@@ -1591,7 +1879,7 @@
       "Algorithm:                SHA256_RSA2048\n"
       "Rollback Index:           0\n"
       "Flags:                    0\n"
-      "Release String:           'avbtool 1.0.0 '\n"
+      "Release String:           'avbtool 1.1.0 '\n"
       "Descriptors:\n"
       "    Kernel Cmdline descriptor:\n"
       "      Flags:                 0\n"
@@ -1946,42 +2234,116 @@
                  pk8192_path.value().c_str());
 }
 
-TEST_F(AvbToolTest, PrintRequiredLibavbVersion) {
-  base::FilePath output_path = testdir_.Append("versions.txt");
+class AvbToolTest_PrintRequiredVersion : public AvbToolTest {
+ protected:
+  const char* kOutputFile = "versions.txt";
 
-  const size_t boot_partition_size = 16 * 1024 * 1024;
-  EXPECT_COMMAND(0,
-                 "./avbtool add_hash_footer"
-                 " --rollback_index 0"
-                 " --partition_name boot"
-                 " --partition_size %zd"
-                 " --salt deadbeef"
-                 " --internal_release_string \"\""
-                 " --print_required_libavb_version >> %s",
-                 boot_partition_size,
-                 output_path.value().c_str());
+  void PrintWithAddHashFooter(int target_required_minor_version) {
+    std::string extra_args;
+    if (target_required_minor_version == 1) {
+      // The --do_not_use_ab option will require 1.1.
+      extra_args = "--do_not_use_ab";
+    }
+    const size_t boot_partition_size = 16 * 1024 * 1024;
+    base::FilePath output_path = testdir_.Append(kOutputFile);
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hash_footer"
+                   " --rollback_index 0"
+                   " --partition_name boot"
+                   " --partition_size %zd"
+                   " --salt deadbeef"
+                   " --internal_release_string \"\""
+                   " %s"
+                   " --print_required_libavb_version > %s",
+                   boot_partition_size,
+                   extra_args.c_str(),
+                   output_path.value().c_str());
+    CheckVersion(target_required_minor_version);
+  }
 
-  const size_t system_partition_size = 10 * 1024 * 1024;
-  EXPECT_COMMAND(0,
-                 "./avbtool add_hashtree_footer --salt d00df00d "
-                 "--partition_size %zd --partition_name system "
-                 "--internal_release_string \"\""
-                 " --print_required_libavb_version >> %s",
-                 system_partition_size,
-                 output_path.value().c_str());
+  void PrintWithAddHashtreeFooter(int target_required_minor_version) {
+    std::string extra_args;
+    if (target_required_minor_version == 1) {
+      // The --do_not_use_ab option will require 1.1.
+      extra_args = "--do_not_use_ab";
+    }
+    const size_t system_partition_size = 10 * 1024 * 1024;
+    base::FilePath output_path = testdir_.Append(kOutputFile);
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hashtree_footer --salt d00df00d "
+                   "--partition_size %zd --partition_name system "
+                   "--internal_release_string \"\""
+                   " %s"
+                   " --print_required_libavb_version > %s",
+                   system_partition_size,
+                   extra_args.c_str(),
+                   output_path.value().c_str());
+    CheckVersion(target_required_minor_version);
+  }
 
-  EXPECT_COMMAND(0,
-                 "./avbtool make_vbmeta_image "
-                 "--algorithm SHA256_RSA2048 "
-                 "--key test/data/testkey_rsa2048.pem "
-                 "--internal_release_string \"\""
-                 " --print_required_libavb_version >> %s",
-                 output_path.value().c_str());
+  void PrintWithMakeVbmetaImage(int target_required_minor_version) {
+    std::string extra_args;
+    if (target_required_minor_version == 1) {
+      // An included descriptor that requires 1.1 will require 1.1 for vbmeta.
+      const size_t boot_partition_size = 16 * 1024 * 1024;
+      base::FilePath image_path = GenerateImage("test_print_version", 1024);
+      EXPECT_COMMAND(0,
+                     "./avbtool add_hash_footer --salt d00df00d "
+                     "--hash_algorithm sha256 --image %s "
+                     "--partition_size %d --partition_name foobar "
+                     "--algorithm SHA256_RSA2048 "
+                     "--key test/data/testkey_rsa2048.pem "
+                     "--internal_release_string \"\" "
+                     "--do_not_use_ab",
+                     image_path.value().c_str(),
+                     (int)boot_partition_size);
+      extra_args = base::StringPrintf("--include_descriptors_from_image %s",
+                                      image_path.value().c_str());
+    }
+    base::FilePath output_path = testdir_.Append(kOutputFile);
+    EXPECT_COMMAND(0,
+                   "./avbtool make_vbmeta_image "
+                   "--algorithm SHA256_RSA2048 "
+                   "--key test/data/testkey_rsa2048.pem "
+                   "--internal_release_string \"\""
+                   " %s"
+                   " --print_required_libavb_version > %s",
+                   extra_args.c_str(),
+                   output_path.value().c_str());
+    CheckVersion(target_required_minor_version);
+  }
 
-  // Check that "1.0\n" was printed for all three invocations.
-  std::string versions;
-  ASSERT_TRUE(base::ReadFileToString(output_path, &versions));
-  EXPECT_EQ(versions, std::string("1.0\n1.0\n1.0\n"));
+  void CheckVersion(int expected_required_minor_version) {
+    base::FilePath output_path = testdir_.Append(kOutputFile);
+    std::string output;
+    ASSERT_TRUE(base::ReadFileToString(output_path, &output));
+    EXPECT_EQ(output,
+              base::StringPrintf("1.%d\n", expected_required_minor_version));
+  }
+};
+
+TEST_F(AvbToolTest_PrintRequiredVersion, HashFooter_1_0) {
+  PrintWithAddHashFooter(0);
+}
+
+TEST_F(AvbToolTest_PrintRequiredVersion, HashFooter_1_1) {
+  PrintWithAddHashFooter(1);
+}
+
+TEST_F(AvbToolTest_PrintRequiredVersion, HashtreeFooter_1_0) {
+  PrintWithAddHashtreeFooter(0);
+}
+
+TEST_F(AvbToolTest_PrintRequiredVersion, HashtreeFooter_1_1) {
+  PrintWithAddHashtreeFooter(1);
+}
+
+TEST_F(AvbToolTest_PrintRequiredVersion, Vbmeta_1_0) {
+  PrintWithMakeVbmetaImage(0);
+}
+
+TEST_F(AvbToolTest_PrintRequiredVersion, Vbmeta_1_1) {
+  PrintWithMakeVbmetaImage(1);
 }
 
 TEST_F(AvbToolTest, MakeAtxPikCertificate) {
diff --git a/test/fake_avb_ops.cc b/test/fake_avb_ops.cc
index 9d3963a..6ee128a 100644
--- a/test/fake_avb_ops.cc
+++ b/test/fake_avb_ops.cc
@@ -56,9 +56,7 @@
 
   int64_t file_size;
   if (!base::GetFileSize(path, &file_size)) {
-    fprintf(stderr,
-            "Error getting size of file '%s'\n",
-            path.value().c_str());
+    fprintf(stderr, "Error getting size of file '%s'\n", path.value().c_str());
     return false;
   }
 
@@ -150,7 +148,9 @@
 }
 
 AvbIOResult FakeAvbOps::get_preloaded_partition(
-    const char* partition, size_t num_bytes, uint8_t** out_pointer,
+    const char* partition,
+    size_t num_bytes,
+    uint8_t** out_pointer,
     size_t* out_num_bytes_preloaded) {
   std::map<std::string, uint8_t*>::iterator it =
       preloaded_partitions_.find(std::string(partition));
@@ -161,8 +161,7 @@
   }
 
   uint64_t size;
-  AvbIOResult result = get_size_of_partition(
-      avb_ops(), partition, &size);
+  AvbIOResult result = get_size_of_partition(avb_ops(), partition, &size);
   if (result != AVB_IO_RESULT_OK) {
     return result;
   }
@@ -311,6 +310,33 @@
   return AVB_IO_RESULT_OK;
 }
 
+AvbIOResult FakeAvbOps::read_persistent_value(const char* name,
+                                              size_t buffer_size,
+                                              uint8_t* out_buffer,
+                                              size_t* out_num_bytes_read) {
+  if (out_buffer == NULL && buffer_size > 0) {
+    return AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE;
+  }
+  if (stored_values_.count(name) == 0) {
+    return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
+  }
+  if (stored_values_[name].size() > buffer_size) {
+    *out_num_bytes_read = stored_values_[name].size();
+    return AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE;
+  }
+  memcpy(out_buffer, stored_values_[name].data(), stored_values_[name].size());
+  *out_num_bytes_read = stored_values_[name].size();
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::write_persistent_value(const char* name,
+                                               size_t value_size,
+                                               const uint8_t* value) {
+  stored_values_[name] =
+      std::string(reinterpret_cast<const char*>(value), value_size);
+  return AVB_IO_RESULT_OK;
+}
+
 AvbIOResult FakeAvbOps::read_permanent_attributes(
     AvbAtxPermanentAttributes* attributes) {
   *attributes = permanent_attributes_;
@@ -347,15 +373,16 @@
       ->read_from_partition(partition, offset, num_bytes, buffer, out_num_read);
 }
 
-static AvbIOResult my_ops_get_preloaded_partition(AvbOps* ops,
-                                           const char* partition,
-                                           size_t num_bytes,
-                                           uint8_t** out_pointer,
-                                           size_t* out_num_bytes_preloaded) {
+static AvbIOResult my_ops_get_preloaded_partition(
+    AvbOps* ops,
+    const char* partition,
+    size_t num_bytes,
+    uint8_t** out_pointer,
+    size_t* out_num_bytes_preloaded) {
   return FakeAvbOps::GetInstanceFromAvbOps(ops)
       ->delegate()
-      ->get_preloaded_partition(partition, num_bytes, out_pointer,
-                                out_num_bytes_preloaded);
+      ->get_preloaded_partition(
+          partition, num_bytes, out_pointer, out_num_bytes_preloaded);
 }
 
 static AvbIOResult my_ops_write_to_partition(AvbOps* ops,
@@ -424,6 +451,26 @@
       ->get_size_of_partition(ops, partition, out_size);
 }
 
+static AvbIOResult my_ops_read_persistent_value(AvbOps* ops,
+                                                const char* name,
+                                                size_t buffer_size,
+                                                uint8_t* out_buffer,
+                                                size_t* out_num_bytes_read) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->read_persistent_value(
+          name, buffer_size, out_buffer, out_num_bytes_read);
+}
+
+static AvbIOResult my_ops_write_persistent_value(AvbOps* ops,
+                                                 const char* name,
+                                                 size_t value_size,
+                                                 const uint8_t* value) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->write_persistent_value(name, value_size, value);
+}
+
 static AvbIOResult my_ops_read_permanent_attributes(
     AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes) {
   return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops)
@@ -459,6 +506,8 @@
   avb_ops_.read_is_device_unlocked = my_ops_read_is_device_unlocked;
   avb_ops_.get_unique_guid_for_partition = my_ops_get_unique_guid_for_partition;
   avb_ops_.get_size_of_partition = my_ops_get_size_of_partition;
+  avb_ops_.read_persistent_value = my_ops_read_persistent_value;
+  avb_ops_.write_persistent_value = my_ops_write_persistent_value;
 
   // Just use the built-in A/B metadata read/write routines.
   avb_ab_ops_.ops = &avb_ops_;
@@ -476,8 +525,8 @@
 
 FakeAvbOps::~FakeAvbOps() {
   std::map<std::string, uint8_t*>::iterator it;
-  for (it = preloaded_partitions_.begin();
-       it != preloaded_partitions_.end(); it++) {
+  for (it = preloaded_partitions_.begin(); it != preloaded_partitions_.end();
+       it++) {
     free(it->second);
   }
 }
diff --git a/test/fake_avb_ops.h b/test/fake_avb_ops.h
index 93c7ee9..769f3cc 100644
--- a/test/fake_avb_ops.h
+++ b/test/fake_avb_ops.h
@@ -86,6 +86,15 @@
                                             const char* partition,
                                             uint64_t* out_size) = 0;
 
+  virtual AvbIOResult read_persistent_value(const char* name,
+                                            size_t buffer_size,
+                                            uint8_t* out_buffer,
+                                            size_t* out_num_bytes_read) = 0;
+
+  virtual AvbIOResult write_persistent_value(const char* name,
+                                             size_t value_size,
+                                             const uint8_t* value) = 0;
+
   virtual AvbIOResult read_permanent_attributes(
       AvbAtxPermanentAttributes* attributes) = 0;
 
@@ -222,6 +231,15 @@
                                     const char* partition,
                                     uint64_t* out_size) override;
 
+  AvbIOResult read_persistent_value(const char* name,
+                                    size_t buffer_size,
+                                    uint8_t* out_buffer,
+                                    size_t* out_num_bytes_read) override;
+
+  AvbIOResult write_persistent_value(const char* name,
+                                     size_t value_size,
+                                     const uint8_t* value) override;
+
   AvbIOResult read_permanent_attributes(
       AvbAtxPermanentAttributes* attributes) override;
 
@@ -253,6 +271,8 @@
 
   std::set<std::string> partition_names_read_from_;
   std::map<std::string, uint8_t*> preloaded_partitions_;
+
+  std::map<std::string, std::string> stored_values_;
 };
 
 // A delegate implementation that calls FakeAvbOps by default.
@@ -329,6 +349,20 @@
     return ops_.get_size_of_partition(ops, partition, out_size);
   }
 
+  AvbIOResult read_persistent_value(const char* name,
+                                    size_t buffer_size,
+                                    uint8_t* out_buffer,
+                                    size_t* out_num_bytes_read) override {
+    return ops_.read_persistent_value(
+        name, buffer_size, out_buffer, out_num_bytes_read);
+  }
+
+  AvbIOResult write_persistent_value(const char* name,
+                                     size_t value_size,
+                                     const uint8_t* value) override {
+    return ops_.write_persistent_value(name, value_size, value);
+  }
+
   AvbIOResult read_permanent_attributes(
       AvbAtxPermanentAttributes* attributes) override {
     return ops_.read_permanent_attributes(attributes);