blob: 911a1a08e112f38aca4f570cd648cd49b3533896 [file] [log] [blame] [edit]
// 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