Verify operation source hash if present.
Verify source for every operation that needs to read from source partition
so that we can skip verifying the whole source partition before applying
any operation.
Bug: 23182225
TEST=cros_workon_make update_engine --test
Change-Id: I13e0b450574cee5ef892839ee703d93680531f72
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 1df5214..99db3bf 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -983,6 +983,22 @@
return sum;
}
+// Compare |calculated_hash| with source hash in |operation|, return false and
+// dump hash if don't match.
+bool ValidateSourceHash(const brillo::Blob& calculated_hash,
+ const InstallOperation& operation) {
+ brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
+ operation.src_sha256_hash().end());
+ if (calculated_hash != expected_source_hash) {
+ LOG(ERROR) << "Hash verification failed. Expected hash = ";
+ utils::HexDumpVector(expected_source_hash);
+ LOG(ERROR) << "Calculated hash = ";
+ utils::HexDumpVector(calculated_hash);
+ return false;
+ }
+ return true;
+}
+
} // namespace
bool DeltaPerformer::PerformSourceCopyOperation(
@@ -1006,6 +1022,7 @@
brillo::Blob buf(block_size_);
ssize_t bytes_read = 0;
+ HashCalculator source_hasher;
// Read/write one block at a time.
for (uint64_t i = 0; i < blocks_to_read; i++) {
ssize_t bytes_read_this_iteration = 0;
@@ -1030,7 +1047,17 @@
bytes_read += bytes_read_this_iteration;
TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
static_cast<ssize_t>(block_size_));
+
+ if (operation.has_src_sha256_hash())
+ TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), buf.size()));
}
+
+ if (operation.has_src_sha256_hash()) {
+ TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+ TEST_AND_RETURN_FALSE(
+ ValidateSourceHash(source_hasher.raw_hash(), operation));
+ }
+
DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
return true;
}
@@ -1126,6 +1153,29 @@
if (operation.has_dst_length())
TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+ if (operation.has_src_sha256_hash()) {
+ HashCalculator source_hasher;
+ const uint64_t kMaxBlocksToRead = 512; // 2MB if block size is 4KB
+ brillo::Blob buf(kMaxBlocksToRead * block_size_);
+ for (const Extent& extent : operation.src_extents()) {
+ for (uint64_t i = 0; i < extent.num_blocks(); i += kMaxBlocksToRead) {
+ uint64_t blocks_to_read =
+ min(kMaxBlocksToRead, extent.num_blocks() - i);
+ ssize_t bytes_to_read = blocks_to_read * block_size_;
+ ssize_t bytes_read_this_iteration = 0;
+ TEST_AND_RETURN_FALSE(
+ utils::PReadAll(source_fd_, buf.data(), bytes_to_read,
+ (extent.start_block() + i) * block_size_,
+ &bytes_read_this_iteration));
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes_to_read);
+ TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), bytes_to_read));
+ }
+ }
+ TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+ TEST_AND_RETURN_FALSE(
+ ValidateSourceHash(source_hasher.raw_hash(), operation));
+ }
+
string input_positions;
TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
block_size_,
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index c0a1a57..25e4a55 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -149,18 +149,24 @@
}
// Apply |payload_data| on partition specified in |source_path|.
+ // Expect result of performer_.Write() to be |expect_success|.
+ // Returns the result of the payload application.
brillo::Blob ApplyPayload(const brillo::Blob& payload_data,
- const string& source_path) {
- return ApplyPayloadToData(payload_data, source_path, brillo::Blob());
+ const string& source_path,
+ bool expect_success) {
+ return ApplyPayloadToData(payload_data, source_path, brillo::Blob(),
+ expect_success);
}
// Apply the payload provided in |payload_data| reading from the |source_path|
// file and writing the contents to a new partition. The existing data in the
// new target file are set to |target_data| before applying the payload.
+ // Expect result of performer_.Write() to be |expect_success|.
// Returns the result of the payload application.
brillo::Blob ApplyPayloadToData(const brillo::Blob& payload_data,
const string& source_path,
- const brillo::Blob& target_data) {
+ const brillo::Blob& target_data,
+ bool expect_success) {
string new_part;
EXPECT_TRUE(utils::MakeTempFile("Partition-XXXXXX", &new_part, nullptr));
ScopedPathUnlinker partition_unlinker(new_part);
@@ -178,7 +184,8 @@
fake_system_state_.fake_boot_control()->SetPartitionDevice(
kLegacyPartitionNameKernel, install_plan_.source_slot, "/dev/null");
- EXPECT_TRUE(performer_.Write(payload_data.data(), payload_data.size()));
+ EXPECT_EQ(expect_success,
+ performer_.Write(payload_data.data(), payload_data.size()));
EXPECT_EQ(0, performer_.Close());
brillo::Blob partition_data;
@@ -315,7 +322,7 @@
brillo::Blob payload_data = GeneratePayload(expected_data, aops, false,
kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
- EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
}
TEST_F(DeltaPerformerTest, ReplaceOperationTest) {
@@ -334,7 +341,7 @@
kChromeOSMajorPayloadVersion,
kSourceMinorPayloadVersion);
- EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
}
TEST_F(DeltaPerformerTest, ReplaceBzOperationTest) {
@@ -356,7 +363,7 @@
kChromeOSMajorPayloadVersion,
kSourceMinorPayloadVersion);
- EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
}
TEST_F(DeltaPerformerTest, ReplaceXzOperationTest) {
@@ -378,7 +385,7 @@
kChromeOSMajorPayloadVersion,
kSourceMinorPayloadVersion);
- EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
}
TEST_F(DeltaPerformerTest, ZeroOperationTest) {
@@ -402,23 +409,24 @@
kSourceMinorPayloadVersion);
EXPECT_EQ(expected_data,
- ApplyPayloadToData(payload_data, "/dev/null", existing_data));
+ ApplyPayloadToData(payload_data, "/dev/null", existing_data, true));
}
TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
- brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
- std::end(kRandomString));
+ brillo::Blob expected_data(std::begin(kRandomString),
+ std::end(kRandomString));
expected_data.resize(4096); // block size
- vector<AnnotatedOperation> aops;
AnnotatedOperation aop;
*(aop.op.add_src_extents()) = ExtentForRange(0, 1);
*(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
aop.op.set_type(InstallOperation::SOURCE_COPY);
- aops.push_back(aop);
+ brillo::Blob src_hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+ aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
- brillo::Blob payload_data = GeneratePayload(brillo::Blob(), aops, false,
- kChromeOSMajorPayloadVersion,
- kSourceMinorPayloadVersion);
+ brillo::Blob payload_data =
+ GeneratePayload(brillo::Blob(), {aop}, false,
+ kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion);
string source_path;
EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX",
&source_path, nullptr));
@@ -427,7 +435,33 @@
expected_data.data(),
expected_data.size()));
- EXPECT_EQ(expected_data, ApplyPayload(payload_data, source_path));
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, source_path, true));
+}
+
+TEST_F(DeltaPerformerTest, SourceHashMismatchTest) {
+ brillo::Blob expected_data = {'f', 'o', 'o'};
+ brillo::Blob actual_data = {'b', 'a', 'r'};
+ expected_data.resize(4096); // block size
+ actual_data.resize(4096); // block size
+
+ AnnotatedOperation aop;
+ *(aop.op.add_src_extents()) = ExtentForRange(0, 1);
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_type(InstallOperation::SOURCE_COPY);
+ brillo::Blob src_hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+ aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+
+ brillo::Blob payload_data =
+ GeneratePayload(brillo::Blob(), {aop}, false,
+ kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion);
+ string source_path;
+ EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX", &source_path, nullptr));
+ ScopedPathUnlinker path_unlinker(source_path);
+ EXPECT_TRUE(utils::WriteFile(source_path.c_str(), actual_data.data(),
+ actual_data.size()));
+
+ EXPECT_EQ(actual_data, ApplyPayload(payload_data, source_path, false));
}
TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {