Add maximum timestamp to the payload.

Added a new field max_timestamp in the protobuf, from now on
update_engine will reject any payload without this field.
If the OS build timestamp is newer than the max_timestamp, the payload
will also be rejected to prevent downgrade.

Bug: 36232423
Test: update_engine_unittests

Merged-In: Ib20f5f35aaf41165013bada02bc8720917358237
Change-Id: Ib20f5f35aaf41165013bada02bc8720917358237
(cherry picked from commit 5011df680621eb477cad8b34f03fba5b542cc2f9)
(cherry picked from commit ccb01b2a3beb94094388de806bc15b210ebe8b11)
Exempt-From-Owner-Approval: OWNERS file outdated in nyc-*
(cherry picked from commit 8d853bab09b34cf15a9369357d9261af2e9b862d)
diff --git a/common/error_code.h b/common/error_code.h
index 32155f2..c3751ef 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -72,6 +72,9 @@
   kOmahaRequestXMLHasEntityDecl = 46,
   kFilesystemVerifierError = 47,
   kUserCanceled = 48,
+  // kNonCriticalUpdateInOOBE = 49,
+  // kOmahaUpdateIgnoredOverCellular = 50,
+  kPayloadTimestampError = 51,
 
   // VERY IMPORTANT! When adding new error codes:
   //
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index dc9eaf4..d28b019 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -142,6 +142,8 @@
       return "ErrorCode::kFilesystemVerifierError";
     case ErrorCode::kUserCanceled:
       return "ErrorCode::kUserCanceled";
+    case ErrorCode::kPayloadTimestampError:
+      return "ErrorCode::kPayloadTimestampError";
     // Don't add a default case to let the compiler warn about newly added
     // error codes which should be added here.
   }
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 0bd297b..25324ae 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -82,6 +82,8 @@
     return false;
   }
 
+  int64_t GetBuildTimestamp() const override { return build_timestamp_; }
+
   // Setters
   void SetIsOfficialBuild(bool is_official_build) {
     is_official_build_ = is_official_build;
@@ -118,6 +120,10 @@
     powerwash_count_ = powerwash_count;
   }
 
+  void SetBuildTimestamp(int64_t build_timestamp) {
+    build_timestamp_ = build_timestamp;
+  }
+
  private:
   bool is_official_build_;
   bool is_normal_boot_mode_;
@@ -128,6 +134,7 @@
   std::string ec_version_;
   int powerwash_count_;
   bool powerwash_scheduled_{false};
+  int64_t build_timestamp_{0};
 
   DISALLOW_COPY_AND_ASSIGN(FakeHardware);
 };
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index c2d4296..d5f73f7 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -17,6 +17,8 @@
 #ifndef UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
 #define UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
 
+#include <stdint.h>
+
 #include <string>
 #include <vector>
 
@@ -81,6 +83,9 @@
   // powerwash cycles. In case of an error, such as no directory available,
   // returns false.
   virtual bool GetPowerwashSafeDirectory(base::FilePath* path) const = 0;
+
+  // Returns the timestamp of the current OS build.
+  virtual int64_t GetBuildTimestamp() const = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/hardware_android.cc b/hardware_android.cc
index 778f8ad..5b13eec 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -45,6 +45,8 @@
     "--wipe_data\n"
     "--reason=wipe_data_from_ota\n";
 
+const char kPropBuildDateUTC[] = "ro.build.date.utc";
+
 // Write a recovery command line |message| to the BCB. The arguments to recovery
 // must be separated by '\n'. An empty string will erase the BCB.
 bool WriteBootloaderRecoveryMessage(const string& message) {
@@ -173,4 +175,8 @@
   return false;
 }
 
+int64_t HardwareAndroid::GetBuildTimestamp() const {
+  return property_get_int64(kPropBuildDateUTC, 0);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/hardware_android.h b/hardware_android.h
index 4ea3404..6561377 100644
--- a/hardware_android.h
+++ b/hardware_android.h
@@ -45,6 +45,7 @@
   bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+  int64_t GetBuildTimestamp() const override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(HardwareAndroid);
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 85131fc..88b2783 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -182,4 +182,9 @@
   return true;
 }
 
+int64_t HardwareChromeOS::GetBuildTimestamp() const {
+  // TODO(senj): implement this in Chrome OS.
+  return 0;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
index 221f12c..e3f086f 100644
--- a/hardware_chromeos.h
+++ b/hardware_chromeos.h
@@ -46,6 +46,7 @@
   bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+  int64_t GetBuildTimestamp() const override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(HardwareChromeOS);
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 11260fc..433ca1e 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -74,6 +74,7 @@
     case ErrorCode::kDownloadPayloadVerificationError:
     case ErrorCode::kSignedDeltaPayloadExpectedError:
     case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kPayloadTimestampError:
       return metrics::AttemptResult::kPayloadVerificationFailed;
 
     case ErrorCode::kNewRootfsVerificationError:
@@ -205,6 +206,7 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
+    case ErrorCode::kPayloadTimestampError:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index a156132..b338d34 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -1502,6 +1502,14 @@
     }
   }
 
+  if (manifest_.max_timestamp() < hardware_->GetBuildTimestamp()) {
+    LOG(ERROR) << "The current OS build timestamp ("
+               << hardware_->GetBuildTimestamp()
+               << ") is newer than the maximum timestamp in the manifest ("
+               << manifest_.max_timestamp() << ")";
+    return ErrorCode::kPayloadTimestampError;
+  }
+
   // TODO(garnold) we should be adding more and more manifest checks, such as
   // partition boundaries etc (see chromium-os:37661).
 
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index d1918b7..2ee4516 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -638,6 +638,20 @@
                         ErrorCode::kUnsupportedMinorPayloadVersion);
 }
 
+TEST_F(DeltaPerformerTest, ValidateManifestDowngrade) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  manifest.set_minor_version(kFullPayloadMinorVersion);
+  manifest.set_max_timestamp(1);
+  fake_hardware_.SetBuildTimestamp(2);
+
+  RunManifestValidation(manifest,
+                        DeltaPerformer::kSupportedMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kPayloadTimestampError);
+}
+
 TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
   EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
 
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index 0716c1f..85785c5 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -322,6 +322,10 @@
   DEFINE_string(zlib_fingerprint, "",
                 "The fingerprint of zlib in the source image in hash string "
                 "format, used to check imgdiff compatibility.");
+  DEFINE_int64(max_timestamp,
+               0,
+               "The maximum timestamp of the OS allowed to apply this "
+               "payload.");
 
   DEFINE_string(old_channel, "",
                 "The channel for the old image. 'dev-channel', 'npo-channel', "
@@ -573,6 +577,8 @@
     }
   }
 
+  payload_config.max_timestamp = FLAGS_max_timestamp;
+
   if (payload_config.is_delta) {
     LOG(INFO) << "Generating delta update";
   } else {
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 2f95b21..d2ae706 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -70,6 +70,7 @@
     *(manifest_.mutable_new_image_info()) = config.target.image_info;
 
   manifest_.set_block_size(config.block_size);
+  manifest_.set_max_timestamp(config.max_timestamp);
   return true;
 }
 
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index 8617d14..dd3242a 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -190,6 +190,9 @@
 
   // The block size used for all the operations in the manifest.
   size_t block_size = 4096;
+
+  // The maximum timestamp of the OS allowed to apply this payload.
+  int64_t max_timestamp = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_state.cc b/payload_state.cc
index 04b6579..7859420 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -295,6 +295,7 @@
     case ErrorCode::kPayloadMismatchedType:
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kPayloadTimestampError:
       IncrementUrlIndex();
       break;
 
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index 8d51118..9b599d4 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -143,6 +143,10 @@
     "Optional: Path to a source image. If specified, this makes a delta update."
   DEFINE_string metadata_size_file "" \
     "Optional: Path to output metadata size."
+  DEFINE_string max_timestamp "" \
+    "Optional: The maximum unix timestamp of the OS allowed to apply this \
+payload, should be set to a number higher than the build timestamp of the \
+system running on the device, 0 if not specified."
 fi
 if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then
   DEFINE_string unsigned_payload "" "Path to the input unsigned payload."
@@ -524,6 +528,10 @@
     GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
   fi
 
+  if [[ -n "${FLAGS_max_timestamp}" ]]; then
+    GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" )
+  fi
+
   if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then
     GENERATOR_ARGS+=(
       --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}"
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index aed2aaa..02ec19f 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -76,6 +76,7 @@
     case ErrorCode::kPayloadMismatchedType:
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kPayloadTimestampError:
       LOG(INFO) << "Advancing download URL due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
diff --git a/update_metadata.proto b/update_metadata.proto
index 454c736..596a04e 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -281,4 +281,8 @@
   // array can have more than two partitions if needed, and they are identified
   // by the partition name.
   repeated PartitionUpdate partitions = 13;
+
+  // The maximum timestamp of the OS allowed to apply this payload.
+  // Can be used to prevent downgrading the OS.
+  optional int64 max_timestamp = 14;
 }