Add security patch level to update manifest

When installing a full OTA, the target build might have a newer
timestamp but older SPL. In these caess, update_engine will fail to
recognize the SPL downgrade and skip data wipe, causing /data decryption
to fail on next reboot. To fix this issue, add a SPL field to update
manifest. update_engine will check this field on OTA install and
schedule data wipe as needed.

Test: install OTA with newer timestamp but older SPL, make sure data wipe is scheduled
Bug: 242812845


Change-Id: I9d1dd73b46323939bbf990e29da5cc0ba79f86e2
diff --git a/aosp/update_attempter_android.cc b/aosp/update_attempter_android.cc
index b95da94..4faae08 100644
--- a/aosp/update_attempter_android.cc
+++ b/aosp/update_attempter_android.cc
@@ -47,6 +47,7 @@
 #include "update_engine/payload_consumer/file_descriptor.h"
 #include "update_engine/payload_consumer/file_descriptor_utils.h"
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include "update_engine/payload_consumer/partition_writer.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/payload_metadata.h"
 #include "update_engine/payload_consumer/payload_verifier.h"
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index fc8858f..47eb353 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -28,10 +28,12 @@
 #include <utility>
 #include <vector>
 
+#include <android-base/properties.h>
 #include <base/files/file_util.h>
 #include <base/format_macros.h>
 #include <base/metrics/histogram_macros.h>
 #include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
 #include <base/time/time.h>
 #include <brillo/data_encoding.h>
 #include <bsdiff/bspatch.h>
@@ -44,24 +46,15 @@
 #include "update_engine/common/error_code_utils.h"
 #include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/prefs_interface.h"
-#include "update_engine/common/subprocess.h"
 #include "update_engine/common/terminator.h"
 #include "update_engine/common/utils.h"
-#include "update_engine/payload_consumer/bzip_extent_writer.h"
-#include "update_engine/payload_consumer/cached_file_descriptor.h"
-#include "update_engine/payload_consumer/certificate_parser_interface.h"
-#include "update_engine/payload_consumer/extent_reader.h"
-#include "update_engine/payload_consumer/extent_writer.h"
 #include "update_engine/payload_consumer/partition_update_generator_interface.h"
 #include "update_engine/payload_consumer/partition_writer.h"
 #if USE_FEC
 #include "update_engine/payload_consumer/fec_file_descriptor.h"
 #endif  // USE_FEC
-#include "update_engine/payload_consumer/file_descriptor_utils.h"
-#include "update_engine/payload_consumer/mount_history.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/payload_verifier.h"
-#include "update_engine/payload_consumer/xz_extent_writer.h"
 
 using google::protobuf::RepeatedPtrField;
 using std::min;
@@ -398,6 +391,23 @@
       base::TimeDelta::FromMinutes(5),                                      \
       20);
 
+void DeltaPerformer::CheckSPLDowngrade() {
+  const auto new_spl = manifest_.security_patch_level();
+  const auto current_spl =
+      android::base::GetProperty("ro.build.version.security_patch", "");
+  if (current_spl.empty()) {
+    LOG(ERROR) << "Failed to get ro.build.version.security_patch, unable to "
+                  "determine if this OTA is a SPL downgrade.";
+    return;
+  }
+  if (new_spl < current_spl) {
+    install_plan_->powerwash_required = true;
+    LOG(INFO) << "Target build SPL " << new_spl
+              << " is older than current build's SPL " << current_spl
+              << ", this OTA is an SPL downgrade. Data wipe will be required";
+  }
+}
+
 // Wrapper around write. Returns true if all requested bytes
 // were written, or false on any error, regardless of progress
 // and stores an action exit code in |error|.
@@ -444,6 +454,8 @@
 
     block_size_ = manifest_.block_size();
 
+    CheckSPLDowngrade();
+
     // This populates |partitions_| and the |install_plan.partitions| with the
     // list of partitions from the manifest.
     if (!ParseManifestPartitions(error))
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index dd71467..633c533 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -32,10 +32,9 @@
 
 #include "update_engine/common/hash_calculator.h"
 #include "update_engine/common/platform_constants.h"
-#include "update_engine/payload_consumer/file_descriptor.h"
 #include "update_engine/payload_consumer/file_writer.h"
 #include "update_engine/payload_consumer/install_plan.h"
-#include "update_engine/payload_consumer/partition_writer.h"
+#include "update_engine/payload_consumer/partition_writer_interface.h"
 #include "update_engine/payload_consumer/payload_metadata.h"
 #include "update_engine/payload_consumer/payload_verifier.h"
 #include "update_engine/update_metadata.pb.h"
@@ -88,7 +87,7 @@
   // FileWriter's Write implementation where caller doesn't care about
   // error codes.
   bool Write(const void* bytes, size_t count) override {
-    ErrorCode error;
+    ErrorCode error{};
     return Write(bytes, count, &error);
   }
 
@@ -315,6 +314,8 @@
   // Check if partition `part_name` is a dynamic partition.
   bool IsDynamicPartition(const std::string& part_name, uint32_t slot);
 
+  void CheckSPLDowngrade();
+
   // Update Engine preference store.
   PrefsInterface* prefs_;
 
diff --git a/payload_consumer/partition_writer_unittest.cc b/payload_consumer/partition_writer_unittest.cc
index 331a061..4910594 100644
--- a/payload_consumer/partition_writer_unittest.cc
+++ b/payload_consumer/partition_writer_unittest.cc
@@ -26,16 +26,15 @@
 #include "update_engine/common/hash_calculator.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
-#include "update_engine/payload_consumer/delta_performer.h"
-#include "update_engine/payload_consumer/extent_reader.h"
 #include "update_engine/payload_consumer/extent_writer.h"
 #include "update_engine/payload_consumer/fake_file_descriptor.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
 #include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/payload_consumer/partition_writer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_generator/annotated_operation.h"
 #include "update_engine/payload_generator/delta_diff_generator.h"
 #include "update_engine/payload_generator/extent_ranges.h"
-#include "update_engine/payload_generator/payload_file.h"
 #include "update_engine/payload_generator/payload_generation_config.h"
 #include "update_engine/update_metadata.pb.h"
 
diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h
index dcb6867..bfbcdf7 100644
--- a/payload_generator/delta_diff_utils.h
+++ b/payload_generator/delta_diff_utils.h
@@ -25,8 +25,9 @@
 #include <brillo/secure_blob.h>
 #include <puffin/puffdiff.h>
 
-#include "payload_generator/deflate_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/deflate_utils.h"
 #include "update_engine/payload_generator/extent_ranges.h"
 #include "update_engine/payload_generator/payload_generation_config.h"
 #include "update_engine/update_metadata.pb.h"
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index 49c1e9c..96883a8 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -394,6 +394,11 @@
                "The maximum timestamp of the OS allowed to apply this "
                "payload.");
   DEFINE_string(
+      security_patch_level,
+      "",
+      "The security patch level of this OTA. Devices with a newer SPL "
+      "will not be allowed to apply this payload");
+  DEFINE_string(
       partition_timestamps,
       "",
       "The per-partition maximum timestamps which the OS allowed to apply this "
@@ -719,6 +724,9 @@
   }
 
   payload_config.max_timestamp = FLAGS_max_timestamp;
+
+  payload_config.security_patch_level = FLAGS_security_patch_level;
+
   if (!FLAGS_partition_timestamps.empty()) {
     CHECK(ParsePerPartitionTimestamps(FLAGS_partition_timestamps,
                                       &payload_config));
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 129377a..0ef747a 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -26,7 +26,6 @@
 
 #include "update_engine/common/hash_calculator.h"
 #include "update_engine/common/utils.h"
-#include "update_engine/payload_consumer/delta_performer.h"
 #include "update_engine/payload_consumer/file_writer.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_generator/annotated_operation.h"
@@ -67,6 +66,7 @@
   manifest_.set_minor_version(config.version.minor);
   manifest_.set_block_size(config.block_size);
   manifest_.set_max_timestamp(config.max_timestamp);
+  manifest_.set_security_patch_level(config.security_patch_level);
 
   if (config.target.dynamic_partition_metadata != nullptr)
     *(manifest_.mutable_dynamic_partition_metadata()) =
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index 1e8794b..fc56f56 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -27,7 +27,6 @@
 #include <brillo/secure_blob.h>
 
 #include "bsdiff/constants.h"
-#include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_generator/filesystem_interface.h"
 #include "update_engine/update_metadata.pb.h"
 
@@ -262,6 +261,8 @@
   // Whether to enable zucchini ops
   bool enable_zucchini = true;
 
+  std::string security_patch_level;
+
   std::vector<bsdiff::CompressorType> compressors{
       bsdiff::CompressorType::kBZ2, bsdiff::CompressorType::kBrotli};
 
diff --git a/payload_generator/payload_properties_unittest.cc b/payload_generator/payload_properties_unittest.cc
index 0ff364f..0d62681 100644
--- a/payload_generator/payload_properties_unittest.cc
+++ b/payload_generator/payload_properties_unittest.cc
@@ -104,9 +104,9 @@
       "{"
       R"("is_delta":true,)"
       R"("metadata_signature":"",)"
-      R"("metadata_size":165,)"
-      R"("sha256_hex":"cV7kfZBH3K0B6QJHxxykDh6b6x0WgVOmc63whPLOy7U=",)"
-      R"("size":211,)"
+      R"("metadata_size":168,)"
+      R"("sha256_hex":"6rXHDjFO8k8mNtIbLhimWOifecVI1Ts230Ia1DyNuPY=",)"
+      R"("size":214,)"
       R"("version":2)"
       "}";
   string json;
@@ -118,10 +118,10 @@
 // Validate the hash of file and metadata are within the output.
 TEST_F(PayloadPropertiesTest, GetPropertiesAsKeyValueTestHash) {
   constexpr char kKeyValueProperties[] =
-      "FILE_HASH=cV7kfZBH3K0B6QJHxxykDh6b6x0WgVOmc63whPLOy7U=\n"
-      "FILE_SIZE=211\n"
-      "METADATA_HASH=aEKYyzJt2E8Gz8fzB+gmekN5mriotZCSq6R+kDfdeV4=\n"
-      "METADATA_SIZE=165\n";
+      "FILE_HASH=6rXHDjFO8k8mNtIbLhimWOifecVI1Ts230Ia1DyNuPY=\n"
+      "FILE_SIZE=214\n"
+      "METADATA_HASH=wq2nRZ7o/aqEeVWcc2Z+bebLYEI8quPEnXHlyLtdW9Y=\n"
+      "METADATA_SIZE=168\n";
   string key_value;
   EXPECT_TRUE(PayloadProperties{payload_file_.path()}.GetPropertiesAsKeyValue(
       &key_value));
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index b2d6080..6652b38 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -220,6 +220,8 @@
     "Required if --enabled_lz4diff true is passed. Path to liblz4.so. delta_generator will use this copy of liblz4.so for compression. It is important that this copy of liblz4.so is the same as the one on source build."
   DEFINE_string erofs_compression_param "" \
     "Compression parameter passed to mkfs.erofs's -z option."
+  DEFINE_string security_patch_level "" \
+    "Optional: security patch level of this OTA"
 fi
 if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then
   DEFINE_string unsigned_payload "" "Path to the input unsigned payload."
@@ -776,6 +778,10 @@
     GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" )
   fi
 
+  if [[ -n "${FLAGS_security_patch_level}" ]]; then
+    GENERATOR_ARGS+=( --security_patch_level="${FLAGS_security_patch_level}" )
+  fi
+
   if [[ -n "${FLAGS_partition_timestamps}" ]]; then
     GENERATOR_ARGS+=( --partition_timestamps="${FLAGS_partition_timestamps}" )
   fi
diff --git a/update_metadata.proto b/update_metadata.proto
index 3f454ad..84b991b 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -423,4 +423,8 @@
   // Information on compressed APEX to figure out how much space is required for
   // their decompression
   repeated ApexInfo apex_info = 17;
+
+  // Security patch level of the device, usually in the format of
+  // yyyy-mm-dd
+  optional string security_patch_level = 18;
 }