Implement support for on-device persistent digests.

This feature allows digests from on-device persistent storage to be used
in place of digests embedded in descriptors. This allows verification of
partitions which hold per-device configuration data set during a factory
or provisioning stage and expected to remain unchanged from that point
forward.

Support is added for both 'hash' and 'hashtree' descriptors. In the case
of hashtree descriptors, the verity root digest needs to be added to the
kernel command line so this can be configured later without access to
AVB persistent storage. This is accomplished by supporting substitutions
of the form $(AVB_<part_name>_ROOT_DIGEST) where <part_name> is the
uppercase partition name. For example, if the partition name was
'factory' the kernel command line descriptor would hold:

 "androidboot.vbmeta.root_digest.factory=$(AVB_FACTORY_ROOT_DIGEST)"

The persistent value ops are designed to be reusable. Persistent values
are expected to be tamper-proof, similar to rollback indexes, and are
not expected to be available outside of the boot code running AVB.

Using persistent digests also requires that the partition not use A/B.
A new flag has been added to avbtool to support this as well as a
'flags' field in hash and hashtree descriptors.

This CL bumps the AVB version to 1.1 and any use of persistent digests
(or the --do_not_use_ab flag) will set the minimum libavb version in
vbmeta to 1.1. If these features are not used, the minimum remains 1.0.

Bug: 73020477
Test: Unit

Change-Id: Iffef31b232492bc8700ab8496c5da2ccfb49be44
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);