diff options
Diffstat (limited to 'payload_consumer/filesystem_verifier_action_unittest.cc')
-rw-r--r-- | payload_consumer/filesystem_verifier_action_unittest.cc | 342 |
1 files changed, 85 insertions, 257 deletions
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc index f2f29547..586662d9 100644 --- a/payload_consumer/filesystem_verifier_action_unittest.cc +++ b/payload_consumer/filesystem_verifier_action_unittest.cc @@ -16,8 +16,6 @@ #include "update_engine/payload_consumer/filesystem_verifier_action.h" -#include <algorithm> -#include <cstring> #include <memory> #include <string> #include <utility> @@ -27,10 +25,8 @@ #include <brillo/message_loops/fake_message_loop.h> #include <brillo/message_loops/message_loop_utils.h> #include <brillo/secure_blob.h> -#include <fec/ecc.h> #include <gtest/gtest.h> #include <libsnapshot/snapshot_writer.h> -#include <sys/stat.h> #include "update_engine/common/dynamic_partition_control_stub.h" #include "update_engine/common/hash_calculator.h" @@ -39,7 +35,6 @@ #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/fake_file_descriptor.h" #include "update_engine/payload_consumer/install_plan.h" -#include "update_engine/payload_consumer/verity_writer_android.h" using brillo::MessageLoop; using std::string; @@ -55,46 +50,19 @@ namespace chromeos_update_engine { class FilesystemVerifierActionTest : public ::testing::Test { public: static constexpr size_t BLOCK_SIZE = 4096; - // We use SHA256 for testing, so hash size is 256bits / 8 - static constexpr size_t HASH_SIZE = 256 / 8; static constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 1024; - static constexpr size_t HASH_TREE_START_OFFSET = 800 * BLOCK_SIZE; - size_t hash_tree_size = 0; - size_t fec_start_offset = 0; - size_t fec_data_size = 0; - static constexpr size_t FEC_ROOTS = 2; - size_t fec_rounds = 0; - size_t fec_size = 0; protected: void SetUp() override { - hash_tree_size = HashTreeBuilder::CalculateSize( - HASH_TREE_START_OFFSET, BLOCK_SIZE, HASH_SIZE); - fec_start_offset = HASH_TREE_START_OFFSET + hash_tree_size; - fec_data_size = fec_start_offset; - static constexpr size_t FEC_ROOTS = 2; - fec_rounds = - utils::DivRoundUp(fec_data_size / BLOCK_SIZE, FEC_RSM - FEC_ROOTS); - fec_size = fec_rounds * FEC_ROOTS * BLOCK_SIZE; - - fec_data_.resize(fec_size); - hash_tree_data_.resize(hash_tree_size); - // Globally readable writable, as we want to write data - ASSERT_EQ(0, fchmod(source_part_.fd(), 0666)) - << " Failed to set " << source_part_.path() << " as writable " - << strerror(errno); - ASSERT_EQ(0, fchmod(target_part_.fd(), 0666)) - << " Failed to set " << target_part_.path() << " as writable " - << strerror(errno); brillo::Blob part_data(PARTITION_SIZE); test_utils::FillWithData(&part_data); ASSERT_TRUE(utils::WriteFile( - source_part_.path().c_str(), part_data.data(), part_data.size())); - // FillWithData() will fill with different data next call. We want + source_part.path().c_str(), part_data.data(), part_data.size())); + // FillWithData() will will with different data next call. We want // source/target partitions to contain different data for testing. test_utils::FillWithData(&part_data); ASSERT_TRUE(utils::WriteFile( - target_part_.path().c_str(), part_data.data(), part_data.size())); + target_part.path().c_str(), part_data.data(), part_data.size())); loop_.SetAsCurrent(); } @@ -102,8 +70,6 @@ class FilesystemVerifierActionTest : public ::testing::Test { EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1)); } - void DoTestVABC(bool clear_target_hash, bool enable_verity); - // Returns true iff test has completed successfully. bool DoTest(bool terminate_early, bool hash_fail); @@ -115,105 +81,29 @@ class FilesystemVerifierActionTest : public ::testing::Test { std::string name = "fake_part") { InstallPlan::Partition& part = install_plan->partitions.emplace_back(); part.name = name; - part.target_path = target_part_.path(); + part.target_path = target_part.path(); part.readonly_target_path = part.target_path; part.target_size = PARTITION_SIZE; part.block_size = BLOCK_SIZE; - part.source_path = source_part_.path(); - part.source_size = PARTITION_SIZE; + part.source_path = source_part.path(); EXPECT_TRUE( - HashCalculator::RawHashOfFile(source_part_.path(), &part.source_hash)); + HashCalculator::RawHashOfFile(source_part.path(), &part.source_hash)); EXPECT_TRUE( - HashCalculator::RawHashOfFile(target_part_.path(), &part.target_hash)); + HashCalculator::RawHashOfFile(target_part.path(), &part.target_hash)); return ∂ } - static void ZeroRange(FileDescriptorPtr fd, - size_t start_block, - size_t num_blocks) { - std::vector<unsigned char> buffer(BLOCK_SIZE); - ASSERT_EQ((ssize_t)(start_block * BLOCK_SIZE), - fd->Seek(start_block * BLOCK_SIZE, SEEK_SET)); - for (size_t i = 0; i < num_blocks; i++) { - ASSERT_TRUE(utils::WriteAll(fd, buffer.data(), buffer.size())); - } - } - - void SetHashWithVerity(InstallPlan::Partition* partition) { - partition->hash_tree_algorithm = "sha256"; - partition->hash_tree_size = hash_tree_size; - partition->hash_tree_offset = HASH_TREE_START_OFFSET; - partition->hash_tree_data_offset = 0; - partition->hash_tree_data_size = HASH_TREE_START_OFFSET; - partition->fec_size = fec_size; - partition->fec_offset = fec_start_offset; - partition->fec_data_offset = 0; - partition->fec_data_size = fec_data_size; - partition->fec_roots = FEC_ROOTS; - VerityWriterAndroid verity_writer; - ASSERT_TRUE(verity_writer.Init(*partition)); - LOG(INFO) << "Opening " << partition->readonly_target_path; - auto fd = std::make_shared<EintrSafeFileDescriptor>(); - ASSERT_TRUE(fd->Open(partition->readonly_target_path.c_str(), O_RDWR)) - << "Failed to open " << partition->target_path.c_str() << " " - << strerror(errno); - std::vector<unsigned char> buffer(BLOCK_SIZE); - // Only need to read up to hash tree - auto bytes_to_read = HASH_TREE_START_OFFSET; - auto offset = 0; - while (bytes_to_read > 0) { - const auto bytes_read = fd->Read( - buffer.data(), std::min<size_t>(buffer.size(), bytes_to_read)); - ASSERT_GT(bytes_read, 0) - << "offset: " << offset << " bytes to read: " << bytes_to_read - << " error: " << strerror(errno); - ASSERT_TRUE(verity_writer.Update(offset, buffer.data(), bytes_read)); - bytes_to_read -= bytes_read; - offset += bytes_read; - } - ASSERT_TRUE(verity_writer.Finalize(fd, fd)); - ASSERT_TRUE(fd->IsOpen()); - ASSERT_TRUE(HashCalculator::RawHashOfFile(target_part_.path(), - &partition->target_hash)); - - ASSERT_TRUE(fd->Seek(HASH_TREE_START_OFFSET, SEEK_SET)); - ASSERT_EQ(fd->Read(hash_tree_data_.data(), hash_tree_data_.size()), - static_cast<ssize_t>(hash_tree_data_.size())) - << "Failed to read hashtree " << strerror(errno); - ASSERT_TRUE(fd->Seek(fec_start_offset, SEEK_SET)); - ASSERT_EQ(fd->Read(fec_data_.data(), fec_data_.size()), - static_cast<ssize_t>(fec_data_.size())) - << "Failed to read FEC " << strerror(errno); - // Fs verification action is expected to write them, so clear verity data to - // ensure that they are re-created correctly. - ZeroRange( - fd, HASH_TREE_START_OFFSET / BLOCK_SIZE, hash_tree_size / BLOCK_SIZE); - ZeroRange(fd, fec_start_offset / BLOCK_SIZE, fec_size / BLOCK_SIZE); - } brillo::FakeMessageLoop loop_{nullptr}; ActionProcessor processor_; DynamicPartitionControlStub dynamic_control_stub_; - std::vector<unsigned char> fec_data_; - std::vector<unsigned char> hash_tree_data_; - static ScopedTempFile source_part_; - static ScopedTempFile target_part_; - InstallPlan install_plan_; + static ScopedTempFile source_part; + static ScopedTempFile target_part; }; -ScopedTempFile FilesystemVerifierActionTest::source_part_{ - "source_part.XXXXXX", true, PARTITION_SIZE}; -ScopedTempFile FilesystemVerifierActionTest::target_part_{ - "target_part.XXXXXX", true, PARTITION_SIZE}; - -static void EnableVABC(MockDynamicPartitionControl* dynamic_control, - const std::string& part_name) { - ON_CALL(*dynamic_control, GetDynamicPartitionsFeatureFlag()) - .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); - ON_CALL(*dynamic_control, UpdateUsesSnapshotCompression()) - .WillByDefault(Return(true)); - ON_CALL(*dynamic_control, IsDynamicPartition(part_name, _)) - .WillByDefault(Return(true)); -} +ScopedTempFile FilesystemVerifierActionTest::source_part{ + "source_part.XXXXXX", false, PARTITION_SIZE}; +ScopedTempFile FilesystemVerifierActionTest::target_part{ + "target_part.XXXXXX", false, PARTITION_SIZE}; class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate { public: @@ -280,8 +170,9 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, bool success = true; // Set up the action objects - install_plan_.source_slot = 0; - install_plan_.target_slot = 1; + InstallPlan install_plan; + install_plan.source_slot = 0; + install_plan.target_slot = 1; InstallPlan::Partition part; part.name = "part"; part.target_size = kLoopFileSize - (hash_fail ? 1 : 0); @@ -296,19 +187,23 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, ADD_FAILURE(); success = false; } - install_plan_.partitions = {part}; + install_plan.partitions = {part}; - BuildActions(install_plan_); + BuildActions(install_plan); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); - loop_.PostTask(base::Bind(&ActionProcessor::StartProcessing, - base::Unretained(&processor_))); - if (terminate_early) { - loop_.PostTask(base::Bind(&ActionProcessor::StopProcessing, - base::Unretained(&processor_))); - } + loop_.PostTask(FROM_HERE, + base::Bind( + [](ActionProcessor* processor, bool terminate_early) { + processor->StartProcessing(); + if (terminate_early) { + processor->StopProcessing(); + } + }, + base::Unretained(&processor_), + terminate_early)); loop_.Run(); if (!terminate_early) { @@ -337,7 +232,7 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, EXPECT_TRUE(is_a_file_reading_eq); success = success && is_a_file_reading_eq; - bool is_install_plan_eq = (*delegate.install_plan_ == install_plan_); + bool is_install_plan_eq = (*delegate.install_plan_ == install_plan); EXPECT_TRUE(is_install_plan_eq); success = success && is_install_plan_eq; return success; @@ -402,13 +297,14 @@ TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) { } TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) { + InstallPlan install_plan; InstallPlan::Partition part; part.name = "nope"; part.source_path = "/no/such/file"; part.target_path = "/no/such/file"; - install_plan_.partitions = {part}; + install_plan.partitions = {part}; - BuildActions(install_plan_); + BuildActions(install_plan); FilesystemVerifierActionTest2Delegate delegate; processor_.set_delegate(&delegate); @@ -449,6 +345,7 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { test_utils::ScopedLoopbackDeviceBinder target_device( part_file.path(), true, &target_path); + InstallPlan install_plan; InstallPlan::Partition part; part.name = "part"; part.target_path = target_path; @@ -479,9 +376,9 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { part.hash_tree_salt = {0x9e, 0xcb, 0xf8, 0xd5, 0x0b, 0xb4, 0x43, 0x0a, 0x7a, 0x10, 0xad, 0x96, 0xd7, 0x15, 0x70, 0xba, 0xed, 0x27, 0xe2, 0xae}; - install_plan_.partitions = {part}; + install_plan.partitions = {part}; - BuildActions(install_plan_); + BuildActions(install_plan); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); @@ -510,7 +407,8 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { test_utils::ScopedLoopbackDeviceBinder target_device( part_file.path(), true, &target_path); - install_plan_.write_verity = false; + InstallPlan install_plan; + install_plan.write_verity = false; InstallPlan::Partition part; part.name = "part"; part.target_path = target_path; @@ -525,9 +423,9 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { part.fec_offset = part.fec_data_size; part.fec_size = 2 * 4096; EXPECT_TRUE(HashCalculator::RawHashOfData(part_data, &part.target_hash)); - install_plan_.partitions = {part}; + install_plan.partitions = {part}; - BuildActions(install_plan_); + BuildActions(install_plan); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); @@ -544,146 +442,76 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); } -void FilesystemVerifierActionTest::DoTestVABC(bool clear_target_hash, - bool enable_verity) { - auto part_ptr = AddFakePartition(&install_plan_); - if (::testing::Test::HasFailure()) { - return; - } +TEST_F(FilesystemVerifierActionTest, RunWithVABCNoVerity) { + InstallPlan install_plan; + auto part_ptr = AddFakePartition(&install_plan); ASSERT_NE(part_ptr, nullptr); InstallPlan::Partition& part = *part_ptr; part.target_path = "Shouldn't attempt to open this path"; - if (enable_verity) { - install_plan_.write_verity = true; - ASSERT_NO_FATAL_FAILURE(SetHashWithVerity(&part)); - } - if (clear_target_hash) { - part.target_hash.clear(); - } NiceMock<MockDynamicPartitionControl> dynamic_control; - EnableVABC(&dynamic_control, part.name); - auto open_cow = [part]() { - auto cow_fd = std::make_shared<EintrSafeFileDescriptor>(); - EXPECT_TRUE(cow_fd->Open(part.readonly_target_path.c_str(), O_RDWR)) - << "Failed to open part " << part.readonly_target_path - << strerror(errno); - return cow_fd; - }; + ON_CALL(dynamic_control, GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamic_control, UpdateUsesSnapshotCompression()) + .WillByDefault(Return(true)); + ON_CALL(dynamic_control, IsDynamicPartition(part.name, _)) + .WillByDefault(Return(true)); EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression()) .Times(AtLeast(1)); - auto cow_fd = open_cow(); - if (HasFailure()) { - return; - } - - if (enable_verity) { - ON_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) - .WillByDefault(open_cow); - EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) - .Times(AtLeast(1)); - - // fs verification isn't supposed to write to |readonly_target_path|. All - // writes should go through fd returned by |OpenCowFd|. Therefore we set - // target part as read-only to make sure. - ASSERT_EQ(0, chmod(part.readonly_target_path.c_str(), 0444)) - << " Failed to set " << part.readonly_target_path << " as read-only " - << strerror(errno); - } else { - // Since we are not writing verity, we should not attempt to OpenCowFd() - // reads should go through regular file descriptors on mapped partitions. - EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) - .Times(0); - EXPECT_CALL(dynamic_control, MapAllPartitions()).Times(AtLeast(1)); - } + // Since we are not writing verity, we should not attempt to OpenCowFd() + // reads should go through regular file descriptors on mapped partitions. + EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) + .Times(0); + EXPECT_CALL(dynamic_control, MapAllPartitions()).Times(AtLeast(1)); EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _)) .WillRepeatedly( DoAll(SetArgPointee<2, std::vector<std::string>>({part.name}), Return(true))); - BuildActions(install_plan_, &dynamic_control); + BuildActions(install_plan, &dynamic_control); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); - loop_.PostTask(FROM_HERE, - base::Bind(&ActionProcessor::StartProcessing, - base::Unretained(&processor_))); + loop_.PostTask( + FROM_HERE, + base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor_))); loop_.Run(); ASSERT_FALSE(processor_.IsRunning()); ASSERT_TRUE(delegate.ran()); - if (enable_verity) { - std::vector<unsigned char> actual_fec(fec_size); - ssize_t bytes_read = 0; - ASSERT_TRUE(utils::PReadAll(cow_fd, - actual_fec.data(), - actual_fec.size(), - fec_start_offset, - &bytes_read)); - ASSERT_EQ(actual_fec, fec_data_); - std::vector<unsigned char> actual_hash_tree(hash_tree_size); - ASSERT_TRUE(utils::PReadAll(cow_fd, - actual_hash_tree.data(), - actual_hash_tree.size(), - HASH_TREE_START_OFFSET, - &bytes_read)); - ASSERT_EQ(actual_hash_tree, hash_tree_data_); - } - if (clear_target_hash) { - ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code()); - } else { - ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); - } -} - -TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Success) { - DoTestVABC(false, false); -} - -TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Target_Mismatch) { - DoTestVABC(true, false); -} - -TEST_F(FilesystemVerifierActionTest, VABC_Verity_Success) { - DoTestVABC(false, true); -} - -TEST_F(FilesystemVerifierActionTest, VABC_Verity_ReadAfterWrite) { - ASSERT_NO_FATAL_FAILURE(DoTestVABC(false, true)); - // Run FS verification again, w/o writing verity. We have seen a bug where - // attempting to run fs again will cause previously written verity data to be - // dropped, so cover this scenario. - ASSERT_GE(install_plan_.partitions.size(), 1UL); - auto& part = install_plan_.partitions[0]; - install_plan_.write_verity = false; - part.readonly_target_path = target_part_.path(); - NiceMock<MockDynamicPartitionControl> dynamic_control; - EnableVABC(&dynamic_control, part.name); - - // b/186196758 is only visible if we repeatedely run FS verification w/o - // writing verity - for (int i = 0; i < 3; i++) { - BuildActions(install_plan_, &dynamic_control); - - FilesystemVerifierActionTestDelegate delegate; - processor_.set_delegate(&delegate); - loop_.PostTask( - FROM_HERE, - base::Bind( - [](ActionProcessor* processor) { processor->StartProcessing(); }, - base::Unretained(&processor_))); - loop_.Run(); - ASSERT_FALSE(processor_.IsRunning()); - ASSERT_TRUE(delegate.ran()); - ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); - } + ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); } -TEST_F(FilesystemVerifierActionTest, VABC_Verity_Target_Mismatch) { - DoTestVABC(true, true); +TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) { + ScopedTempFile cow_device_file("cow_device.XXXXXX", true); + android::snapshot::CompressedSnapshotWriter snapshot_writer{ + {.block_size = BLOCK_SIZE}}; + snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd()}); + snapshot_writer.Initialize(); + std::vector<unsigned char> buffer; + buffer.resize(BLOCK_SIZE); + std::fill(buffer.begin(), buffer.end(), 123); + + ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size())); + ASSERT_TRUE(snapshot_writer.Finalize()); + auto cow_reader = snapshot_writer.OpenReader(); + ASSERT_NE(cow_reader, nullptr); + ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size())); + ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size())); + ASSERT_TRUE(snapshot_writer.Finalize()); + cow_reader = snapshot_writer.OpenReader(); + ASSERT_NE(cow_reader, nullptr); + std::vector<unsigned char> read_back; + read_back.resize(buffer.size()); + cow_reader->Seek(BLOCK_SIZE, SEEK_SET); + const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size()); + ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE); + ASSERT_EQ(read_back, buffer); } } // namespace chromeos_update_engine |