1 // Copyright (C) 2020 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <ftw.h>
16 #include <inttypes.h>
17 #include <sys/mman.h>
18 #include <sys/mount.h>
19 #include <sys/stat.h>
20 #include <sysexits.h>
21
22 #include <chrono>
23 #include <string>
24
25 #include <android-base/file.h>
26 #include <android-base/logging.h>
27 #include <android-base/properties.h>
28 #include <android-base/stringprintf.h>
29 #include <android-base/strings.h>
30 #include <fs_mgr.h>
31 #include <libsnapshot/auto_device.h>
32 #include <libsnapshot/snapshot.h>
33 #include <storage_literals/storage_literals.h>
34
35 #include "snapshot_fuzz_utils.h"
36 #include "utility.h"
37
38 // Prepends the errno string, but it is good enough.
39 #ifndef PCHECK
40 #define PCHECK(x) CHECK(x) << strerror(errno) << ": "
41 #endif
42
43 using namespace android::storage_literals;
44 using namespace std::chrono_literals;
45 using namespace std::string_literals;
46
47 using android::base::Basename;
48 using android::base::ReadFileToString;
49 using android::base::SetProperty;
50 using android::base::Split;
51 using android::base::StartsWith;
52 using android::base::StringPrintf;
53 using android::base::unique_fd;
54 using android::base::WriteStringToFile;
55 using android::dm::DeviceMapper;
56 using android::dm::DmTarget;
57 using android::dm::LoopControl;
58 using android::fiemap::IImageManager;
59 using android::fiemap::ImageManager;
60 using android::fs_mgr::BlockDeviceInfo;
61 using android::fs_mgr::FstabEntry;
62 using android::fs_mgr::IPartitionOpener;
63 using chromeos_update_engine::DynamicPartitionMetadata;
64
65 static const char MNT_DIR[] = "/mnt";
66 static const char BLOCK_SYSFS[] = "/sys/block";
67
68 static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
69 static const auto SUPER_IMAGE_SIZE = 16_MiB;
70 static const auto DATA_IMAGE_SIZE = 16_MiB;
71 static const auto FAKE_ROOT_SIZE = 64_MiB;
72
73 namespace android::snapshot {
74
Mkdir(const std::string & path)75 bool Mkdir(const std::string& path) {
76 if (mkdir(path.c_str(), 0750) == -1 && errno != EEXIST) {
77 PLOG(ERROR) << "Cannot create " << path;
78 return false;
79 }
80 return true;
81 }
82
RmdirRecursive(const std::string & path)83 bool RmdirRecursive(const std::string& path) {
84 auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
85 switch (file_type) {
86 case FTW_D:
87 case FTW_DP:
88 case FTW_DNR:
89 if (rmdir(child) == -1) {
90 PLOG(ERROR) << "rmdir " << child;
91 return -1;
92 }
93 return 0;
94 case FTW_NS:
95 default:
96 if (rmdir(child) != -1) break;
97 [[fallthrough]];
98 case FTW_F:
99 case FTW_SL:
100 case FTW_SLN:
101 if (unlink(child) == -1) {
102 PLOG(ERROR) << "unlink " << child;
103 return -1;
104 }
105 return 0;
106 }
107 return 0;
108 };
109
110 return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) == 0;
111 }
112
GetLinearBaseDeviceString(const DeviceMapper::TargetInfo & target)113 std::string GetLinearBaseDeviceString(const DeviceMapper::TargetInfo& target) {
114 if (target.spec.target_type != "linear"s) return {};
115 auto tokens = Split(target.data, " ");
116 CHECK_EQ(2, tokens.size());
117 return tokens[0];
118 }
119
GetSnapshotBaseDeviceStrings(const DeviceMapper::TargetInfo & target)120 std::vector<std::string> GetSnapshotBaseDeviceStrings(const DeviceMapper::TargetInfo& target) {
121 if (target.spec.target_type != "snapshot"s && target.spec.target_type != "snapshot-merge"s)
122 return {};
123 auto tokens = Split(target.data, " ");
124 CHECK_EQ(4, tokens.size());
125 return {tokens[0], tokens[1]};
126 }
127
ShouldDeleteLoopDevice(const std::string & node)128 bool ShouldDeleteLoopDevice(const std::string& node) {
129 std::string backing_file;
130 if (ReadFileToString(StringPrintf("%s/loop/backing_file", node.data()), &backing_file)) {
131 if (StartsWith(backing_file, std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME)) {
132 return true;
133 }
134 }
135 return false;
136 }
137
GetTableInfoIfExists(const std::string & dev_name)138 std::vector<DeviceMapper::TargetInfo> GetTableInfoIfExists(const std::string& dev_name) {
139 auto& dm = DeviceMapper::Instance();
140 std::vector<DeviceMapper::TargetInfo> table;
141 if (!dm.GetTableInfo(dev_name, &table)) {
142 PCHECK(errno == ENODEV);
143 return {};
144 }
145 return table;
146 }
147
GetAllBaseDeviceStrings(const std::string & child_dev)148 std::set<std::string> GetAllBaseDeviceStrings(const std::string& child_dev) {
149 std::set<std::string> ret;
150 for (const auto& child_target : GetTableInfoIfExists(child_dev)) {
151 auto snapshot_bases = GetSnapshotBaseDeviceStrings(child_target);
152 ret.insert(snapshot_bases.begin(), snapshot_bases.end());
153
154 auto linear_base = GetLinearBaseDeviceString(child_target);
155 if (!linear_base.empty()) {
156 ret.insert(linear_base);
157 }
158 }
159 return ret;
160 }
161
162 using PropertyList = std::set<std::string>;
InsertProperty(const char * key,const char *,void * cookie)163 void InsertProperty(const char* key, const char* /*name*/, void* cookie) {
164 reinterpret_cast<PropertyList*>(cookie)->insert(key);
165 }
166
167 // Attempt to delete all devices that is based on dev_name, including itself.
CheckDeleteDeviceMapperTree(const std::string & dev_name,bool known_allow_delete=false,uint64_t depth=100)168 void CheckDeleteDeviceMapperTree(const std::string& dev_name, bool known_allow_delete = false,
169 uint64_t depth = 100) {
170 CHECK(depth > 0) << "Reaching max depth when deleting " << dev_name
171 << ". There may be devices referencing itself. Check `dmctl list devices -v`.";
172
173 auto& dm = DeviceMapper::Instance();
174 auto table = GetTableInfoIfExists(dev_name);
175 if (table.empty()) {
176 PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
177 return;
178 }
179
180 if (!known_allow_delete) {
181 for (const auto& target : table) {
182 auto base_device_string = GetLinearBaseDeviceString(target);
183 if (base_device_string.empty()) continue;
184 if (ShouldDeleteLoopDevice(
185 StringPrintf("/sys/dev/block/%s", base_device_string.data()))) {
186 known_allow_delete = true;
187 break;
188 }
189 }
190 }
191 if (!known_allow_delete) {
192 return;
193 }
194
195 std::string dev_string;
196 PCHECK(dm.GetDeviceString(dev_name, &dev_string));
197
198 std::vector<DeviceMapper::DmBlockDevice> devices;
199 PCHECK(dm.GetAvailableDevices(&devices));
200 for (const auto& child_dev : devices) {
201 auto child_bases = GetAllBaseDeviceStrings(child_dev.name());
202 if (child_bases.find(dev_string) != child_bases.end()) {
203 CheckDeleteDeviceMapperTree(child_dev.name(), true /* known_allow_delete */, depth - 1);
204 }
205 }
206
207 PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
208 }
209
210 // Attempt to clean up residues from previous runs.
CheckCleanupDeviceMapperDevices()211 void CheckCleanupDeviceMapperDevices() {
212 auto& dm = DeviceMapper::Instance();
213 std::vector<DeviceMapper::DmBlockDevice> devices;
214 PCHECK(dm.GetAvailableDevices(&devices));
215
216 for (const auto& dev : devices) {
217 CheckDeleteDeviceMapperTree(dev.name());
218 }
219 }
220
CheckUmount(const std::string & path)221 void CheckUmount(const std::string& path) {
222 PCHECK(TEMP_FAILURE_RETRY(umount(path.data()) == 0) || errno == ENOENT || errno == EINVAL)
223 << path;
224 }
225
CheckDetachLoopDevices(const std::set<std::string> & exclude_names={})226 void CheckDetachLoopDevices(const std::set<std::string>& exclude_names = {}) {
227 // ~SnapshotFuzzEnv automatically does the following.
228 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(BLOCK_SYSFS), closedir);
229 PCHECK(dir != nullptr) << BLOCK_SYSFS;
230 LoopControl loop_control;
231 dirent* dp;
232 while ((dp = readdir(dir.get())) != nullptr) {
233 if (exclude_names.find(dp->d_name) != exclude_names.end()) {
234 continue;
235 }
236 if (!ShouldDeleteLoopDevice(StringPrintf("%s/%s", BLOCK_SYSFS, dp->d_name).data())) {
237 continue;
238 }
239 PCHECK(loop_control.Detach(StringPrintf("/dev/block/%s", dp->d_name).data()));
240 }
241 }
242
CheckUmountAll()243 void CheckUmountAll() {
244 CheckUmount(std::string(MNT_DIR) + "/snapshot_fuzz_data");
245 CheckUmount(std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME);
246 }
247
248 class AutoDeleteDir : public AutoDevice {
249 public:
New(const std::string & path)250 static std::unique_ptr<AutoDeleteDir> New(const std::string& path) {
251 if (!Mkdir(path)) {
252 return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(""));
253 }
254 return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(path));
255 }
~AutoDeleteDir()256 ~AutoDeleteDir() {
257 if (!HasDevice()) return;
258 PCHECK(rmdir(name_.c_str()) == 0 || errno == ENOENT) << name_;
259 }
260
261 private:
AutoDeleteDir(const std::string & path)262 AutoDeleteDir(const std::string& path) : AutoDevice(path) {}
263 };
264
265 class AutoUnmount : public AutoDevice {
266 public:
~AutoUnmount()267 ~AutoUnmount() {
268 if (!HasDevice()) return;
269 CheckUmount(name_);
270 }
AutoUnmount(const std::string & path)271 AutoUnmount(const std::string& path) : AutoDevice(path) {}
272 };
273
274 class AutoUnmountTmpfs : public AutoUnmount {
275 public:
New(const std::string & path,uint64_t size)276 static std::unique_ptr<AutoUnmount> New(const std::string& path, uint64_t size) {
277 if (mount("tmpfs", path.c_str(), "tmpfs", 0,
278 (void*)StringPrintf("size=%" PRIu64, size).data()) == -1) {
279 PLOG(ERROR) << "Cannot mount " << path;
280 return std::unique_ptr<AutoUnmount>(new AutoUnmount(""));
281 }
282 return std::unique_ptr<AutoUnmount>(new AutoUnmount(path));
283 }
284 private:
285 using AutoUnmount::AutoUnmount;
286 };
287
288 // A directory on tmpfs. Upon destruct, it is unmounted and deleted.
289 class AutoMemBasedDir : public AutoDevice {
290 public:
New(const std::string & name,uint64_t size)291 static std::unique_ptr<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
292 auto ret = std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(name));
293 ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path());
294 if (!ret->auto_delete_mount_dir_->HasDevice()) {
295 return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
296 }
297 ret->auto_umount_mount_point_ = AutoUnmountTmpfs::New(ret->mount_path(), size);
298 if (!ret->auto_umount_mount_point_->HasDevice()) {
299 return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
300 }
301 // tmp_path() and persist_path does not need to be deleted upon destruction, hence it is
302 // not wrapped with AutoDeleteDir.
303 if (!Mkdir(ret->tmp_path())) {
304 return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
305 }
306 if (!Mkdir(ret->persist_path())) {
307 return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
308 }
309 return ret;
310 }
311 // Return the temporary scratch directory.
tmp_path() const312 std::string tmp_path() const {
313 CHECK(HasDevice());
314 return mount_path() + "/tmp";
315 }
316 // Return the temporary scratch directory.
persist_path() const317 std::string persist_path() const {
318 CHECK(HasDevice());
319 return mount_path() + "/persist";
320 }
321 // Delete all contents in tmp_path() and start over. tmp_path() itself is re-created.
CheckSoftReset()322 void CheckSoftReset() {
323 PCHECK(RmdirRecursive(tmp_path()));
324 PCHECK(Mkdir(tmp_path()));
325 }
326
327 private:
AutoMemBasedDir(const std::string & name)328 AutoMemBasedDir(const std::string& name) : AutoDevice(name) {}
mount_path() const329 std::string mount_path() const {
330 CHECK(HasDevice());
331 return MNT_DIR + "/"s + name_;
332 }
333 std::unique_ptr<AutoDeleteDir> auto_delete_mount_dir_;
334 std::unique_ptr<AutoUnmount> auto_umount_mount_point_;
335 };
336
SnapshotFuzzEnv()337 SnapshotFuzzEnv::SnapshotFuzzEnv() {
338 CheckCleanupDeviceMapperDevices();
339 CheckDetachLoopDevices();
340 CheckUmountAll();
341
342 fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
343 CHECK(fake_root_ != nullptr);
344 CHECK(fake_root_->HasDevice());
345 loop_control_ = std::make_unique<LoopControl>();
346
347 fake_data_mount_point_ = MNT_DIR + "/snapshot_fuzz_data"s;
348 auto_delete_data_mount_point_ = AutoDeleteDir::New(fake_data_mount_point_);
349 CHECK(auto_delete_data_mount_point_ != nullptr);
350 CHECK(auto_delete_data_mount_point_->HasDevice());
351
352 const auto& fake_persist_path = fake_root_->persist_path();
353 mapped_super_ = CheckMapImage(fake_persist_path + "/super.img", SUPER_IMAGE_SIZE,
354 loop_control_.get(), &fake_super_);
355 mapped_data_ = CheckMapImage(fake_persist_path + "/data.img", DATA_IMAGE_SIZE,
356 loop_control_.get(), &fake_data_block_device_);
357 mounted_data_ = CheckMountFormatData(fake_data_block_device_, fake_data_mount_point_);
358 }
359
~SnapshotFuzzEnv()360 SnapshotFuzzEnv::~SnapshotFuzzEnv() {
361 CheckCleanupDeviceMapperDevices();
362 mounted_data_ = nullptr;
363 auto_delete_data_mount_point_ = nullptr;
364 mapped_data_ = nullptr;
365 mapped_super_ = nullptr;
366 CheckDetachLoopDevices();
367 loop_control_ = nullptr;
368 fake_root_ = nullptr;
369 CheckUmountAll();
370 }
371
CheckZeroFill(const std::string & file,size_t size)372 void CheckZeroFill(const std::string& file, size_t size) {
373 std::string zeros(size, '\0');
374 PCHECK(WriteStringToFile(zeros, file)) << "Cannot write zeros to " << file;
375 }
376
CheckSoftReset()377 void SnapshotFuzzEnv::CheckSoftReset() {
378 fake_root_->CheckSoftReset();
379 CheckZeroFill(super(), SUPER_IMAGE_SIZE);
380 CheckCleanupDeviceMapperDevices();
381 CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
382 }
383
CheckCreateFakeImageManager()384 std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
385 auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
386 auto data_dir = fake_data_mount_point_ + "/image_manager_data";
387 PCHECK(Mkdir(metadata_dir));
388 PCHECK(Mkdir(data_dir));
389 return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
390 }
391
392 // Helper to create a loop device for a file.
CheckCreateLoopDevice(LoopControl * control,const std::string & file,const std::chrono::milliseconds & timeout_ms,std::string * path)393 static void CheckCreateLoopDevice(LoopControl* control, const std::string& file,
394 const std::chrono::milliseconds& timeout_ms, std::string* path) {
395 static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
396 android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
397 PCHECK(file_fd >= 0) << "Could not open file: " << file;
398 CHECK(control->Attach(file_fd, timeout_ms, path))
399 << "Could not create loop device for: " << file;
400 }
401
402 class AutoDetachLoopDevice : public AutoDevice {
403 public:
AutoDetachLoopDevice(LoopControl * control,const std::string & device)404 AutoDetachLoopDevice(LoopControl* control, const std::string& device)
405 : AutoDevice(device), control_(control) {}
~AutoDetachLoopDevice()406 ~AutoDetachLoopDevice() { PCHECK(control_->Detach(name_)) << name_; }
407
408 private:
409 LoopControl* control_;
410 };
411
CheckMapImage(const std::string & img_path,uint64_t size,LoopControl * control,std::string * mapped_path)412 std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapImage(const std::string& img_path,
413 uint64_t size, LoopControl* control,
414 std::string* mapped_path) {
415 CheckZeroFill(img_path, size);
416 CheckCreateLoopDevice(control, img_path, 1s, mapped_path);
417
418 return std::make_unique<AutoDetachLoopDevice>(control, *mapped_path);
419 }
420
CheckCreateSnapshotManager(const SnapshotFuzzData & data)421 SnapshotTestModule SnapshotFuzzEnv::CheckCreateSnapshotManager(const SnapshotFuzzData& data) {
422 SnapshotTestModule ret;
423 auto partition_opener = std::make_unique<TestPartitionOpener>(super());
424 ret.opener = partition_opener.get();
425 CheckWriteSuperMetadata(data, *partition_opener);
426 auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
427 PCHECK(Mkdir(metadata_dir));
428 if (data.has_metadata_snapshots_dir()) {
429 PCHECK(Mkdir(metadata_dir + "/snapshots"));
430 }
431
432 ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
433 std::move(partition_opener), metadata_dir);
434 auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
435 ret.snapshot = std::move(snapshot);
436
437 return ret;
438 }
439
super() const440 const std::string& SnapshotFuzzEnv::super() const {
441 return fake_super_;
442 }
443
CheckWriteSuperMetadata(const SnapshotFuzzData & data,const IPartitionOpener & opener)444 void SnapshotFuzzEnv::CheckWriteSuperMetadata(const SnapshotFuzzData& data,
445 const IPartitionOpener& opener) {
446 if (!data.is_super_metadata_valid()) {
447 // Leave it zero.
448 return;
449 }
450
451 BlockDeviceInfo super_device("super", SUPER_IMAGE_SIZE, 0, 0, 4096);
452 std::vector<BlockDeviceInfo> devices = {super_device};
453 auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
454 CHECK(builder != nullptr);
455
456 // Attempt to create a super partition metadata using proto. All errors are ignored.
457 for (const auto& group_proto : data.super_data().dynamic_partition_metadata().groups()) {
458 (void)builder->AddGroup(group_proto.name(), group_proto.size());
459 for (const auto& partition_name : group_proto.partition_names()) {
460 (void)builder->AddPartition(partition_name, group_proto.name(),
461 LP_PARTITION_ATTR_READONLY);
462 }
463 }
464
465 for (const auto& partition_proto : data.super_data().partitions()) {
466 auto p = builder->FindPartition(partition_proto.partition_name());
467 if (p == nullptr) continue;
468 (void)builder->ResizePartition(p, partition_proto.new_partition_info().size());
469 }
470
471 auto metadata = builder->Export();
472 // metadata may be nullptr if it is not valid (e.g. partition name too long).
473 // In this case, just use empty super partition data.
474 if (metadata == nullptr) {
475 builder = MetadataBuilder::New(devices, "super", 65536, 2);
476 CHECK(builder != nullptr);
477 metadata = builder->Export();
478 CHECK(metadata != nullptr);
479 }
480 CHECK(FlashPartitionTable(opener, super(), *metadata.get()));
481 }
482
CheckMountFormatData(const std::string & blk_device,const std::string & mount_point)483 std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMountFormatData(const std::string& blk_device,
484 const std::string& mount_point) {
485 FstabEntry entry{
486 .blk_device = blk_device,
487 .length = static_cast<off64_t>(DATA_IMAGE_SIZE),
488 .fs_type = "ext4",
489 .mount_point = mount_point,
490 };
491 CHECK(0 == fs_mgr_do_format(entry, false /* crypt_footer */));
492 CHECK(0 == fs_mgr_do_mount_one(entry));
493 return std::make_unique<AutoUnmount>(mount_point);
494 }
495
~SnapshotFuzzImageManager()496 SnapshotFuzzImageManager::~SnapshotFuzzImageManager() {
497 // Remove relevant gsid.mapped_images.* props.
498 for (const auto& name : mapped_) {
499 CHECK(UnmapImageIfExists(name)) << "Cannot unmap " << name;
500 }
501 }
502
MapImageDevice(const std::string & name,const std::chrono::milliseconds & timeout_ms,std::string * path)503 bool SnapshotFuzzImageManager::MapImageDevice(const std::string& name,
504 const std::chrono::milliseconds& timeout_ms,
505 std::string* path) {
506 if (impl_->MapImageDevice(name, timeout_ms, path)) {
507 mapped_.insert(name);
508 return true;
509 }
510 return false;
511 }
512
513 } // namespace android::snapshot
514