blob: f04c00a964017ec6374ea1b2acf266943fafe4a9 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "init/startup/security_manager.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/rand_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <libstorage/platform/platform.h>
#include <linux/loadpin.h>
#include <openssl/sha.h>
#include "init/startup/startup_dep_impl.h"
namespace {
constexpr char kSysKernelSecurity[] = "sys/kernel/security";
constexpr char kDevNull[] = "dev/null";
constexpr char kLoadPinVerity[] = "loadpin/dm-verity";
// During CrOS build phases, this file will be produced and baked into the
// rootfs. Specifically during the DLC build flows.
constexpr char kTrustedDlcVerityDigests[] =
"opt/google/dlc/_trusted_verity_digests";
constexpr char kProcessMgmtPoliciesDir[] =
"usr/share/cros/startup/process_management_policies";
constexpr char kSafeSetIDProcessMgmtPolicies[] = "safesetid";
constexpr char kLsmInodePolicies[] =
"sys/kernel/security/chromiumos/inode_security_policies";
constexpr char kNoEarlyKeyFile[] = ".no_early_system_key";
constexpr char kSysKeyBackupFile[] = "unencrypted/preserve/system.key";
constexpr int kKeySize = SHA256_DIGEST_LENGTH;
const std::array<const char*, 3> kSymlinkExceptions = {
"var/lib/timezone",
"var/log",
"home",
};
constexpr char kSymlinkExceptionsDir[] =
"usr/share/cros/startup/symlink_exceptions";
constexpr char kFifoExceptionsDir[] = "usr/share/cros/startup/fifo_exceptions";
constexpr char kVar[] = "var";
} // namespace
namespace startup {
// Project-specific process management policies. Projects may add policies by
// adding a file under usr/share/cros/startup/process_management_policies/
// for UID's, whose contents are one or more lines specifying a parent ID
// and a child UID that the parent can use for the purposes of process
// management. There should be one line for every mapping that is to be put in
// the allow list. Lines in the file should use the following format:
// <UID>:<UID>.
//
// For example, if the 'shill' user needs to use 'dhcp', 'openvpn' and 'ipsec'
// and 'syslog' for process management, the file would look like:
// 20104:224
// 20104:217
// 20104:212
// 20104:202
//
// AccumulatePolicyFiles takes in all the files contained in the policy_dir
// reads their contents, copies and appends them to a file determined by
// output_file.
bool AccumulatePolicyFiles(libstorage::Platform* platform,
const base::FilePath& root,
const base::FilePath& output_file,
const base::FilePath& policy_dir) {
if (!platform->FileExists(output_file)) {
// securityfs files are located elsewhere, return.
return true;
}
if (!platform->DirectoryExists(policy_dir)) {
LOG(WARNING) << "Can't configure process management security. "
<< policy_dir << " not found.";
return false;
}
std::unique_ptr<libstorage::FileEnumerator> enumerator(
platform->GetFileEnumerator(policy_dir, false,
base::FileEnumerator::FileType::FILES));
std::vector<std::string> combined_policy;
for (base::FilePath file = enumerator->Next(); !file.empty();
file = enumerator->Next()) {
std::string file_str;
DLOG(INFO) << "Loading: " << file.value();
if (!platform->ReadFileToString(file, &file_str)) {
PLOG(WARNING) << "Can't read policy file " << file;
continue;
}
std::vector<std::string> split_files = base::SplitString(
file_str, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
split_files.erase(std::remove_if(split_files.begin(), split_files.end(),
[&](const std::string line) {
return base::StartsWith(line, "#");
}),
split_files.end());
combined_policy.push_back(base::JoinString(split_files, "\n"));
}
std::string combined_policy_str = base::JoinString(combined_policy, "\n");
combined_policy_str.append("\n");
DLOG(INFO) << "Applying policy to: " << output_file.value();
if (!platform->WriteStringToFile(output_file, combined_policy_str)) {
PLOG(ERROR) << output_file << ": Failed to write to file";
return false;
}
return true;
}
// Determine where securityfs files are placed.
// No inputs, checks for which securityfs file paths exist
// and accumulates files for securityfs.
bool ConfigureProcessMgmtSecurity(libstorage::Platform* platform,
const base::FilePath& root) {
DLOG(INFO) << "ConfigureProcessMgmtSecurity";
// For UID relevant files.
const base::FilePath policies_dir =
root.Append(kSysKernelSecurity).Append(kSafeSetIDProcessMgmtPolicies);
// Path to the securityfs file for configuring process management security
// policies, for UIDs, in the SafeSetID LSM (used for kernel version >= 5.9).
const base::FilePath uid_mgmt_policies =
policies_dir.Append("uid_allowlist_policy");
// Path to the securityfs file for configuring process management security
// policies in the SafeSetID LSM (used for kernel version >= 4.14)
const base::FilePath mgmt_policies = policies_dir.Append("whitelist_policy");
const base::FilePath pmpd = root.Append(kProcessMgmtPoliciesDir);
return AccumulatePolicyFiles(platform, root, uid_mgmt_policies, pmpd) &&
AccumulatePolicyFiles(platform, root, mgmt_policies, pmpd);
}
bool SetupLoadPinVerityDigests(libstorage::Platform* platform,
const base::FilePath& root,
StartupDep* startup_dep) {
const auto loadpin_verity =
root.Append(kSysKernelSecurity).Append(kLoadPinVerity);
const auto trusted_dlc_digests = root.Append(kTrustedDlcVerityDigests);
const auto dev_null = root.Append(kDevNull);
// Only try loading the trusted dm-verity root digests if:
// 1. LoadPin dm-verity attribute is supported.
// 2a. Trusted list of DLC dm-verity root digest file exists.
// 2b. Otherwise, we must feed LoadPin with an invalid digests file.
// Open (write) the LoadPin dm-verity attribute file.
if (!platform->FileExists(loadpin_verity)) {
// This means LoadPin dm-verity attribute is not supported.
// No further action is required.
return true;
}
FILE* fd = platform->OpenFile(loadpin_verity, "w");
if (!fd) {
// TODO(kimjae): Need to somehow handle this failure, as this still means
// later a digest can get fed into LoadPin.
PLOG(ERROR) << "Failed to open LoadPin verity file.";
return false;
}
// Open (read) the trusted digest file in rootfs.
FILE* digests_fd = platform->OpenFile(trusted_dlc_digests, "r");
if (!digests_fd) {
if (errno == ENOENT) {
PLOG(WARNING) << "Missing trusted DLC verity digests file.";
// NOTE: Do not return here, so invalid digests get fed into LoadPin.
} else {
PLOG(WARNING) << "Failed to open trusted DLC verity digests file.";
// NOTE: Do not return here, so invalid digests get fed into LoadPin.
}
// Any failure in loading/parsing will block subsequent feeds into LoadPin.
digests_fd = platform->OpenFile(dev_null, "r");
if (!digests_fd) {
PLOG(ERROR) << "Failed to open " << dev_null.value() << ".";
platform->CloseFile(fd);
return false;
}
LOG(WARNING) << "Forcing LoadPin to ingest /dev/null.";
}
// Write trusted digests or /dev/null into LoadPin.
int arg1 = fileno(digests_fd);
int ret = platform->Ioctl(fd, LOADPIN_IOC_SET_TRUSTED_VERITY_DIGESTS, &arg1);
if (ret != 0) {
PLOG(WARNING) << "Unable to setup trusted DLC verity digests";
}
// On success or failure:
// Subsequent `ioctl` on loadpin/dm-verity should fail as the trusted
// dm-verity root digest list is not empty or invalid digest file descriptor
// is fed into LoadPin.
platform->CloseFile(fd);
platform->CloseFile(digests_fd);
return ret == 0;
}
bool BlockSymlinkAndFifo(libstorage::Platform* platform,
const base::FilePath& root,
const std::string& path) {
base::FilePath base = root.Append(kLsmInodePolicies);
base::FilePath sym = base.Append("block_symlink");
base::FilePath fifo = base.Append("block_fifo");
bool ret = true;
if (!platform->WriteStringToFile(sym, path)) {
PLOG(WARNING) << "Failed to write to block_symlink for " << path;
ret = false;
}
if (!platform->WriteStringToFile(fifo, path)) {
PLOG(WARNING) << "Failed to write to block_fifo for " << path;
ret = false;
}
return ret;
}
// Generates a system key in test images, before the normal mount-encrypted.
// This allows us to soft-clear the TPM in integration tests w/o accidentally
// wiping encstateful after a reboot.
void CreateSystemKey(libstorage::Platform* platform,
const base::FilePath& root,
const base::FilePath& stateful,
StartupDep* startup_dep,
std::string* log_content) {
base::FilePath no_early = stateful.Append(kNoEarlyKeyFile);
base::FilePath backup = stateful.Append(kSysKeyBackupFile);
base::FilePath empty;
if (platform->FileExists(no_early)) {
log_content->append("Opt not to create a system key in advance.");
return;
}
log_content->append("Checking if a system key already exists in NVRAM...\n");
std::string output;
std::vector<std::string> mnt_enc_info = {"info"};
if (!startup_dep->MountEncrypted(mnt_enc_info, &output)) {
log_content->append(output);
log_content->append("\n");
if (output.find("NVRAM: available.") != std::string::npos) {
log_content->append("There is already a system key in NVRAM.\n");
return;
}
}
log_content->append("No system key found in NVRAM. Start creating one.\n");
// Generates 32-byte random key material and backs it up.
brillo::Blob buf(kKeySize);
base::RandBytes(buf);
if (!platform->WriteFile(backup, buf)) {
log_content->append("Failed to generate or back up system key material.\n");
return;
}
// Persists system key.
std::vector<std::string> mnt_enc_set = {"set", backup.value()};
if (!startup_dep->MountEncrypted(mnt_enc_set, &output)) {
log_content->append(output);
log_content->append("Successfully created a system key.");
}
}
bool AllowSymlink(libstorage::Platform* platform,
const base::FilePath& root,
const std::string& path) {
base::FilePath sym = root.Append(kLsmInodePolicies).Append("allow_symlink");
return platform->WriteStringToFile(sym, path);
}
bool AllowFifo(libstorage::Platform* platform,
const base::FilePath& root,
const std::string& path) {
base::FilePath fifo = root.Append(kLsmInodePolicies).Append("allow_fifo");
return platform->WriteStringToFile(fifo, path);
}
void SymlinkExceptions(libstorage::Platform* platform,
const base::FilePath& root) {
// Generic symlink exceptions.
for (auto d_it = kSymlinkExceptions.begin(); d_it != kSymlinkExceptions.end();
d_it++) {
base::FilePath d = root.Append(*d_it);
if (!platform->CreateDirectory(d)) {
PLOG(WARNING) << "mkdir failed for " << d.value();
}
if (!platform->SetPermissions(d, 0755)) {
PLOG(WARNING) << "Failed to set permissions for " << d.value();
}
AllowSymlink(platform, root, d.value());
}
}
// Project-specific exceptions. Projects may add exceptions by
// adding a file under excepts_dir whose contents contains a list
// of paths (one per line) for which an exception should be made.
// File name should use the following format:
// <project-name>-{symlink|fifo}-exceptions.txt
void ExceptionsProjectSpecific(libstorage::Platform* platform,
const base::FilePath& root,
const base::FilePath& config_dir,
bool (*callback)(libstorage::Platform* platform,
const base::FilePath& root,
const std::string& path)) {
if (platform->DirectoryExists(config_dir)) {
std::unique_ptr<libstorage::FileEnumerator> iter(
platform->GetFileEnumerator(config_dir, false,
base::FileEnumerator::FileType::FILES));
for (base::FilePath path_file = iter->Next(); !path_file.empty();
path_file = iter->Next()) {
if (!platform->FileExists(path_file)) {
continue;
}
std::string contents;
if (!platform->ReadFileToString(path_file, &contents)) {
PLOG(WARNING) << "Can't open exceptions file " << path_file.value();
continue;
}
std::vector<std::string> files = base::SplitString(
contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& path : files) {
if (path.find("#") == 0) {
continue;
} else {
base::FilePath p(path);
if (!platform->CreateDirectory(p)) {
PLOG(WARNING) << "mkdir failed for " << path;
}
if (!platform->SetPermissions(p, 0755)) {
PLOG(WARNING) << "Failed to set permissions for " << path;
}
callback(platform, root, path);
}
}
}
}
}
// Set up symlink traversal and FIFO blocking policy, and project
// specific symlink and FIFO exceptions.
void ConfigureFilesystemExceptions(libstorage::Platform* platform,
const base::FilePath& root) {
// Set up symlink traversal and FIFO blocking policy for /var, which may
// reside on a separate file system than /mnt/stateful_partition. Block
// symlink traversal and opening of FIFOs by default, but allow exceptions
// in the few instances where they are used intentionally.
BlockSymlinkAndFifo(platform, root, root.Append(kVar).value());
SymlinkExceptions(platform, root);
// Project-specific symlink exceptions. Projects may add exceptions by
// adding a file under /usr/share/cros/startup/symlink_exceptions/ whose
// contents contains a list of paths (one per line) for which an exception
// should be made. File name should use the following format:
// <project-name>-symlink-exceptions.txt
base::FilePath sym_excepts = root.Append(kSymlinkExceptionsDir);
ExceptionsProjectSpecific(platform, root, sym_excepts, &AllowSymlink);
// Project-specific FIFO exceptions. Projects may add exceptions by adding
// a file under /usr/share/cros/startup/fifo_exceptions/ whose contents
// contains a list of paths (one per line) for which an exception should be
// made. File name should use the following format:
// <project-name>-fifo-exceptions.txt
base::FilePath fifo_excepts = root.Append(kFifoExceptionsDir);
ExceptionsProjectSpecific(platform, root, fifo_excepts, &AllowFifo);
}
} // namespace startup