-
Notifications
You must be signed in to change notification settings - Fork 1
/
verity_mounter.cc
596 lines (515 loc) · 18.5 KB
/
verity_mounter.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "imageloader/verity_mounter.h"
#include <algorithm>
#include <memory>
#include <utility>
#include </usr/include/linux/magic.h>
#include <fcntl.h>
#include <libdevmapper.h>
#include <linux/loop.h>
#include <mntent.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/vfs.h>
#include <base/command_line.h>
#include <base/containers/adapters.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/process/launch.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include "imageloader/component.h"
#include "imageloader/verity_mounter_impl.h"
namespace imageloader {
namespace {
// Path to devices created by device mapper.
constexpr char kDeviceMapperPath[] = "/dev/mapper";
// Path to the loop control device.
constexpr char kDevLoopControl[] = "/dev/loop-control";
using dm_task_ptr = std::unique_ptr<dm_task, void (*)(dm_task*)>;
enum class MountStatus { FAIL, RETRY, SUCCESS };
constexpr int GET_LOOP_DEVICE_MAX_RETRY = 5;
// Fetches the device mapper table entry with the specified name and writes the
// type and parameters into the provided pointers.
// Returns true on success.
bool MapperGetEntry(const std::string& name,
std::string* type,
std::string* parameters) {
auto task = dm_task_ptr(dm_task_create(DM_DEVICE_TABLE), &dm_task_destroy);
struct dm_info info;
if (!task) {
LOG(ERROR) << "dm_task_create failed!";
return false;
}
if (!dm_task_set_name(task.get(), name.c_str())) {
LOG(ERROR) << "dm_task_set_name failed!";
return false;
}
if (!dm_task_run(task.get())) {
LOG(ERROR) << "dm_task_run failed!";
return false;
}
if (!dm_task_get_info(task.get(), &info)) {
LOG(ERROR) << "dm_task_get_info failed!";
return false;
}
void* next = nullptr;
uint64_t start;
uint64_t length;
char* type_cstr;
char* parameters_cstr;
next = dm_get_next_target(task.get(), next, &start, &length, &type_cstr,
¶meters_cstr);
if (type != nullptr) {
*type = std::string(type_cstr);
}
if (parameters != nullptr) {
*parameters = std::string(parameters_cstr);
}
return true;
}
// Fetches the length of the device mapper table entries for the specified name.
bool MapperTableLength(const std::string& name, uint64_t* length) {
auto task = dm_task_ptr(dm_task_create(DM_DEVICE_TABLE), &dm_task_destroy);
struct dm_info info;
if (!task) {
LOG(ERROR) << "dm_task_create failed!";
return false;
}
if (!dm_task_set_name(task.get(), name.c_str())) {
LOG(ERROR) << "dm_task_set_name failed!";
return false;
}
if (!dm_task_run(task.get())) {
LOG(ERROR) << "dm_task_run failed!";
return false;
}
if (!dm_task_get_info(task.get(), &info)) {
LOG(ERROR) << "dm_task_get_info failed!";
return false;
}
*length = 0;
void* next = nullptr;
uint64_t start;
uint64_t length_part;
char* type;
char* parameters;
do {
next = dm_get_next_target(task.get(), next, &start, &length_part, &type,
¶meters);
*length += length_part;
} while (next);
return true;
}
// Executes the equivalent of: dmsetup wipe_table <name>
// Returns true on success.
bool MapperWipeTable(const std::string& name) {
uint64_t length;
if (!MapperTableLength(name, &length)) {
return false;
}
auto task = dm_task_ptr(dm_task_create(DM_DEVICE_RELOAD), &dm_task_destroy);
if (!task) {
LOG(ERROR) << "dm_task_create failed!";
return false;
}
if (!dm_task_set_name(task.get(), name.c_str())) {
LOG(ERROR) << "dm_task_set_name failed!";
return false;
}
if (!dm_task_add_target(task.get(), 0, length, "error", "")) {
LOG(ERROR) << "dm_task_add_target failed!";
return false;
}
if (!dm_task_run(task.get())) {
LOG(ERROR) << "dm_task_run failed!";
return false;
}
return true;
}
// Executes the equivalent of: dmsetup remove <name>
// Returns true on success.
bool MapperRemove(const std::string& name) {
auto task = dm_task_ptr(dm_task_create(DM_DEVICE_REMOVE), &dm_task_destroy);
if (!task) {
LOG(ERROR) << "dm_task_create failed!";
return false;
}
if (!dm_task_set_name(task.get(), name.c_str())) {
LOG(ERROR) << "dm_task_set_name failed!";
return false;
}
if (!dm_task_run(task.get())) {
LOG(ERROR) << "dm_task_run failed!";
return false;
}
return true;
}
// |argv| should include all the commands and table to dmsetup, but not
// include the path to the binary.
bool RunDMSetup(const std::vector<std::string>& argv) {
base::LaunchOptions options;
options.clear_environment = true;
std::vector<std::string> full_argv(argv);
full_argv.insert(full_argv.begin(), "/sbin/dmsetup");
base::Process process = base::LaunchProcess(full_argv, options);
if (!process.IsValid()) {
LOG(ERROR) << "Failed to launch dmsetup process";
return false;
}
int exit_code;
if (!process.WaitForExitWithTimeout(
base::TimeDelta::FromSeconds(kDMSetupTimeoutSeconds), &exit_code)) {
LOG(ERROR) << "Failed to wait for dmsetup process.";
return false;
}
return exit_code == 0;
}
bool LaunchDMCreate(const std::string& name, const std::string& table) {
const std::vector<std::string> argv = {"create", name, "--table",
table.c_str(), "--readonly"};
return RunDMSetup(argv);
}
// Clear the /dev/mapper/<foo> verity device.
void ClearVerityDevice(const std::string& name) {
// Per the man page, wipe_table:
// Wait for any I/O in-flight through the device to complete, then replace the
// table with a new table that fails any new I/O sent to the device. If
// successful, this should release any devices held open by the device's
// table(s).
MapperWipeTable(name);
// Now remove the actual device.
MapperRemove(name);
}
// Clear the file descriptor behind a loop device.
void ClearLoopDevice(const std::string& device_path, int loop_device_num) {
base::ScopedFD loop_device_fd(
open(device_path.c_str(), O_RDONLY | O_CLOEXEC));
if (loop_device_fd.is_valid())
ioctl(loop_device_fd.get(), LOOP_CLR_FD, 0);
base::ScopedFD control(
open(kDevLoopControl, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK));
if (!control.is_valid()) {
PLOG(WARNING) << "Failed to open " << kDevLoopControl;
return;
}
if (ioctl(control.get(), LOOP_CTL_REMOVE, loop_device_num) < 0)
PLOG(WARNING) << "ioctl LOOP_CTL_REMOVE failed";
}
bool SetupDeviceMapper(const std::string& device_path,
const std::string& table,
std::string* dev_name) {
// Now setup the dmsetup table.
std::string final_table = table;
if (!VerityMounter::SetupTable(&final_table, device_path))
return false;
// Generate a name with a random string of 32 characters: we consider this to
// have sufficiently low chance of collision to assume the name isn't taken.
std::vector<uint8_t> rand_bytes(32);
base::RandBytes(rand_bytes.data(), rand_bytes.size());
std::string name = base::HexEncode(rand_bytes.data(), rand_bytes.size());
if (!LaunchDMCreate(name, final_table)) {
LOG(ERROR) << "Failed to run dmsetup.";
return false;
}
const base::FilePath dev_mapper(kDeviceMapperPath);
dev_name->assign(dev_mapper.Append(name).value().c_str());
return true;
}
bool CreateDirectoryWithMode(const base::FilePath& full_path, int mode) {
std::vector<base::FilePath> subpaths;
// Collect a list of all parent directories.
base::FilePath last_path = full_path;
subpaths.push_back(full_path);
for (base::FilePath path = full_path.DirName();
path.value() != last_path.value(); path = path.DirName()) {
subpaths.push_back(path);
last_path = path;
}
// Iterate through the parents and create the missing ones.
for (const auto& subpath : base::Reversed(subpaths)) {
if (base::DirectoryExists(subpath))
continue;
if (mkdir(subpath.value().c_str(), mode) == 0)
continue;
// Mkdir failed, but it might have failed with EEXIST, or some other error
// due to the directory appearing out of thin air. This can occur if two
// processes are trying to create the same file system tree at the same
// time. Check to see if it exists and make sure it is a directory.
if (!base::DirectoryExists(subpath)) {
PLOG(ERROR) << "Failed to create directory: " << subpath.value();
return false;
}
}
return true;
}
bool CreateMountPointIfNeeded(const base::FilePath& mount_point,
bool* already_mounted) {
*already_mounted = false;
// Is this mount point somehow already taken?
struct stat st;
if (lstat(mount_point.value().c_str(), &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
LOG(ERROR) << "Mount point exists but is not a directory.";
return false;
}
base::FilePath mount_parent = mount_point.DirName();
struct stat st2;
if (stat(mount_parent.value().c_str(), &st2) != 0) {
PLOG(ERROR) << "Could not stat the mount point parent";
return false;
}
if (st.st_dev != st2.st_dev) {
struct statfs st_fs;
if (statfs(mount_point.value().c_str(), &st_fs) != 0) {
PLOG(ERROR) << "statfs";
return false;
}
if ((st_fs.f_type != SQUASHFS_MAGIC &&
st_fs.f_type != EXT4_SUPER_MAGIC) ||
!(st_fs.f_flags & ST_NODEV) || !(st_fs.f_flags & ST_NOSUID) ||
!(st_fs.f_flags & ST_RDONLY)) {
LOG(ERROR) << "File system is not the expected type.";
return false;
}
*already_mounted = true;
return true;
}
} else if (!CreateDirectoryWithMode(mount_point, kComponentDirPerms)) {
LOG(ERROR) << "Failed to create mount point: " << mount_point.value();
return false;
}
return true;
}
// Reserves a loop device and associates it with |image_fd|. The path to the
// loop device is returned in |device_path_out| and the int value is in
// |loop_device_num|. When the loop device is no
// longer being used, free the resource with ClearLoopDevice().
MountStatus GetLoopDevice(const base::ScopedFD& image_fd,
std::string* device_path_out,
int* loop_device_num) {
DCHECK(device_path_out);
DCHECK(loop_device_num);
base::ScopedFD loopctl_fd(open("/dev/loop-control", O_RDONLY | O_CLOEXEC));
if (!loopctl_fd.is_valid()) {
PLOG(ERROR) << "loopctl_fd";
return MountStatus::FAIL;
}
int device_free_number = ioctl(loopctl_fd.get(), LOOP_CTL_GET_FREE);
if (device_free_number < 0) {
PLOG(ERROR) << "ioctl : LOOP_CTL_GET_FREE";
return MountStatus::FAIL;
}
*loop_device_num = device_free_number;
std::string device_path =
base::StringPrintf("/dev/loop%d", device_free_number);
base::ScopedFD loop_device_fd(
open(device_path.c_str(), O_RDONLY | O_CLOEXEC));
if (!loop_device_fd.is_valid()) {
PLOG(ERROR) << "Failed to open loop device: " << device_path;
return MountStatus::FAIL;
}
if (ioctl(loop_device_fd.get(), LOOP_SET_FD, image_fd.get()) == -1) {
if (errno != EBUSY) {
PLOG(ERROR) << "ioctl: LOOP_SET_FD";
ioctl(loop_device_fd.get(), LOOP_CLR_FD, 0);
return MountStatus::FAIL;
}
return MountStatus::RETRY;
}
device_path_out->assign(device_path);
return MountStatus::SUCCESS;
}
} // namespace
// static
bool VerityMounter::SetupTable(std::string* table,
const std::string& device_path) {
// Make sure there is only one entry in the device mapper table.
if (std::count(table->begin(), table->end(), '\n') > 1)
return false;
// Remove all newlines from the table. This is to workaround the server
// incorrectly inserting a newline when writing out the table.
table->erase(std::remove(table->begin(), table->end(), '\n'), table->end());
// Replace in the actual loop device name.
base::ReplaceSubstringsAfterOffset(table, 0, "ROOT_DEV", device_path);
base::ReplaceSubstringsAfterOffset(table, 0, "HASH_DEV", device_path);
// If the table does not specify an error condition, use the default (eio).
// This is critical because the default behavior is to panic the device and
// force a system recovery. Do not do this for component corruption.
if (table->find("error_behavior") == std::string::npos)
table->append(" error_behavior=eio");
return true;
}
bool VerityMounter::Mount(const base::ScopedFD& image_fd,
const base::FilePath& mount_point,
const std::string& fs_type,
const std::string& table) {
// First check if the component is already mounted and avoid unnecessary work.
bool already_mounted = false;
if (!CreateMountPointIfNeeded(mount_point, &already_mounted))
return false;
if (already_mounted)
return true;
int loop_device_num;
std::string loop_device_path;
// We need to retry because another program could grap the loop device,
// resulting in an EBUSY error. If that happens, run again and grab a new
// device.
int retries = GET_LOOP_DEVICE_MAX_RETRY;
while (true) {
MountStatus status =
GetLoopDevice(image_fd, &loop_device_path, &loop_device_num);
if (status == MountStatus::FAIL ||
(status == MountStatus::RETRY && retries == 0)) {
LOG(ERROR) << "GetLoopDevice failed, mount_point: "
<< mount_point.value();
return false;
} else if (status == MountStatus::SUCCESS) {
break;
}
--retries;
}
std::string dev_name;
if (!SetupDeviceMapper(loop_device_path, table, &dev_name)) {
LOG(ERROR) << "mount_point: " << mount_point.value();
ClearLoopDevice(loop_device_path, loop_device_num);
return false;
}
if (mount(dev_name.c_str(), mount_point.value().c_str(), fs_type.c_str(),
MS_RDONLY | MS_NOSUID | MS_NODEV, "") < 0) {
PLOG(ERROR) << "mount, mount_point " << mount_point.value();
ClearVerityDevice(dev_name);
ClearLoopDevice(loop_device_path, loop_device_num);
return false;
}
return true;
}
// Takes a device mapper name and determines the loop device number.
bool MapperNameToLoop(const std::string& name, int32_t* loop) {
std::string type;
std::string parameters;
if (!MapperGetEntry(name, &type, ¶meters)) {
return false;
}
if (type != "verity") {
LOG(ERROR) << "Encountered unexpected mapping \"" << type
<< "\" instead of \"verity\".";
return false;
}
return MapperParametersToLoop(parameters, loop);
}
bool Unmount(const base::FilePath& mount_point) {
return umount(mount_point.value().c_str()) == 0;
}
// Returns (mount point, source path) pairs visible to this process. The order
// can be reversed to help with unmounting.
std::vector<std::pair<std::string, std::string>> GetAllMountPaths(
bool reverse) {
std::vector<std::pair<std::string, std::string>> mount_paths;
base::ScopedFILE mountinfo(fopen("/proc/self/mounts", "re"));
if (!mountinfo) {
PLOG(ERROR) << "Failed to open \"/proc/self/mounts\".";
return mount_paths;
}
// MNT_LINE_MAX from sys/mnttab.h is 1024, extra bytes are for null
// termination of strings.
static char buffer[1024 + 4];
struct mntent mount_entry;
while (getmntent_r(mountinfo.get(), &mount_entry, buffer, sizeof(buffer))) {
mount_paths.emplace(reverse ? mount_paths.end() : mount_paths.begin(),
mount_entry.mnt_dir, mount_entry.mnt_fsname);
}
return mount_paths;
}
// Performs the a cleanup of a given mount point which should be tied to the
// given source path. This entails unmounting the mount point, removing the
// device mapper table entry, deleting the mount point folder, and freeing the
// loop device.
// Returns true on success.
bool CleanupImpl(const base::FilePath& mount_point,
const base::FilePath source_path) {
const base::FilePath dev_mapper(kDeviceMapperPath);
if (!IsAncestor(dev_mapper, source_path)) {
LOG(ERROR) << source_path.value() << " is not device mapped!";
return false;
}
// Lookup loop info.
int32_t loop = -1;
if (!MapperNameToLoop(source_path.BaseName().value(), &loop) || loop < 0) {
LOG(ERROR) << "Unable to determine loop device for " << source_path.value();
return false;
}
// Unmount the image.
if (!Unmount(mount_point)) {
PLOG(ERROR) << "Unmount failed";
return false;
}
// Delete mount target folder
base::DeletePathRecursively(mount_point);
// Clear Verity device.
if (!MapperWipeTable(source_path.value())) {
PLOG(ERROR) << "Device mapper wipe table failed";
return false;
}
if (!MapperRemove(source_path.value())) {
PLOG(ERROR) << "Device mapper remove failed";
return false;
}
// Clear loop device.
ClearLoopDevice(base::StringPrintf("/dev/loop%d", loop), loop);
return true;
}
// Unmounts the given path, cleans up the device mapper entry, and frees the
// loop device for the specified mount point.
// Returns true on success.
bool VerityMounter::Cleanup(const base::FilePath& mount_point) {
bool ret = true;
for (const auto& mount_path : GetAllMountPaths(true)) {
if (mount_point.value() == mount_path.first) {
if (!CleanupImpl(mount_point, base::FilePath(mount_path.second))) {
ret = false;
}
}
}
return ret;
}
// Performs a cleanup for all mount points under parent_dir.
// All successfully cleaned up mount points will be appended to "paths".
// Returns false if cleanup fails for any mount point.
bool VerityMounter::CleanupAll(bool dry_run,
const base::FilePath& parent_dir,
std::vector<base::FilePath>* paths) {
bool ret = true;
for (const auto& mount_path : GetAllMountPaths(true)) {
base::FilePath fp_mount_path(mount_path.first);
// It is not enough to check if fp_mount_path is a direct child because
// some mount points, such as printer drivers, are mounted under a sub
// folder.
if (IsAncestor(parent_dir, fp_mount_path)) {
if (dry_run) {
if (paths) {
paths->push_back(fp_mount_path);
}
} else if (CleanupImpl(fp_mount_path,
base::FilePath(mount_path.second))) {
if (paths) {
paths->push_back(fp_mount_path);
}
} else {
LOG(ERROR) << "Failed to cleanup \"" << mount_path.first << "\"";
ret = false;
}
}
}
return ret;
}
} // namespace imageloader