Fix verity discarded bug am: 9105f4baeb

Original change: https://android-review.googlesource.com/c/platform/system/update_engine/+/1686865

Change-Id: I1fddd2c732e2e8c95c6b1142a85dae8d587101bf
diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc
index d010a53..60812d5 100644
--- a/common/hash_calculator.cc
+++ b/common/hash_calculator.cc
@@ -95,6 +95,11 @@
   return RawHashOfBytes(data.data(), data.size(), out_hash);
 }
 
+bool HashCalculator::RawHashOfFile(const string& name, brillo::Blob* out_hash) {
+  const auto file_size = utils::FileSize(name);
+  return RawHashOfFile(name, file_size, out_hash) == file_size;
+}
+
 off_t HashCalculator::RawHashOfFile(const string& name,
                                     off_t length,
                                     brillo::Blob* out_hash) {
diff --git a/common/hash_calculator.h b/common/hash_calculator.h
index b7e4d86..4426128 100644
--- a/common/hash_calculator.h
+++ b/common/hash_calculator.h
@@ -75,6 +75,7 @@
   static off_t RawHashOfFile(const std::string& name,
                              off_t length,
                              brillo::Blob* out_hash);
+  static bool RawHashOfFile(const std::string& name, brillo::Blob* out_hash);
 
  private:
   // If non-empty, the final raw hash. Will only be set to non-empty when
diff --git a/common/utils.h b/common/utils.h
index 5f6e475..59f236e 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -399,13 +399,19 @@
 
   // If |open_fd| is true, a writable file descriptor will be opened for this
   // file.
-  explicit ScopedTempFile(const std::string& pattern, bool open_fd = false) {
+  // If |truncate_size| is non-zero, truncate file to that size on creation.
+  explicit ScopedTempFile(const std::string& pattern,
+                          bool open_fd = false,
+                          size_t truncate_size = 0) {
     CHECK(utils::MakeTempFile(pattern, &path_, open_fd ? &fd_ : nullptr));
     unlinker_.reset(new ScopedPathUnlinker(path_));
     if (open_fd) {
       CHECK_GE(fd_, 0);
       fd_closer_.reset(new ScopedFdCloser(&fd_));
     }
+    if (truncate_size > 0) {
+      CHECK_EQ(0, truncate(path_.c_str(), truncate_size));
+    }
   }
   virtual ~ScopedTempFile() = default;
 
diff --git a/payload_consumer/cow_writer_file_descriptor.cc b/payload_consumer/cow_writer_file_descriptor.cc
index d8c7afb..2de6664 100644
--- a/payload_consumer/cow_writer_file_descriptor.cc
+++ b/payload_consumer/cow_writer_file_descriptor.cc
@@ -28,7 +28,10 @@
 CowWriterFileDescriptor::CowWriterFileDescriptor(
     std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer)
     : cow_writer_(std::move(cow_writer)),
-      cow_reader_(cow_writer_->OpenReader()) {}
+      cow_reader_(cow_writer_->OpenReader()) {
+  CHECK_NE(cow_writer_, nullptr);
+  CHECK_NE(cow_reader_, nullptr);
+}
 
 bool CowWriterFileDescriptor::Open(const char* path, int flags, mode_t mode) {
   LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()";
@@ -113,7 +116,17 @@
 
 bool CowWriterFileDescriptor::Close() {
   if (cow_writer_) {
-    TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+    // b/186196758
+    // When calling
+    // InitializeAppend(kEndOfInstall), the SnapshotWriter only reads up to the
+    // given label. But OpenReader() completely disregards the resume label and
+    // reads all ops. Therefore, update_engine sees the verity data. However,
+    // when calling SnapshotWriter::Finalize(), data after resume label are
+    // discarded, therefore verity data is gone. To prevent phantom reads, don't
+    // call Finalize() unless we actually write something.
+    if (dirty_) {
+      TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+    }
     cow_writer_ = nullptr;
   }
   if (cow_reader_) {
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index 09dc638..b14cbc8 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -112,6 +112,14 @@
   // This memory is not used anymore.
   buffer_.clear();
 
+  // If we didn't write verity, partitions were maped. Releaase resource now.
+  if (!install_plan_.write_verity &&
+      dynamic_control_->UpdateUsesSnapshotCompression()) {
+    LOG(INFO) << "Not writing verity and VABC is enabled, unmapping all "
+                 "partitions";
+    dynamic_control_->UnmapAllPartitions();
+  }
+
   if (cancelled_)
     return;
   if (code == ErrorCode::kSuccess && HasOutputPipe())
@@ -130,6 +138,21 @@
   const InstallPlan::Partition& partition =
       install_plan_.partitions[partition_index_];
 
+  if (!ShouldWriteVerity()) {
+    // In VABC, if we are not writing verity, just map all partitions,
+    // and read using regular fd on |postinstall_mount_device| .
+    // All read will go through snapuserd, which provides a consistent
+    // view: device will use snapuserd to read partition during boot.
+    // b/186196758
+    // Call UnmapAllPartitions() first, because if we wrote verity before, these
+    // writes won't be visible to previously opened snapuserd daemon. To ensure
+    // that we will see the most up to date data from partitions, call Unmap()
+    // then Map() to re-spin daemon.
+    dynamic_control_->UnmapAllPartitions();
+    dynamic_control_->MapAllPartitions();
+    return InitializeFd(partition.readonly_target_path);
+  }
+
   // FilesystemVerifierAction need the read_fd_.
   partition_fd_ =
       dynamic_control_->OpenCowFd(partition.name, partition.source_path, true);
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index c100684..d2a015d 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -48,8 +48,23 @@
 namespace chromeos_update_engine {
 
 class FilesystemVerifierActionTest : public ::testing::Test {
+ public:
+  static constexpr size_t BLOCK_SIZE = 4096;
+  static constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 1024;
+
  protected:
-  void SetUp() override { loop_.SetAsCurrent(); }
+  void SetUp() override {
+    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 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()));
+    loop_.SetAsCurrent();
+  }
 
   void TearDown() override {
     EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
@@ -62,11 +77,34 @@
   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();
+    EXPECT_TRUE(
+        HashCalculator::RawHashOfFile(source_part.path(), &part.source_hash));
+    EXPECT_TRUE(
+        HashCalculator::RawHashOfFile(target_part.path(), &part.target_hash));
+    return &part;
+  }
+
   brillo::FakeMessageLoop loop_{nullptr};
   ActionProcessor processor_;
   DynamicPartitionControlStub dynamic_control_stub_;
+  static ScopedTempFile source_part;
+  static ScopedTempFile target_part;
 };
 
+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:
   FilesystemVerifierActionTestDelegate()
@@ -406,32 +444,27 @@
 
 TEST_F(FilesystemVerifierActionTest, RunWithVABCNoVerity) {
   InstallPlan install_plan;
-  InstallPlan::Partition& part = install_plan.partitions.emplace_back();
-  part.name = "fake_part";
-  part.target_path = "/dev/fake_target_path";
-  part.target_size = 4096 * 4096;
-  part.block_size = 4096;
-  part.source_path = "/dev/fake_source_path";
-  part.fec_size = 0;
-  part.hash_tree_size = 0;
-  part.target_hash.clear();
-  part.source_hash.clear();
+  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";
 
   NiceMock<MockDynamicPartitionControl> dynamic_control;
-  auto fake_fd = std::make_shared<FakeFileDescriptor>();
 
   ON_CALL(dynamic_control, GetDynamicPartitionsFeatureFlag())
       .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
   ON_CALL(dynamic_control, UpdateUsesSnapshotCompression())
       .WillByDefault(Return(true));
-  ON_CALL(dynamic_control, OpenCowFd(_, _, _)).WillByDefault(Return(fake_fd));
   ON_CALL(dynamic_control, IsDynamicPartition(part.name, _))
       .WillByDefault(Return(true));
 
   EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression())
       .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(1);
+      .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}),
@@ -451,16 +484,7 @@
 
   ASSERT_FALSE(processor_.IsRunning());
   ASSERT_TRUE(delegate.ran());
-  // Filesystem verifier will fail, because we set an empty hash
-  ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code());
-  const auto& read_pos = fake_fd->GetReadOps();
-  size_t expected_offset = 0;
-  for (const auto& [off, size] : read_pos) {
-    ASSERT_EQ(off, expected_offset);
-    expected_offset += size;
-  }
-  const auto actual_read_size = expected_offset;
-  ASSERT_EQ(actual_read_size, part.target_size);
+  ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
 }
 
 TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) {