| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "minios/log_store_manager.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| |
| #include <base/files/file.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <brillo/blkdev_utils/storage_utils.h> |
| #include <brillo/file_utils.h> |
| #include <brillo/files/file_util.h> |
| #include <brillo/secure_blob.h> |
| #include <libcrossystem/crossystem.h> |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> |
| #include <minios/proto_bindings/minios.pb.h> |
| |
| #include "minios/cgpt_util.h" |
| #include "minios/disk_util.h" |
| #include "minios/log_store_manifest.h" |
| #include "minios/utils.h" |
| |
| namespace minios { |
| |
| // Offset from end of partition to store encrypted logs. |
| const uint64_t kLogStoreOffset = 22 * kBlockSize; |
| // Max allowable size of a log when saving to disk. |
| const uint64_t kMaxLogSize = 20 * kBlockSize; |
| // Strip `/var/log` folder paths when extracting. |
| const char kTarStripComponentFlag[] = "--strip-components=2"; |
| |
| bool LogStoreManager::Init(std::shared_ptr<DiskUtil> disk_util, |
| std::shared_ptr<crossystem::Crossystem> cros_system, |
| std::shared_ptr<CgptWrapperInterface> cgpt_wrapper) { |
| // Identify the fixed drive along with active MiniOS side to determine the |
| // current partition. |
| const auto& fixed_drive = disk_util->GetFixedDrive(); |
| if (fixed_drive.empty()) { |
| LOG(ERROR) << "Couldn't find fixed drive."; |
| return false; |
| } |
| |
| if (!partition_number_) |
| partition_number_ = GetMiniOsPriorityPartition(cros_system); |
| |
| if (!partition_number_) { |
| LOG(ERROR) << "Failed to find priority MiniOS partition."; |
| return false; |
| } |
| disk_path_ = brillo::AppendPartition(fixed_drive, partition_number_.value()); |
| |
| const auto cgpt_util = std::make_shared<CgptUtil>(fixed_drive, cgpt_wrapper); |
| partition_size_ = GetPartitionSize(partition_number_.value(), cgpt_util); |
| |
| if (!partition_size_) { |
| LOG(ERROR) << "Couldn't determine size of partition=" |
| << partition_number_.value(); |
| return false; |
| } |
| |
| // Determine the kernel size and partition size to ensure our disk operations |
| // are always at a valid location. |
| kernel_size_ = KernelSize(process_manager_, disk_path_); |
| if (!kernel_size_) { |
| LOG(ERROR) << "Could not determine kernel size on partition: " |
| << disk_path_; |
| return false; |
| } |
| |
| // Ensure that the log store does not encroach kernel space on disk. |
| if (kernel_size_.value() > (partition_size_.value() - kLogStoreOffset)) { |
| LOG(ERROR) << "Kernel overlaps with log store."; |
| return false; |
| } |
| |
| log_store_manifest_ = std::make_unique<LogStoreManifest>( |
| disk_path_, kernel_size_.value(), partition_size_.value()); |
| |
| return true; |
| } |
| |
| bool LogStoreManager::SaveLogs(LogDirection direction, |
| const std::optional<base::FilePath>& path) { |
| if ((direction == LogDirection::RemovableDevice || |
| direction == LogDirection::Stateful) && |
| !path) { |
| LOG(ERROR) |
| << "Path must be specified when saving logs to stateful or device."; |
| return false; |
| } |
| |
| base::ScopedTempDir archive_folder; |
| base::FilePath archive_path; |
| if (!archive_folder.CreateUniqueTempDir() || !archive_folder.IsValid()) { |
| LOG(ERROR) << "Failed to create temp dir."; |
| return false; |
| } |
| if (!base::CreateTemporaryFileInDir(archive_folder.GetPath(), |
| &archive_path)) { |
| LOG(ERROR) << "Failed to create temporary file in folder=" |
| << archive_folder.GetPath(); |
| return false; |
| } |
| |
| if (CompressLogs(process_manager_, archive_path) != 0) { |
| LOG(ERROR) << "Failed to compress logs."; |
| return false; |
| } |
| |
| std::optional<EncryptedLogFile> encrypted_archive; |
| if (direction == LogDirection::Disk || direction == LogDirection::Stateful) { |
| encrypted_archive = EncryptLogs(archive_path); |
| if (!encrypted_archive || !encrypt_key_) { |
| LOG(ERROR) << "Couldn't encrypt logs"; |
| return false; |
| } |
| if (!SaveLogStoreKey(vpd_, encrypt_key_.value())) { |
| LOG(ERROR) << "Failed to save key."; |
| return false; |
| } |
| } |
| |
| switch (direction) { |
| case LogDirection::Disk: |
| if (!SaveLogsToDisk(encrypted_archive.value())) { |
| LOG(ERROR) << "Failed to save logs to disk."; |
| return false; |
| } |
| return true; |
| case LogDirection::Stateful: |
| if (!SaveLogsToPath(path.value(), encrypted_archive.value())) { |
| LOG(ERROR) << "Failed to save logs to stateful path=" << path->value(); |
| return false; |
| } |
| return true; |
| case LogDirection::RemovableDevice: |
| if (!base::Move(archive_path, path.value())) { |
| PLOG(ERROR) << "Failed to move file from=" << archive_path |
| << " to=" << path.value(); |
| return false; |
| } |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| std::optional<bool> LogStoreManager::FetchLogs( |
| LogDirection direction, |
| const base::FilePath& dest_directory, |
| const brillo::SecureBlob& key, |
| const std::optional<base::FilePath>& encrypted_archive_path) const { |
| EncryptedLogFile encrypted_archive; |
| // If read resulted in errors, or no logs were found, return immediately. |
| if (const auto read_result = |
| ReadLogs(direction, encrypted_archive_path, encrypted_archive); |
| !read_result || !read_result.value()) { |
| return read_result; |
| } |
| |
| // If the key is zero'd out then the log store is presumed to be cleared, |
| // return without decrypting. |
| if (key == kNullKey) { |
| LOG(INFO) << "No key found."; |
| return false; |
| } |
| const auto& archive = DecryptLogArchive(encrypted_archive, key); |
| // If logs can't be decrypted, report that no logs were fetched. |
| if (!archive) { |
| return false; |
| } |
| return ExtractLogs(archive.value(), dest_directory); |
| } |
| |
| std::optional<EncryptedLogFile> LogStoreManager::EncryptLogs( |
| const base::FilePath& archive_path) { |
| encrypt_key_ = |
| hwsec_foundation::CreateSecureRandomBlob(kLogStoreKeySizeBytes); |
| |
| const auto& archive = ReadFileToSecureBlob(archive_path); |
| if (!archive) { |
| LOG(ERROR) << "Failed to read log archive."; |
| return std::nullopt; |
| } |
| const auto& encrypted_archive = |
| EncryptLogArchive(archive.value(), encrypt_key_.value()); |
| if (!encrypted_archive) { |
| LOG(ERROR) << "Failed to encrypt logs."; |
| return std::nullopt; |
| } |
| return encrypted_archive; |
| } |
| |
| bool LogStoreManager::SaveLogsToDisk( |
| const EncryptedLogFile& encrypted_archive) { |
| if (encrypted_archive.ByteSizeLong() > kMaxLogSize) { |
| LOG(WARNING) << "Encrypted compressed logs exceed reserved disk space."; |
| return false; |
| } |
| |
| LogManifest::Entry entry; |
| entry.set_offset(partition_size_.value() - kLogStoreOffset); |
| entry.set_count(encrypted_archive.ByteSizeLong()); |
| |
| if (!log_store_manifest_) { |
| LOG(ERROR) << "No log store manifest, unable to store logs to disk."; |
| return false; |
| } |
| log_store_manifest_->Generate(entry); |
| |
| base::File file{disk_path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE}; |
| if (!file.IsValid()) { |
| PLOG(ERROR) << "Couldn't open file=" << disk_path_; |
| return false; |
| } |
| if (!file.WriteAndCheck(partition_size_.value() - kLogStoreOffset, |
| {reinterpret_cast<const uint8_t*>( |
| encrypted_archive.SerializeAsString().c_str()), |
| encrypted_archive.SerializeAsString().size()})) { |
| PLOG(ERROR) << "Failed to write to file=" << disk_path_; |
| return false; |
| } |
| |
| if (!log_store_manifest_->Write()) { |
| LOG(ERROR) << "Failed to write manifest to disk."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool LogStoreManager::SaveLogsToPath( |
| const base::FilePath& path, const EncryptedLogFile& encrypted_archive) { |
| if (!brillo::WriteStringToFile(path, encrypted_archive.SerializeAsString())) { |
| PLOG(ERROR) << "Failed to write to file=" << path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::optional<EncryptedLogFile> LogStoreManager::GetEncryptedArchive( |
| const base::FilePath& path, uint64_t offset) const { |
| base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY))); |
| if (!fd.is_valid()) { |
| LOG(ERROR) << "Failed to open archive at: " << path; |
| return std::nullopt; |
| } |
| if (offset > 0 && lseek(fd.get(), offset, SEEK_SET) != offset) { |
| PLOG(ERROR) << "Failed to seek=" << path; |
| return std::nullopt; |
| } |
| |
| EncryptedLogFile encrypted_archive; |
| encrypted_archive.ParseFromFileDescriptor(fd.get()); |
| return encrypted_archive; |
| } |
| |
| bool LogStoreManager::ExtractLogs(const brillo::SecureBlob& archive, |
| const base::FilePath& dest_directory) const { |
| base::ScopedTempDir archive_folder; |
| base::FilePath archive_path; |
| if (!archive_folder.CreateUniqueTempDir() || !archive_folder.IsValid()) { |
| LOG(ERROR) << "Failed to create temp dir."; |
| return false; |
| } |
| if (!base::CreateTemporaryFileInDir(archive_folder.GetPath(), |
| &archive_path)) { |
| LOG(ERROR) << "Failed to create temporary file in folder=" |
| << archive_folder.GetPath(); |
| return false; |
| } |
| if (!WriteSecureBlobToFile(archive_path, archive)) { |
| LOG(ERROR) << "Failed to write blob to=" << archive_path; |
| return false; |
| } |
| if (!ExtractArchive(process_manager_, archive_path, dest_directory, |
| {kTarStripComponentFlag})) { |
| LOG(ERROR) << "Extracting logs failed."; |
| return false; |
| } |
| return true; |
| } |
| |
| std::optional<bool> LogStoreManager::ReadLogs( |
| LogDirection direction, |
| const std::optional<base::FilePath>& encrypted_archive_path, |
| EncryptedLogFile& encrypted_archive) const { |
| std::optional<EncryptedLogFile> read_archive; |
| switch (direction) { |
| case LogDirection::Disk: { |
| if (!log_store_manifest_) { |
| LOG(ERROR) << "No log store manifest, unable to fetch logs."; |
| return std::nullopt; |
| } |
| // If no manifest is present, then no logs are assumed to be stored on |
| // this partition. |
| const auto& manifest = log_store_manifest_->Retrieve(); |
| if (!manifest) { |
| LOG(INFO) << "No manifest found, no logs retrieved."; |
| return false; |
| } |
| |
| if (manifest->entry().offset() <= kernel_size_.value()) { |
| LOG(ERROR) << "Log store within kernel offset, log store offset=" |
| << manifest->entry().offset() |
| << " kernel size=" << kernel_size_.value(); |
| return std::nullopt; |
| } |
| read_archive = |
| GetEncryptedArchive(disk_path_, manifest->entry().offset()); |
| break; |
| } |
| case LogDirection::Stateful: |
| if (!encrypted_archive_path) { |
| LOG(ERROR) << "Path must be specified for fetching stateful logs."; |
| return std::nullopt; |
| } |
| // If no logs are present at specified path, assume they were already |
| // cleared. |
| if (!base::PathExists(encrypted_archive_path.value())) { |
| LOG(INFO) << "No logs present at=" << encrypted_archive_path.value(); |
| return false; |
| } |
| |
| read_archive = GetEncryptedArchive(encrypted_archive_path.value()); |
| break; |
| case LogDirection::RemovableDevice: |
| LOG(ERROR) << "Fetching logs from removable device not supported."; |
| return std::nullopt; |
| } |
| if (!read_archive) { |
| LOG(ERROR) << "Failed to fetch encrypted archive."; |
| return std::nullopt; |
| } |
| encrypted_archive = read_archive.value(); |
| return true; |
| } |
| |
| bool LogStoreManager::ClearLogs() const { |
| if (!log_store_manifest_) { |
| LOG(ERROR) << "No log store manifest, unable to clear logs."; |
| return false; |
| } |
| auto manifest = log_store_manifest_->Retrieve(); |
| if (!manifest) { |
| LOG(INFO) << "No manifest found on disk, nothing to clear."; |
| return true; |
| } |
| |
| if (manifest->entry().offset() <= kernel_size_.value()) { |
| LOG(ERROR) |
| << "Skipping clear. Log store within kernel offset, log store offset=" |
| << manifest->entry().offset() |
| << " kernel size=" << kernel_size_.value(); |
| return false; |
| } |
| |
| const std::string clear_data( |
| partition_size_.value() - manifest->entry().offset(), '0'); |
| |
| base::File file{disk_path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE}; |
| if (!file.IsValid()) { |
| PLOG(ERROR) << "Couldn't open file=" << disk_path_; |
| return false; |
| } |
| if (!file.WriteAndCheck(manifest->entry().offset(), |
| {reinterpret_cast<const uint8_t*>(clear_data.c_str()), |
| clear_data.size()})) { |
| PLOG(ERROR) << "Failed to clear disk."; |
| return false; |
| } |
| |
| log_store_manifest_->Clear(); |
| return true; |
| } |
| |
| } // namespace minios |