Error correction: Append codes to verified partitions

Append error-correcting codes to verified partitions provided that
PRODUCT_SUPPORTS_VERITY_FEC is true.

This moves verity metadata to be after the hash tree, and requires
matching changes from
  Ide48f581bbba77aed6132f77b309db71630d81ed

Bug: 21893453
Change-Id: I6945cbab99e214566a1f9d3702333f2dbbc35816
diff --git a/core/Makefile b/core/Makefile
index b1b3d13..0d24719 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -724,6 +724,9 @@
 
 ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY))
 INTERNAL_USERIMAGES_DEPS += $(BUILD_VERITY_TREE) $(APPEND2SIMG) $(VERITY_SIGNER)
+ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY_FEC))
+INTERNAL_USERIMAGES_DEPS += $(FEC)
+endif
 endif
 
 SELINUX_FC := $(TARGET_ROOT_OUT)/file_contexts.bin
@@ -754,6 +757,7 @@
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity_key=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity_signer_cmd=$(notdir $(VERITY_SIGNER))" >> $(1))
+$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY_FEC),$(hide) echo "verity_fec=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY_FEC)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION),$(hide) echo "system_verity_block_device=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION),$(hide) echo "vendor_verity_block_device=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION)" >> $(1))
 $(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT)" >> $(1))
@@ -1363,7 +1367,8 @@
   $(HOST_OUT_EXECUTABLES)/verity_signer \
   $(HOST_OUT_EXECUTABLES)/append2simg \
   $(HOST_OUT_EXECUTABLES)/img2simg \
-  $(HOST_OUT_EXECUTABLES)/boot_signer
+  $(HOST_OUT_EXECUTABLES)/boot_signer \
+  $(HOST_OUT_EXECUTABLES)/fec
 
 # Shared libraries.
 OTATOOLS += \
diff --git a/core/config.mk b/core/config.mk
index 1db3d7a..2b7cb7f 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -515,6 +515,7 @@
 BOOT_SIGNER := $(HOST_OUT_EXECUTABLES)/boot_signer
 FUTILITY := prebuilts/misc/$(BUILD_OS)-$(HOST_PREBUILT_ARCH)/futility/futility
 VBOOT_SIGNER := prebuilts/misc/scripts/vboot_signer/vboot_signer.sh
+FEC := $(HOST_OUT_EXECUTABLES)/fec
 
 # ACP is always for the build OS, not for the host OS
 ACP := $(BUILD_OUT_EXECUTABLES)/acp$(BUILD_EXECUTABLE_SUFFIX)
diff --git a/core/product.mk b/core/product.mk
index 2c846aa..f242e82 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -100,6 +100,7 @@
     PRODUCT_SUPPORTS_BOOT_SIGNER \
     PRODUCT_SUPPORTS_VBOOT \
     PRODUCT_SUPPORTS_VERITY \
+    PRODUCT_SUPPORTS_VERITY_FEC \
     PRODUCT_OEM_PROPERTIES \
     PRODUCT_SYSTEM_PROPERTY_BLACKLIST \
     PRODUCT_SYSTEM_SERVER_JARS \
diff --git a/target/product/verity.mk b/target/product/verity.mk
index 6676ffe..0badb9f 100644
--- a/target/product/verity.mk
+++ b/target/product/verity.mk
@@ -21,6 +21,7 @@
 ifneq (,$(user_variant))
     PRODUCT_SUPPORTS_BOOT_SIGNER := true
     PRODUCT_SUPPORTS_VERITY := true
+    PRODUCT_SUPPORTS_VERITY_FEC := true
 
     # The dev key is used to sign boot and recovery images, and the verity
     # metadata table. Actual product deliverables will be re-signed by hand.
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 470a108..cd750e8 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -33,6 +33,7 @@
 OPTIONS = common.OPTIONS
 
 FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
+BLOCK_SIZE = 4096
 
 def RunCommand(cmd):
   """Echo and run the given command.
@@ -48,6 +49,14 @@
   print "%s" % (output.rstrip(),)
   return (output, p.returncode)
 
+def GetVerityFECSize(partition_size):
+  cmd = "fec -s %d" % partition_size
+  status, output = commands.getstatusoutput(cmd)
+  if status:
+    print output
+    return False, 0
+  return True, int(output)
+
 def GetVerityTreeSize(partition_size):
   cmd = "build_verity_tree -s %d"
   cmd %= partition_size
@@ -67,7 +76,22 @@
     return False, 0
   return True, int(output)
 
-def AdjustPartitionSizeForVerity(partition_size):
+def GetVeritySize(partition_size, fec_supported):
+  success, verity_tree_size = GetVerityTreeSize(partition_size)
+  if not success:
+    return 0
+  success, verity_metadata_size = GetVerityMetadataSize(partition_size)
+  if not success:
+    return 0
+  verity_size = verity_tree_size + verity_metadata_size
+  if fec_supported:
+    success, fec_size = GetVerityFECSize(partition_size + verity_size)
+    if not success:
+      return 0
+    return verity_size + fec_size
+  return verity_size
+
+def AdjustPartitionSizeForVerity(partition_size, fec_supported):
   """Modifies the provided partition size to account for the verity metadata.
 
   This information is used to size the created image appropriately.
@@ -76,13 +100,43 @@
   Returns:
     The size of the partition adjusted for verity metadata.
   """
-  success, verity_tree_size = GetVerityTreeSize(partition_size)
-  if not success:
-    return 0
-  success, verity_metadata_size = GetVerityMetadataSize(partition_size)
-  if not success:
-    return 0
-  return partition_size - verity_tree_size - verity_metadata_size
+  key = "%d %d" % (partition_size, fec_supported)
+  if key in AdjustPartitionSizeForVerity.results:
+    return AdjustPartitionSizeForVerity.results[key]
+
+  hi = partition_size
+  if hi % BLOCK_SIZE != 0:
+    hi = (hi // BLOCK_SIZE) * BLOCK_SIZE
+
+  # verity tree and fec sizes depend on the partition size, which
+  # means this estimate is always going to be unnecessarily small
+  lo = partition_size - GetVeritySize(hi, fec_supported)
+  result = lo
+
+  # do a binary search for the optimal size
+  while lo < hi:
+    i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
+    size = i + GetVeritySize(i, fec_supported)
+    if size <= partition_size:
+      if result < i:
+        result = i
+      lo = i + BLOCK_SIZE
+    else:
+      hi = i
+
+  AdjustPartitionSizeForVerity.results[key] = result
+  return result
+
+AdjustPartitionSizeForVerity.results = {}
+
+def BuildVerityFEC(sparse_image_path, verity_fec_path, prop_dict):
+  cmd = "fec -e %s %s" % (sparse_image_path, verity_fec_path)
+  print cmd
+  status, output = commands.getstatusoutput(cmd)
+  if status:
+    print "Could not build FEC data! Error: %s" % output
+    return False
+  return True
 
 def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict):
   cmd = "build_verity_tree -A %s %s %s" % (
@@ -130,12 +184,12 @@
 
 def BuildVerifiedImage(data_image_path, verity_image_path,
                        verity_metadata_path):
-  if not Append2Simg(data_image_path, verity_metadata_path,
-                     "Could not append verity metadata!"):
-    return False
   if not Append2Simg(data_image_path, verity_image_path,
                      "Could not append verity tree!"):
     return False
+  if not Append2Simg(data_image_path, verity_metadata_path,
+                     "Could not append verity metadata!"):
+    return False
   return True
 
 def UnsparseImage(sparse_image_path, replace=True):
@@ -154,7 +208,7 @@
     return False, None
   return True, unsparse_image_path
 
-def MakeVerityEnabledImage(out_file, prop_dict):
+def MakeVerityEnabledImage(out_file, fec_supported, prop_dict):
   """Creates an image that is verifiable using dm-verity.
 
   Args:
@@ -180,6 +234,7 @@
   # get partial image paths
   verity_image_path = os.path.join(tempdir_name, "verity.img")
   verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
+  verity_fec_path = os.path.join(tempdir_name, "verity_fec.img")
 
   # build the verity tree and get the root hash and salt
   if not BuildVerityTree(out_file, verity_image_path, prop_dict):
@@ -201,6 +256,16 @@
     shutil.rmtree(tempdir_name, ignore_errors=True)
     return False
 
+  if fec_supported:
+    # build FEC for the entire partition, including metadata
+    if not BuildVerityFEC(out_file, verity_fec_path, prop_dict):
+      shutil.rmtree(tempdir_name, ignore_errors=True)
+      return False
+
+    if not Append2Simg(out_file, verity_fec_path, "Could not append FEC!"):
+      shutil.rmtree(tempdir_name, ignore_errors=True)
+      return False
+
   shutil.rmtree(tempdir_name, ignore_errors=True)
   return True
 
@@ -248,12 +313,14 @@
 
   is_verity_partition = "verity_block_device" in prop_dict
   verity_supported = prop_dict.get("verity") == "true"
+  verity_fec_supported = prop_dict.get("verity_fec") == "true"
+
   # Adjust the partition size to make room for the hashes if this is to be
   # verified.
   if verity_supported and is_verity_partition and fs_spans_partition:
     partition_size = int(prop_dict.get("partition_size"))
-
-    adjusted_size = AdjustPartitionSizeForVerity(partition_size)
+    adjusted_size = AdjustPartitionSizeForVerity(partition_size,
+                                                 verity_fec_supported)
     if not adjusted_size:
       return False
     prop_dict["partition_size"] = str(adjusted_size)
@@ -366,7 +433,7 @@
             "%d" % (mount_point, image_size, partition_size))
       return False
     if verity_supported and is_verity_partition:
-      if 2 * image_size - AdjustPartitionSizeForVerity(image_size) > partition_size:
+      if 2 * image_size - AdjustPartitionSizeForVerity(image_size, verity_fec_supported) > partition_size:
         print "Error: No more room on %s to fit verity data" % mount_point
         return False
     prop_dict["original_partition_size"] = prop_dict["partition_size"]
@@ -374,7 +441,7 @@
 
   # create the verified image if this is to be verified
   if verity_supported and is_verity_partition:
-    if not MakeVerityEnabledImage(out_file, prop_dict):
+    if not MakeVerityEnabledImage(out_file, verity_fec_supported, prop_dict):
       return False
 
   if run_fsck and prop_dict.get("skip_fsck") != "true":
@@ -416,7 +483,8 @@
       "skip_fsck",
       "verity",
       "verity_key",
-      "verity_signer_cmd"
+      "verity_signer_cmd",
+      "verity_fec"
       )
   for p in common_props:
     copy_prop(p, p)