Merge "Detect downgrade by checking build time for all partitions" into main am: fb5630b393 am: fb96253429 am: 6a83277968

Original change: https://android-review.googlesource.com/c/platform/build/+/2878035

Change-Id: Iba7d4b196d92f779c9aaeafb182aa7a9c8ec8203
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 4a5facd..b65764b 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1038,7 +1038,11 @@
   partition_timestamps_flags = []
   # Enforce a max timestamp this payload can be applied on top of.
   if OPTIONS.downgrade:
-    max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
+    # When generating ota between merged target-files, partition build date can
+    # decrease in target, at the same time as ro.build.date.utc increases,
+    # so always pick largest value.
+    max_timestamp = max(source_info.GetBuildProp("ro.build.date.utc"),
+        str(metadata.postcondition.timestamp))
     partition_timestamps_flags = GeneratePartitionTimestampFlagsDowngrade(
         metadata.precondition.partition_state,
         metadata.postcondition.partition_state
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index ddd2d36..048a497 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -364,26 +364,66 @@
   # Only incremental OTAs are allowed to reach here.
   assert OPTIONS.incremental_source is not None
 
+  # used for logging upon errors
+  log_downgrades = []
+  log_upgrades = []
+
   post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
   pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
-  is_downgrade = int(post_timestamp) < int(pre_timestamp)
+  if int(post_timestamp) < int(pre_timestamp):
+    logger.info(f"ro.build.date.utc pre timestamp: {pre_timestamp}, "
+                f"post timestamp: {post_timestamp}. Downgrade detected.")
+    log_downgrades.append(f"ro.build.date.utc pre: {pre_timestamp} post: {post_timestamp}")
+  else:
+    logger.info(f"ro.build.date.utc pre timestamp: {pre_timestamp}, "
+                f"post timestamp: {post_timestamp}.")
+    log_upgrades.append(f"ro.build.date.utc pre: {pre_timestamp} post: {post_timestamp}")
+
+  # When merging system and vendor target files, it is not enough
+  # to check ro.build.date.utc, the timestamp for each partition must
+  # be checked.
+  if source_info.is_ab:
+    ab_partitions = set(source_info.get("ab_partitions"))
+    for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
+
+      partition_prop = source_info.get('{}.build.prop'.format(partition))
+      # Skip if the partition is missing, or it doesn't have a build.prop
+      if not partition_prop or not partition_prop.build_props:
+        continue
+      partition_prop = target_info.get('{}.build.prop'.format(partition))
+      # Skip if the partition is missing, or it doesn't have a build.prop
+      if not partition_prop or not partition_prop.build_props:
+        continue
+
+      post_timestamp = target_info.GetPartitionBuildProp(
+        'ro.build.date.utc', partition)
+      pre_timestamp = source_info.GetPartitionBuildProp(
+        'ro.build.date.utc', partition)
+      if int(post_timestamp) < int(pre_timestamp):
+        logger.info(f"Partition {partition} pre timestamp: {pre_timestamp}, "
+                    f"post time: {post_timestamp}. Downgrade detected.")
+        log_downgrades.append(f"{partition} pre: {pre_timestamp} post: {post_timestamp}")
+      else:
+        logger.info(f"Partition {partition} pre timestamp: {pre_timestamp}, "
+                    f"post timestamp: {post_timestamp}.")
+        log_upgrades.append(f"{partition} pre: {pre_timestamp} post: {post_timestamp}")
 
   if OPTIONS.spl_downgrade:
     metadata_proto.spl_downgrade = True
 
   if OPTIONS.downgrade:
-    if not is_downgrade:
+    if len(log_downgrades) == 0:
       raise RuntimeError(
           "--downgrade or --override_timestamp specified but no downgrade "
-          "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
+          "detected. Current values for ro.build.date.utc: " + ', '.join(log_upgrades))
     metadata_proto.downgrade = True
   else:
-    if is_downgrade:
+    if len(log_downgrades) != 0:
       raise RuntimeError(
-          "Downgrade detected based on timestamp check: pre: %s, post: %s. "
+          "Downgrade detected based on timestamp check in ro.build.date.utc. "
           "Need to specify --override_timestamp OR --downgrade to allow "
-          "building the incremental." % (pre_timestamp, post_timestamp))
-
+          "building the incremental. Downgrades detected for: "
+          + ', '.join(log_downgrades))
 
 def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
   """Returns a set of build info objects that may exist during runtime."""
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index ad0f7a8..d1e76b9 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -163,6 +163,20 @@
       'oem_fingerprint_properties': 'ro.product.device ro.product.brand',
   }
 
+  TEST_TARGET_VENDOR_INFO_DICT = common.PartitionBuildProps.FromDictionary(
+    'vendor', {
+      'ro.vendor.build.date.utc' : '87654321',
+      'ro.product.vendor.device':'vendor-device',
+      'ro.vendor.build.fingerprint': 'build-fingerprint-vendor'}
+  )
+
+  TEST_SOURCE_VENDOR_INFO_DICT = common.PartitionBuildProps.FromDictionary(
+    'vendor', {
+      'ro.vendor.build.date.utc' : '12345678',
+      'ro.product.vendor.device':'vendor-device',
+      'ro.vendor.build.fingerprint': 'build-fingerprint-vendor'}
+  )
+
   def setUp(self):
     self.testdata_dir = test_utils.get_testdata_dir()
     self.assertTrue(os.path.exists(self.testdata_dir))
@@ -351,6 +365,13 @@
          source_info['build.prop'].build_props['ro.build.date.utc'],
          target_info['build.prop'].build_props['ro.build.date.utc'])
 
+  @staticmethod
+  def _test_GetPackageMetadata_swapVendorBuildTimestamps(target_info, source_info):
+    (target_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'],
+     source_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc']) = (
+         source_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'],
+         target_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'])
+
   def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
     target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
     source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
@@ -363,6 +384,24 @@
     self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info,
                       source_info)
 
+  def test_GetPackageMetadata_unintentionalVendorDowngradeDetected(self):
+    target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
+    target_info_dict['ab_update'] = 'true'
+    target_info_dict['ab_partitions'] = ['vendor']
+    target_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_TARGET_VENDOR_INFO_DICT)
+    source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
+    source_info_dict['ab_update'] = 'true'
+    source_info_dict['ab_partitions'] = ['vendor']
+    source_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_SOURCE_VENDOR_INFO_DICT)
+    self._test_GetPackageMetadata_swapVendorBuildTimestamps(
+        target_info_dict, source_info_dict)
+
+    target_info = common.BuildInfo(target_info_dict, None)
+    source_info = common.BuildInfo(source_info_dict, None)
+    common.OPTIONS.incremental_source = ''
+    self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info,
+                      source_info)
+
   def test_GetPackageMetadata_downgrade(self):
     target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
     source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
@@ -397,6 +436,55 @@
         },
         metadata)
 
+  def test_GetPackageMetadata_vendorDowngrade(self):
+    target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
+    target_info_dict['ab_update'] = 'true'
+    target_info_dict['ab_partitions'] = ['vendor']
+    target_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_TARGET_VENDOR_INFO_DICT)
+    source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
+    source_info_dict['ab_update'] = 'true'
+    source_info_dict['ab_partitions'] = ['vendor']
+    source_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_SOURCE_VENDOR_INFO_DICT)
+    self._test_GetPackageMetadata_swapVendorBuildTimestamps(
+        target_info_dict, source_info_dict)
+
+    target_info = common.BuildInfo(target_info_dict, None)
+    source_info = common.BuildInfo(source_info_dict, None)
+    common.OPTIONS.incremental_source = ''
+    common.OPTIONS.downgrade = True
+    common.OPTIONS.wipe_user_data = True
+    common.OPTIONS.spl_downgrade = True
+    metadata = self.GetLegacyOtaMetadata(target_info, source_info)
+    # Reset spl_downgrade so other tests are unaffected
+    common.OPTIONS.spl_downgrade = False
+
+    self.assertDictEqual(
+        {
+            'ota-downgrade': 'yes',
+            'ota-type': 'AB',
+            'ota-required-cache': '0',
+            'ota-wipe': 'yes',
+            'post-build': 'build-fingerprint-target',
+            'post-build-incremental': 'build-version-incremental-target',
+            'post-sdk-level': '27',
+            'post-security-patch-level': '2017-12-01',
+            'post-timestamp': '1500000000',
+            'pre-device': 'product-device',
+            'pre-build': 'build-fingerprint-source',
+            'pre-build-incremental': 'build-version-incremental-source',
+            'spl-downgrade': 'yes',
+        },
+        metadata)
+
+    post_build = GetPackageMetadata(target_info, source_info).postcondition
+    self.assertEqual('vendor', post_build.partition_state[0].partition_name)
+    self.assertEqual('12345678', post_build.partition_state[0].version)
+
+    pre_build = GetPackageMetadata(target_info, source_info).precondition
+    self.assertEqual('vendor', pre_build.partition_state[0].partition_name)
+    self.assertEqual('87654321', pre_build.partition_state[0].version)
+
+
   @test_utils.SkipIfExternalToolsUnavailable()
   def test_GetTargetFilesZipForSecondaryImages(self):
     input_file = construct_target_files(secondary=True)