Skip to content

Commit 46d6c49

Browse files
committed
Create a minimal testcase to reproduce silent verity corruption
b/186196758 is triggered by the following sequence of events: 1. update_engine finish writing all install ops, emits kEndOfInstall label 2. update_engine opens cow in append mode, invokes InitialiazeAppend(kEndOfInstall) 3. update_engine writes verity data, invokes SnapshotWriter::Finalize() 4. update_engine repeats step 2, but does not write any data after opening SnapshotWriter. Instead, it reads verity and make sure the hash matches what's specified in OTA payload. 5. Reboot device, verity data corrupted, device rollback to slot _a. This is because, during step 4, 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, and determines that everything is fine. However, when calling SnapshotWriter::Finalize(), data after resume label are discarded, therefore verity data is gone. Test: th Bug: 186196758 Change-Id: I0166271b64eb7b574434d617ce730f345ca93ff1
1 parent 9105f4b commit 46d6c49

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed

Android.bp

+1
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ cc_test {
792792
"libcurl_http_fetcher_unittest.cc",
793793
"payload_consumer/bzip_extent_writer_unittest.cc",
794794
"payload_consumer/cached_file_descriptor_unittest.cc",
795+
"payload_consumer/cow_writer_file_descriptor_unittest.cc",
795796
"payload_consumer/certificate_parser_android_unittest.cc",
796797
"payload_consumer/delta_performer_integration_test.cc",
797798
"payload_consumer/delta_performer_unittest.cc",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//
2+
// Copyright (C) 2021 The Android Open Source Project
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#include "update_engine/payload_consumer/cow_writer_file_descriptor.h"
18+
19+
#include <cstring>
20+
#include <memory>
21+
#include <utility>
22+
#include <vector>
23+
24+
#include <android-base/unique_fd.h>
25+
#include <gmock/gmock.h>
26+
#include <gtest/gtest.h>
27+
#include <libsnapshot/snapshot_writer.h>
28+
29+
#include "update_engine/common/utils.h"
30+
31+
namespace chromeos_update_engine {
32+
constexpr size_t BLOCK_SIZE = 4096;
33+
constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 10;
34+
35+
using android::base::unique_fd;
36+
using android::snapshot::CompressedSnapshotWriter;
37+
using android::snapshot::CowOptions;
38+
using android::snapshot::ISnapshotWriter;
39+
40+
class CowWriterFileDescriptorUnittest : public ::testing::Test {
41+
public:
42+
void SetUp() override {
43+
ASSERT_EQ(ftruncate64(cow_device_file_.fd(), PARTITION_SIZE), 0)
44+
<< "Failed to truncate cow_device file to " << PARTITION_SIZE
45+
<< strerror(errno);
46+
ASSERT_EQ(ftruncate64(cow_source_file_.fd(), PARTITION_SIZE), 0)
47+
<< "Failed to truncate cow_source file to " << PARTITION_SIZE
48+
<< strerror(errno);
49+
}
50+
51+
std::unique_ptr<CompressedSnapshotWriter> GetCowWriter() {
52+
const CowOptions options{.block_size = BLOCK_SIZE, .compression = "gz"};
53+
auto snapshot_writer = std::make_unique<CompressedSnapshotWriter>(options);
54+
int fd = open(cow_device_file_.path().c_str(), O_RDWR);
55+
EXPECT_NE(fd, -1);
56+
EXPECT_TRUE(snapshot_writer->SetCowDevice(unique_fd{fd}));
57+
snapshot_writer->SetSourceDevice(cow_source_file_.path());
58+
return snapshot_writer;
59+
}
60+
CowWriterFileDescriptor GetCowFd() {
61+
auto cow_writer = GetCowWriter();
62+
return CowWriterFileDescriptor{std::move(cow_writer)};
63+
}
64+
65+
ScopedTempFile cow_source_file_{"cow_source.XXXXXX", true};
66+
ScopedTempFile cow_device_file_{"cow_device.XXXXXX", true};
67+
};
68+
69+
TEST_F(CowWriterFileDescriptorUnittest, ReadAfterWrite) {
70+
std::vector<unsigned char> buffer;
71+
buffer.resize(BLOCK_SIZE);
72+
std::fill(buffer.begin(), buffer.end(), 234);
73+
74+
std::vector<unsigned char> verity_data;
75+
verity_data.resize(BLOCK_SIZE);
76+
std::fill(verity_data.begin(), verity_data.end(), 0xAA);
77+
78+
auto cow_writer = GetCowWriter();
79+
cow_writer->Initialize();
80+
81+
// Simulate Writing InstallOp data
82+
ASSERT_TRUE(cow_writer->AddRawBlocks(0, buffer.data(), buffer.size()));
83+
ASSERT_TRUE(cow_writer->AddZeroBlocks(1, 2));
84+
ASSERT_TRUE(cow_writer->AddCopy(3, 1));
85+
// Fake label to simulate "end of install"
86+
ASSERT_TRUE(cow_writer->AddLabel(23));
87+
ASSERT_TRUE(
88+
cow_writer->AddRawBlocks(4, verity_data.data(), verity_data.size()));
89+
ASSERT_TRUE(cow_writer->Finalize());
90+
91+
cow_writer = GetCowWriter();
92+
ASSERT_NE(nullptr, cow_writer);
93+
ASSERT_TRUE(cow_writer->InitializeAppend(23));
94+
auto cow_fd =
95+
std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
96+
97+
ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
98+
std::vector<unsigned char> read_back(4096);
99+
ASSERT_EQ((ssize_t)read_back.size(),
100+
cow_fd->Read(read_back.data(), read_back.size()));
101+
ASSERT_EQ(verity_data, read_back);
102+
103+
// Since we didn't write anything to this instance of cow_fd, destructor
104+
// should not call Finalize(). As finalize will drop ops after resume label,
105+
// causing subsequent reads to fail.
106+
cow_writer = GetCowWriter();
107+
ASSERT_NE(nullptr, cow_writer);
108+
ASSERT_TRUE(cow_writer->InitializeAppend(23));
109+
cow_fd = std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
110+
111+
ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
112+
ASSERT_EQ((ssize_t)read_back.size(),
113+
cow_fd->Read(read_back.data(), read_back.size()));
114+
ASSERT_EQ(verity_data, read_back)
115+
<< "Could not read verity data afeter InitializeAppend() => Read() => "
116+
"InitializeAppend() sequence. If no writes happened while CowWriterFd "
117+
"is open, Finalize() should not be called.";
118+
}
119+
120+
} // namespace chromeos_update_engine

payload_consumer/filesystem_verifier_action_unittest.cc

+6-1
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,6 @@ TEST_F(FilesystemVerifierActionTest, RunWithVABCNoVerity) {
488488
}
489489

490490
TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) {
491-
constexpr auto BLOCK_SIZE = 4096;
492491
ScopedTempFile cow_device_file("cow_device.XXXXXX", true);
493492
android::snapshot::CompressedSnapshotWriter snapshot_writer{
494493
{.block_size = BLOCK_SIZE}};
@@ -507,6 +506,12 @@ TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) {
507506
ASSERT_TRUE(snapshot_writer.Finalize());
508507
cow_reader = snapshot_writer.OpenReader();
509508
ASSERT_NE(cow_reader, nullptr);
509+
std::vector<unsigned char> read_back;
510+
read_back.resize(buffer.size());
511+
cow_reader->Seek(BLOCK_SIZE, SEEK_SET);
512+
const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size());
513+
ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE);
514+
ASSERT_EQ(read_back, buffer);
510515
}
511516

512517
} // namespace chromeos_update_engine

0 commit comments

Comments
 (0)