690 lines
24 KiB
C++
690 lines
24 KiB
C++
//
|
|
// 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 <algorithm>
|
|
#include <cstring>
|
|
#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 <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"
|
|
#include "update_engine/common/mock_dynamic_partition_control.h"
|
|
#include "update_engine/common/test_utils.h"
|
|
#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;
|
|
using testing::_;
|
|
using testing::AtLeast;
|
|
using testing::DoAll;
|
|
using testing::NiceMock;
|
|
using testing::Return;
|
|
using testing::SetArgPointee;
|
|
|
|
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/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()));
|
|
loop_.SetAsCurrent();
|
|
}
|
|
|
|
void TearDown() override {
|
|
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);
|
|
|
|
void BuildActions(const InstallPlan& install_plan);
|
|
void BuildActions(const InstallPlan& install_plan,
|
|
DynamicPartitionControlInterface* dynamic_control);
|
|
|
|
InstallPlan::Partition* AddFakePartition(InstallPlan* install_plan,
|
|
std::string name = "fake_part") {
|
|
InstallPlan::Partition& part = install_plan->partitions.emplace_back();
|
|
part.name = name;
|
|
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;
|
|
EXPECT_TRUE(
|
|
HashCalculator::RawHashOfFile(source_part_.path(), &part.source_hash));
|
|
EXPECT_TRUE(
|
|
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.get(), fd.get()));
|
|
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_;
|
|
};
|
|
|
|
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));
|
|
}
|
|
|
|
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)->partition_fd_);
|
|
} 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) {
|
|
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
|
|
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(base::Bind(&ActionProcessor::StartProcessing,
|
|
base::Unretained(&processor_)));
|
|
if (terminate_early) {
|
|
loop_.PostTask(base::Bind(&ActionProcessor::StopProcessing,
|
|
base::Unretained(&processor_)));
|
|
}
|
|
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,
|
|
DynamicPartitionControlInterface* dynamic_control) {
|
|
auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
|
|
auto verifier_action =
|
|
std::make_unique<FilesystemVerifierAction>(dynamic_control);
|
|
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));
|
|
}
|
|
|
|
void FilesystemVerifierActionTest::BuildActions(
|
|
const InstallPlan& install_plan) {
|
|
BuildActions(install_plan, &dynamic_control_stub_);
|
|
}
|
|
|
|
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>(&dynamic_control_stub_);
|
|
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();
|
|
ASSERT_FALSE(processor_.IsRunning());
|
|
ASSERT_TRUE(delegate.ran_);
|
|
EXPECT_EQ(ErrorCode::kError, delegate.code_);
|
|
}
|
|
|
|
TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
|
|
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) {
|
|
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::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) {
|
|
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);
|
|
|
|
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();
|
|
|
|
ASSERT_FALSE(processor_.IsRunning());
|
|
ASSERT_TRUE(delegate.ran());
|
|
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;
|
|
}
|
|
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_unique<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;
|
|
};
|
|
|
|
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));
|
|
}
|
|
EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _))
|
|
.WillRepeatedly(
|
|
DoAll(SetArgPointee<2, std::vector<std::string>>({part.name}),
|
|
Return(true)));
|
|
|
|
BuildActions(install_plan_, &dynamic_control);
|
|
|
|
FilesystemVerifierActionTestDelegate delegate;
|
|
processor_.set_delegate(&delegate);
|
|
|
|
loop_.PostTask(FROM_HERE,
|
|
base::Bind(&ActionProcessor::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.get(),
|
|
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.get(),
|
|
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());
|
|
}
|
|
}
|
|
|
|
TEST_F(FilesystemVerifierActionTest, VABC_Verity_Target_Mismatch) {
|
|
DoTestVABC(true, true);
|
|
}
|
|
|
|
} // namespace chromeos_update_engine
|