// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "update_engine/payload_consumer/filesystem_verifier_action.h" #include <memory> #include <string> #include <utility> #include <base/bind.h> #include <base/posix/eintr_wrapper.h> #include <brillo/message_loops/fake_message_loop.h> #include <brillo/message_loops/message_loop_utils.h> #include <brillo/secure_blob.h> #include <gtest/gtest.h> #include "update_engine/common/hash_calculator.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" using brillo::MessageLoop; using std::string; namespace chromeos_update_engine { class FilesystemVerifierActionTest : public ::testing::Test { protected: void SetUp() override { loop_.SetAsCurrent(); } void TearDown() override { EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1)); } // Returns true iff test has completed successfully. bool DoTest(bool terminate_early, bool hash_fail); void BuildActions(const InstallPlan& install_plan); brillo::FakeMessageLoop loop_{nullptr}; ActionProcessor processor_; }; class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate { public: FilesystemVerifierActionTestDelegate() : ran_(false), code_(ErrorCode::kError) {} void ProcessingDone(const ActionProcessor* processor, ErrorCode code) { MessageLoop::current()->BreakLoop(); } void ProcessingStopped(const ActionProcessor* processor) { MessageLoop::current()->BreakLoop(); } void ActionCompleted(ActionProcessor* processor, AbstractAction* action, ErrorCode code) { if (action->Type() == FilesystemVerifierAction::StaticType()) { ran_ = true; code_ = code; EXPECT_FALSE(static_cast<FilesystemVerifierAction*>(action)->src_stream_); } else if (action->Type() == ObjectCollectorAction<InstallPlan>::StaticType()) { auto collector_action = static_cast<ObjectCollectorAction<InstallPlan>*>(action); install_plan_.reset(new InstallPlan(collector_action->object())); } } bool ran() const { return ran_; } ErrorCode code() const { return code_; } std::unique_ptr<InstallPlan> install_plan_; private: bool ran_; ErrorCode code_; }; bool FilesystemVerifierActionTest::DoTest(bool terminate_early, bool hash_fail) { test_utils::ScopedTempFile a_loop_file("a_loop_file.XXXXXX"); // Make random data for a. const size_t kLoopFileSize = 10 * 1024 * 1024 + 512; brillo::Blob a_loop_data(kLoopFileSize); test_utils::FillWithData(&a_loop_data); // Write data to disk if (!(test_utils::WriteFileVector(a_loop_file.path(), a_loop_data))) { ADD_FAILURE(); return false; } // Attach loop devices to the files string a_dev; test_utils::ScopedLoopbackDeviceBinder a_dev_releaser( a_loop_file.path(), false, &a_dev); if (!(a_dev_releaser.is_bound())) { ADD_FAILURE(); return false; } LOG(INFO) << "verifying: " << a_loop_file.path() << " (" << a_dev << ")"; bool success = true; // Set up the action objects 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); part.target_path = a_dev; if (!HashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) { ADD_FAILURE(); success = false; } part.source_size = kLoopFileSize; part.source_path = a_dev; if (!HashCalculator::RawHashOfData(a_loop_data, &part.source_hash)) { ADD_FAILURE(); success = false; } install_plan.partitions = {part}; BuildActions(install_plan); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); 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) { bool is_delegate_ran = delegate.ran(); EXPECT_TRUE(is_delegate_ran); success = success && is_delegate_ran; } else { EXPECT_EQ(ErrorCode::kError, delegate.code()); return (ErrorCode::kError == delegate.code()); } if (hash_fail) { ErrorCode expected_exit_code = ErrorCode::kNewRootfsVerificationError; EXPECT_EQ(expected_exit_code, delegate.code()); return (expected_exit_code == delegate.code()); } EXPECT_EQ(ErrorCode::kSuccess, delegate.code()); // Make sure everything in the out_image is there brillo::Blob a_out; if (!utils::ReadFile(a_dev, &a_out)) { ADD_FAILURE(); return false; } const bool is_a_file_reading_eq = test_utils::ExpectVectorsEq(a_loop_data, a_out); EXPECT_TRUE(is_a_file_reading_eq); success = success && is_a_file_reading_eq; bool is_install_plan_eq = (*delegate.install_plan_ == install_plan); EXPECT_TRUE(is_install_plan_eq); success = success && is_install_plan_eq; return success; } void FilesystemVerifierActionTest::BuildActions( const InstallPlan& install_plan) { auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); auto verifier_action = std::make_unique<FilesystemVerifierAction>(); auto collector_action = std::make_unique<ObjectCollectorAction<InstallPlan>>(); feeder_action->set_obj(install_plan); BondActions(feeder_action.get(), verifier_action.get()); BondActions(verifier_action.get(), collector_action.get()); processor_.EnqueueAction(std::move(feeder_action)); processor_.EnqueueAction(std::move(verifier_action)); processor_.EnqueueAction(std::move(collector_action)); } class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate { public: void ActionCompleted(ActionProcessor* processor, AbstractAction* action, ErrorCode code) { if (action->Type() == FilesystemVerifierAction::StaticType()) { ran_ = true; code_ = code; } } bool ran_; ErrorCode code_; }; TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) { auto copier_action = std::make_unique<FilesystemVerifierAction>(); auto collector_action = std::make_unique<ObjectCollectorAction<InstallPlan>>(); BondActions(copier_action.get(), collector_action.get()); processor_.EnqueueAction(std::move(copier_action)); processor_.EnqueueAction(std::move(collector_action)); FilesystemVerifierActionTest2Delegate delegate; processor_.set_delegate(&delegate); processor_.StartProcessing(); EXPECT_FALSE(processor_.IsRunning()); EXPECT_TRUE(delegate.ran_); EXPECT_EQ(ErrorCode::kError, delegate.code_); } 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}; BuildActions(install_plan); FilesystemVerifierActionTest2Delegate delegate; processor_.set_delegate(&delegate); processor_.StartProcessing(); EXPECT_FALSE(processor_.IsRunning()); EXPECT_TRUE(delegate.ran_); EXPECT_EQ(ErrorCode::kFilesystemVerifierError, delegate.code_); } TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) { ASSERT_EQ(0U, getuid()); EXPECT_TRUE(DoTest(false, false)); } TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) { ASSERT_EQ(0U, getuid()); EXPECT_TRUE(DoTest(false, true)); } TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) { ASSERT_EQ(0U, getuid()); EXPECT_TRUE(DoTest(true, false)); // TerminateEarlyTest may leak some null callbacks from the Stream class. while (loop_.RunOnce(false)) { } } #ifdef __ANDROID__ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { test_utils::ScopedTempFile part_file("part_file.XXXXXX"); constexpr size_t filesystem_size = 200 * 4096; constexpr size_t part_size = 256 * 4096; brillo::Blob part_data(filesystem_size, 0x1); part_data.resize(part_size); ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data)); string target_path; 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; part.target_size = part_size; part.block_size = 4096; part.hash_tree_algorithm = "sha1"; part.hash_tree_data_offset = 0; part.hash_tree_data_size = filesystem_size; part.hash_tree_offset = filesystem_size; part.hash_tree_size = 3 * 4096; part.fec_data_offset = 0; part.fec_data_size = filesystem_size + part.hash_tree_size; part.fec_offset = part.fec_data_size; part.fec_size = 2 * 4096; part.fec_roots = 2; // for i in {1..$((200 * 4096))}; do echo -n -e '\x1' >> part; done // avbtool add_hashtree_footer --image part --partition_size $((256 * 4096)) // --partition_name part --do_not_append_vbmeta_image // --output_vbmeta_image vbmeta // truncate -s $((256 * 4096)) part // sha256sum part | xxd -r -p | hexdump -v -e '/1 "0x%02x, "' part.target_hash = {0x28, 0xd4, 0x96, 0x75, 0x4c, 0xf5, 0x8a, 0x3e, 0x31, 0x85, 0x08, 0x92, 0x85, 0x62, 0xf0, 0x37, 0xbc, 0x8d, 0x7e, 0xa4, 0xcb, 0x24, 0x18, 0x7b, 0xf3, 0xeb, 0xb5, 0x8d, 0x6f, 0xc8, 0xd8, 0x1a}; // avbtool info_image --image vbmeta | grep Salt | cut -d':' -f 2 | // xxd -r -p | hexdump -v -e '/1 "0x%02x, "' 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}; BuildActions(install_plan); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); loop_.PostTask( FROM_HERE, base::Bind( [](ActionProcessor* processor) { processor->StartProcessing(); }, base::Unretained(&processor_))); loop_.Run(); EXPECT_FALSE(processor_.IsRunning()); EXPECT_TRUE(delegate.ran()); EXPECT_EQ(ErrorCode::kSuccess, delegate.code()); } #endif // __ANDROID__ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { test_utils::ScopedTempFile part_file("part_file.XXXXXX"); constexpr size_t filesystem_size = 200 * 4096; constexpr size_t part_size = 256 * 4096; brillo::Blob part_data(part_size); test_utils::FillWithData(&part_data); ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data)); string target_path; test_utils::ScopedLoopbackDeviceBinder target_device( part_file.path(), true, &target_path); InstallPlan install_plan; install_plan.write_verity = false; InstallPlan::Partition part; part.name = "part"; part.target_path = target_path; part.target_size = part_size; part.block_size = 4096; part.hash_tree_data_offset = 0; part.hash_tree_data_size = filesystem_size; part.hash_tree_offset = filesystem_size; part.hash_tree_size = 3 * 4096; part.fec_data_offset = 0; part.fec_data_size = filesystem_size + part.hash_tree_size; 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}; BuildActions(install_plan); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); loop_.PostTask( FROM_HERE, base::Bind( [](ActionProcessor* processor) { processor->StartProcessing(); }, base::Unretained(&processor_))); loop_.Run(); EXPECT_FALSE(processor_.IsRunning()); EXPECT_TRUE(delegate.ran()); EXPECT_EQ(ErrorCode::kSuccess, delegate.code()); } } // namespace chromeos_update_engine