Create a minimal testcase to reproduce silent verity corruption am: 46d6c4987f am: c852a64987 am: 74f3aef492 am: 0f042c79b9
Original change: https://android-review.googlesource.com/c/platform/system/update_engine/+/1687165
Change-Id: I0d57ef5c643c492730b7591d437c7c7c4fc2b302
diff --git a/Android.bp b/Android.bp
index 11c03b4..d74e78f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -792,6 +792,7 @@
"libcurl_http_fetcher_unittest.cc",
"payload_consumer/bzip_extent_writer_unittest.cc",
"payload_consumer/cached_file_descriptor_unittest.cc",
+ "payload_consumer/cow_writer_file_descriptor_unittest.cc",
"payload_consumer/certificate_parser_android_unittest.cc",
"payload_consumer/delta_performer_integration_test.cc",
"payload_consumer/delta_performer_unittest.cc",
diff --git a/payload_consumer/cow_writer_file_descriptor_unittest.cc b/payload_consumer/cow_writer_file_descriptor_unittest.cc
new file mode 100644
index 0000000..c596e3b
--- /dev/null
+++ b/payload_consumer/cow_writer_file_descriptor_unittest.cc
@@ -0,0 +1,120 @@
+//
+// Copyright (C) 2021 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/cow_writer_file_descriptor.h"
+
+#include <cstring>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+constexpr size_t BLOCK_SIZE = 4096;
+constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 10;
+
+using android::base::unique_fd;
+using android::snapshot::CompressedSnapshotWriter;
+using android::snapshot::CowOptions;
+using android::snapshot::ISnapshotWriter;
+
+class CowWriterFileDescriptorUnittest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_EQ(ftruncate64(cow_device_file_.fd(), PARTITION_SIZE), 0)
+ << "Failed to truncate cow_device file to " << PARTITION_SIZE
+ << strerror(errno);
+ ASSERT_EQ(ftruncate64(cow_source_file_.fd(), PARTITION_SIZE), 0)
+ << "Failed to truncate cow_source file to " << PARTITION_SIZE
+ << strerror(errno);
+ }
+
+ std::unique_ptr<CompressedSnapshotWriter> GetCowWriter() {
+ const CowOptions options{.block_size = BLOCK_SIZE, .compression = "gz"};
+ auto snapshot_writer = std::make_unique<CompressedSnapshotWriter>(options);
+ int fd = open(cow_device_file_.path().c_str(), O_RDWR);
+ EXPECT_NE(fd, -1);
+ EXPECT_TRUE(snapshot_writer->SetCowDevice(unique_fd{fd}));
+ snapshot_writer->SetSourceDevice(cow_source_file_.path());
+ return snapshot_writer;
+ }
+ CowWriterFileDescriptor GetCowFd() {
+ auto cow_writer = GetCowWriter();
+ return CowWriterFileDescriptor{std::move(cow_writer)};
+ }
+
+ ScopedTempFile cow_source_file_{"cow_source.XXXXXX", true};
+ ScopedTempFile cow_device_file_{"cow_device.XXXXXX", true};
+};
+
+TEST_F(CowWriterFileDescriptorUnittest, ReadAfterWrite) {
+ std::vector<unsigned char> buffer;
+ buffer.resize(BLOCK_SIZE);
+ std::fill(buffer.begin(), buffer.end(), 234);
+
+ std::vector<unsigned char> verity_data;
+ verity_data.resize(BLOCK_SIZE);
+ std::fill(verity_data.begin(), verity_data.end(), 0xAA);
+
+ auto cow_writer = GetCowWriter();
+ cow_writer->Initialize();
+
+ // Simulate Writing InstallOp data
+ ASSERT_TRUE(cow_writer->AddRawBlocks(0, buffer.data(), buffer.size()));
+ ASSERT_TRUE(cow_writer->AddZeroBlocks(1, 2));
+ ASSERT_TRUE(cow_writer->AddCopy(3, 1));
+ // Fake label to simulate "end of install"
+ ASSERT_TRUE(cow_writer->AddLabel(23));
+ ASSERT_TRUE(
+ cow_writer->AddRawBlocks(4, verity_data.data(), verity_data.size()));
+ ASSERT_TRUE(cow_writer->Finalize());
+
+ cow_writer = GetCowWriter();
+ ASSERT_NE(nullptr, cow_writer);
+ ASSERT_TRUE(cow_writer->InitializeAppend(23));
+ auto cow_fd =
+ std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
+
+ ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
+ std::vector<unsigned char> read_back(4096);
+ ASSERT_EQ((ssize_t)read_back.size(),
+ cow_fd->Read(read_back.data(), read_back.size()));
+ ASSERT_EQ(verity_data, read_back);
+
+ // Since we didn't write anything to this instance of cow_fd, destructor
+ // should not call Finalize(). As finalize will drop ops after resume label,
+ // causing subsequent reads to fail.
+ cow_writer = GetCowWriter();
+ ASSERT_NE(nullptr, cow_writer);
+ ASSERT_TRUE(cow_writer->InitializeAppend(23));
+ cow_fd = std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
+
+ ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
+ ASSERT_EQ((ssize_t)read_back.size(),
+ cow_fd->Read(read_back.data(), read_back.size()));
+ ASSERT_EQ(verity_data, read_back)
+ << "Could not read verity data afeter InitializeAppend() => Read() => "
+ "InitializeAppend() sequence. If no writes happened while CowWriterFd "
+ "is open, Finalize() should not be called.";
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index d2a015d..586662d 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -488,7 +488,6 @@
}
TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) {
- constexpr auto BLOCK_SIZE = 4096;
ScopedTempFile cow_device_file("cow_device.XXXXXX", true);
android::snapshot::CompressedSnapshotWriter snapshot_writer{
{.block_size = BLOCK_SIZE}};
@@ -507,6 +506,12 @@
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