| // Copyright 2021 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <string> | |
| #include <vector> | |
| #include <base/files/file.h> | |
| #include <base/files/file_enumerator.h> | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/logging.h> | |
| #include <base/posix/eintr_wrapper.h> | |
| #include <brillo/file_utils.h> | |
| #include <brillo/process/process.h> | |
| #include <brillo/userdb_utils.h> | |
| #include "tpm2-simulator/constants.h" | |
| #include "tpm2-simulator/tpm_nvchip_utils.h" | |
| namespace { | |
| constexpr char kNVChipPath[] = "NVChip"; | |
| constexpr size_t kNVChipSize = 10 * 1024 * 1024; // 10MB. | |
| constexpr char kWoringDirectory[] = "."; | |
| // The old NVChip size that need to migrate to new format. | |
| constexpr size_t kOldNVChipSize = 16384; | |
| struct NVChipMigrateData { | |
| int version = 0; | |
| std::string chip_data; | |
| }; | |
| bool Tune2Fs(const base::FilePath& file, const std::vector<std::string>& opts) { | |
| brillo::ProcessImpl tune_process; | |
| tune_process.AddArg("/sbin/tune2fs"); | |
| for (const auto& arg : opts) | |
| tune_process.AddArg(arg); | |
| tune_process.AddArg(file.value()); | |
| // Close unused file descriptors in child process. | |
| tune_process.SetCloseUnusedFileDescriptors(true); | |
| // Avoid polluting the parent process' stdout. | |
| tune_process.RedirectOutput(base::FilePath("/dev/null")); | |
| int rc = tune_process.Run(); | |
| if (rc != 0) { | |
| LOG(ERROR) << "Can't tune ext4: " << file.value() << ", error: " << rc; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool FormatExt4(const base::FilePath& file) { | |
| brillo::ProcessImpl format_process; | |
| format_process.AddArg("/sbin/mkfs.ext4"); | |
| format_process.AddArg(file.value()); | |
| // No need to emit output. | |
| format_process.AddArg("-q"); | |
| // Close unused file descriptors in child process. | |
| format_process.SetCloseUnusedFileDescriptors(true); | |
| // Avoid polluting the parent process' stdout. | |
| format_process.RedirectOutput(base::FilePath("/dev/null")); | |
| int rc = format_process.Run(); | |
| if (rc != 0) { | |
| LOG(ERROR) << "Can't format '" << file.value() | |
| << "' as ext4, exit status: " << rc; | |
| return false; | |
| } | |
| // Tune the formatted filesystem: | |
| // -c 0: Disable max mount count checking. | |
| // -i 0: Disable filesystem checking. | |
| return Tune2Fs(file, {"-c", "0", "-i", "0"}); | |
| } | |
| bool MountLoopbackFile(const base::FilePath& file, | |
| const base::FilePath& mount_point) { | |
| brillo::ProcessImpl mount_process; | |
| mount_process.AddArg("/bin/mount"); | |
| mount_process.AddArg("-o"); | |
| mount_process.AddArg("loop"); | |
| mount_process.AddArg(file.value()); | |
| mount_process.AddArg(mount_point.value()); | |
| // Close unused file descriptors in child process. | |
| mount_process.SetCloseUnusedFileDescriptors(true); | |
| // Avoid polluting the parent process' stdout. | |
| mount_process.RedirectOutput(base::FilePath("/dev/null")); | |
| int rc = mount_process.Run(); | |
| if (rc != 0) { | |
| LOG(ERROR) << "Can't mount '" << file.value() << "' to '" | |
| << mount_point.value() << "', exit status: " << rc; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool ChownDirectoryContents(const base::FilePath& dir, uid_t uid, gid_t gid) { | |
| base::FileEnumerator ent_enum(dir, false, base::FileEnumerator::FILES); | |
| for (base::FilePath path = ent_enum.Next(); !path.empty(); | |
| path = ent_enum.Next()) { | |
| if (HANDLE_EINTR(chown(path.value().c_str(), uid, gid)) < 0) { | |
| PLOG(ERROR) << "Failed to chown " << path.value(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool IncreaseNVChipSize(const base::FilePath& chip_path, size_t size) { | |
| if (HANDLE_EINTR(truncate(chip_path.value().c_str(), size)) < 0) { | |
| PLOG(ERROR) << "Failed to truncate " << chip_path.value(); | |
| return false; | |
| } | |
| brillo::ProcessImpl e2fsck_process; | |
| e2fsck_process.AddArg("/sbin/e2fsck"); | |
| e2fsck_process.AddArg("-f"); | |
| e2fsck_process.AddArg(chip_path.value()); | |
| e2fsck_process.SetCloseUnusedFileDescriptors(true); | |
| e2fsck_process.RedirectOutput(base::FilePath("/dev/null")); | |
| // Ignore the result of e2fsck. | |
| e2fsck_process.Run(); | |
| brillo::ProcessImpl resize2fs_process; | |
| resize2fs_process.AddArg("/sbin/resize2fs"); | |
| resize2fs_process.AddArg("-f"); | |
| resize2fs_process.AddArg(chip_path.value()); | |
| resize2fs_process.SetCloseUnusedFileDescriptors(true); | |
| resize2fs_process.RedirectOutput(base::FilePath("/dev/null")); | |
| if (resize2fs_process.Run() != 0) { | |
| LOG(ERROR) << "Failed to resize2fs the NVChip."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool PrepareMigrateNVChip(const base::FilePath& chip_path, | |
| int64_t chip_size, | |
| NVChipMigrateData* migrate_data) { | |
| if (chip_size == kOldNVChipSize) { | |
| if (!base::ReadFileToString(chip_path, &migrate_data->chip_data)) { | |
| LOG(ERROR) << "Failed to read the NVChip."; | |
| return false; | |
| } | |
| if (migrate_data->chip_data.size() != kOldNVChipSize) { | |
| LOG(ERROR) << "Unknown NVChip size after read."; | |
| return false; | |
| } | |
| if (!brillo::WriteStringToFile(chip_path, std::string(kNVChipSize, '\0'))) { | |
| LOG(ERROR) << "Failed to create the NVChip."; | |
| return false; | |
| } | |
| if (!FormatExt4(chip_path)) { | |
| LOG(ERROR) << "Failed to format the NVChip to ext4."; | |
| return false; | |
| } | |
| migrate_data->version = 1; | |
| return true; | |
| } else if (chip_size < kNVChipSize) { | |
| // Increase the nv chip size. | |
| if (!IncreaseNVChipSize(chip_path, kNVChipSize)) { | |
| LOG(ERROR) << "Failed to migrate NVChip to bigger size."; | |
| return false; | |
| } | |
| migrate_data->version = 2; | |
| return true; | |
| } else { | |
| LOG(ERROR) << "Unknown NVChip size."; | |
| return false; | |
| } | |
| } | |
| bool MigrateNVChip(const base::FilePath& chip_path, | |
| const base::FilePath& mount_point, | |
| const NVChipMigrateData& migrate_data) { | |
| if (migrate_data.version != 1) { | |
| // Nothing to migrate. | |
| return true; | |
| } | |
| base::FilePath new_chip_path = mount_point.Append(kNVChipPath); | |
| if (!brillo::WriteStringToFile(new_chip_path, migrate_data.chip_data)) { | |
| LOG(ERROR) << "Failed to create the NVChip."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| namespace tpm2_simulator { | |
| bool MountAndEnterNVChip() { | |
| base::FilePath chip_path(kNVChipPath); | |
| base::FilePath mount_point(kNVChipMountPoint); | |
| if (!base::PathExists(chip_path)) { | |
| if (!brillo::WriteStringToFile(chip_path, std::string(kNVChipSize, '\0'))) { | |
| LOG(ERROR) << "Failed to create the NVChip."; | |
| return false; | |
| } | |
| if (!FormatExt4(chip_path)) { | |
| LOG(ERROR) << "Failed to format the NVChip to ext4."; | |
| return false; | |
| } | |
| } | |
| int64_t chip_size = 0; | |
| if (!base::GetFileSize(chip_path, &chip_size)) { | |
| LOG(ERROR) << "Failed to get NVChip size."; | |
| return false; | |
| } | |
| bool migrate_nvchip = chip_size != kNVChipSize; | |
| NVChipMigrateData migrate_data; | |
| if (migrate_nvchip) { | |
| if (!PrepareMigrateNVChip(chip_path, chip_size, &migrate_data)) { | |
| LOG(ERROR) << "Failed to prepare migrate NVChip."; | |
| return false; | |
| } | |
| } | |
| if (!base::PathExists(mount_point)) { | |
| if (!base::CreateDirectory(mount_point)) { | |
| LOG(ERROR) << "Failed to create the NVChip mount point."; | |
| return false; | |
| } | |
| } | |
| uid_t uid; | |
| gid_t gid; | |
| if (!brillo::userdb::GetUserInfo(kSimulatorUser, &uid, &gid)) { | |
| LOG(ERROR) << "Failed to lookup the user name."; | |
| return false; | |
| } | |
| if (HANDLE_EINTR(chown(chip_path.value().c_str(), uid, gid)) < 0) { | |
| PLOG(ERROR) << "Failed to chown the NVChip."; | |
| return false; | |
| } | |
| if (HANDLE_EINTR(chown(mount_point.value().c_str(), uid, gid)) < 0) { | |
| PLOG(ERROR) << "Failed to chown the NVChip mount point."; | |
| return false; | |
| } | |
| if (HANDLE_EINTR(unshare(CLONE_NEWNS)) < 0) { | |
| PLOG(ERROR) << "Failed to unshare."; | |
| return false; | |
| } | |
| if (!MountLoopbackFile(chip_path, mount_point)) { | |
| LOG(ERROR) << "Failed to mount the NVChip."; | |
| return false; | |
| } | |
| if (HANDLE_EINTR(chown(mount_point.value().c_str(), uid, gid)) < 0) { | |
| PLOG(ERROR) << "Failed to chown the NVChip mount point after mount."; | |
| return false; | |
| } | |
| if (migrate_nvchip) { | |
| if (!MigrateNVChip(chip_path, mount_point, migrate_data)) { | |
| LOG(ERROR) << "Failed to migrate NVChip."; | |
| return false; | |
| } | |
| } | |
| if (HANDLE_EINTR(chdir(mount_point.value().c_str())) < 0) { | |
| PLOG(ERROR) << "Failed to enter the mount point."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool CorrectWorkingDirectoryFilesOwner() { | |
| uid_t uid; | |
| gid_t gid; | |
| if (!brillo::userdb::GetUserInfo(kSimulatorUser, &uid, &gid)) { | |
| LOG(ERROR) << "Failed to lookup the user name."; | |
| return false; | |
| } | |
| if (!ChownDirectoryContents(base::FilePath(kWoringDirectory), uid, gid)) { | |
| LOG(ERROR) | |
| << "Failed to chown the NVChip mount point contents after mount."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace tpm2_simulator |