blob: 8abdb0e2ab74b4427365f8c474b4fc6f416e6cbc [file] [log] [blame]
// Copyright 2020 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 "pciguard/pciguard_utils.h"
#include <base/command_line.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <brillo/syslog_logging.h>
#include <string>
#include <sysexits.h>
namespace pciguard {
namespace {
// Sysfs driver allowlist file (contains drivers that are allowlisted for
// external PCI devices)).
constexpr char kAllowlistPath[] = "/sys/bus/pci/drivers_allowlist";
// Sysfs PCI lockdown file. When set to 1, this prevents any driver to bind to
// external PCI devices (including allowlisted drivers).
constexpr char kExtPCILockdownPath[] =
"/sys/bus/pci/drivers_allowlist_lockdown";
// Sysfs PCI rescan file. It rescans the PCI bus to discover any new devices.
constexpr char kPCIRescanPath[] = "/sys/bus/pci/rescan";
// Actual driver allowlist.
const char* kAllowlist[] = {
// TODO(b/163121310): Finalize allowlist
"pcieport", // PCI Core services - AER, Hotplug etc.
"xhci_hcd", // XHCI host controller driver.
"nvme", // PCI Express NVME host controller driver.
"ahci", // AHCI driver
};
int SetAuthorizedAttribute(base::FilePath devpath, bool enable) {
if (!PathExists(devpath)) {
PLOG(ERROR) << "Path doesn't exist : " << devpath;
return EXIT_FAILURE;
}
base::FilePath symlink;
// Check it is a thunderbolt path
if (!base::ReadSymbolicLink(devpath.Append("subsystem"), &symlink) ||
!base::EndsWith(symlink.value(), "/bus/thunderbolt",
base::CompareCase::SENSITIVE)) {
LOG(ERROR) << "Not a thunderbolt devpath: " << devpath;
return EXIT_FAILURE;
}
base::FilePath authorized_path = devpath.Append("authorized");
std::string authorized;
// Proceed only if authorized file exists
if (!base::ReadFileToString(authorized_path, &authorized))
return EXIT_SUCCESS;
// Nevermind if no need to change the state.
if (!authorized.empty() &&
((enable && authorized[0] != '0') || (!enable && authorized[0] == '0')))
return EXIT_SUCCESS;
auto val = "0";
if (enable) {
LOG(INFO) << "Authorizing:" << devpath;
val = "1";
} else {
LOG(INFO) << "Deauthorizing:" << devpath;
}
if (base::WriteFile(authorized_path, val, 1) != 1) {
PLOG(ERROR) << "Couldn't write " << val << " to " << authorized_path;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int DeauthorizeThunderboltDev(base::FilePath devpath) {
return SetAuthorizedAttribute(devpath, false);
}
} // namespace
int OnInit(void) {
if (!base::PathIsWritable(base::FilePath(kAllowlistPath)) ||
!base::PathIsWritable(base::FilePath(kExtPCILockdownPath))) {
PLOG(ERROR) << "Kernel is missing needed support for external PCI security";
return EX_OSFILE;
}
if (base::WriteFile(base::FilePath(kExtPCILockdownPath), "1", 1) != 1) {
PLOG(ERROR) << "Couldn't write 1 to " << kExtPCILockdownPath;
return EX_IOERR;
}
const base::FilePath allowlist_file(kAllowlistPath);
for (auto drvr_name : kAllowlist) {
if (base::WriteFile(allowlist_file, drvr_name, sizeof(drvr_name)) ==
sizeof(drvr_name))
LOG(INFO) << "Allowed " << drvr_name;
else
PLOG(ERROR) << "Couldn't allow " << drvr_name;
}
return EX_OK;
}
int AuthorizeThunderboltDev(base::FilePath devpath) {
return SetAuthorizedAttribute(devpath, true);
}
int AuthorizeAllDevices(void) {
LOG(INFO) << "Authorizing all external PCI devices";
// Allow drivers to bind to PCI devices. This also binds any PCI devices
// that may have been hotplugged "into" external peripherals, while the
// screen was locked.
if (base::WriteFile(base::FilePath(kExtPCILockdownPath), "0", 1) != 1) {
PLOG(ERROR) << "Couldn't write 0 to " << kExtPCILockdownPath;
return EXIT_FAILURE;
}
int ret = EXIT_SUCCESS;
// Add any PCI devices that we removed when the user had logged off.
if (base::WriteFile(base::FilePath(kPCIRescanPath), "1", 1) != 1) {
PLOG(ERROR) << "Couldn't write 1 to " << kPCIRescanPath;
ret = EXIT_FAILURE;
}
base::FileEnumerator iter(base::FilePath("/sys/bus/thunderbolt/devices"),
false, base::FileEnumerator::DIRECTORIES);
for (auto devpath = iter.Next(); !devpath.empty(); devpath = iter.Next()) {
// Authorize the device. This takes care of any thunderbolt peripherals
// that were added while the screen was locked.
if (AuthorizeThunderboltDev(devpath))
ret = EXIT_FAILURE;
}
return ret;
}
int DenyNewDevices(void) {
LOG(INFO) << "Will deny all new external PCI devices";
// Deny drivers to bind to any *new* external PCI devices.
if (base::WriteFile(base::FilePath(kExtPCILockdownPath), "1", 1) != 1) {
PLOG(ERROR) << "Couldn't write 1 to " << kExtPCILockdownPath;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int DeauthorizeAllDevices(void) {
int ret = EXIT_SUCCESS;
if (DenyNewDevices())
return EXIT_FAILURE;
LOG(INFO) << "Deauthorizing all external PCI devices";
// Remove all untrusted (external) PCI devices.
base::FileEnumerator iter(base::FilePath("/sys/bus/pci/devices"), false,
base::FileEnumerator::DIRECTORIES);
for (auto devpath = iter.Next(); !devpath.empty(); devpath = iter.Next()) {
std::string untrusted;
// It is possible this device may already been have removed (as an effect
// of its parent being removed).
if (!PathExists(devpath))
continue;
// Proceed only if there is an "untrusted" file.
if (!base::ReadFileToString(devpath.Append("untrusted"), &untrusted) ||
untrusted.empty()) {
PLOG(ERROR) << "Couldn't read " << devpath << "/untrusted";
ret = EXIT_FAILURE;
continue;
}
// Nevermind the trusted devices.
if (untrusted[0] == '0')
continue;
// Remove untrusted device.
if (base::WriteFile(devpath.Append("remove"), "1", 1) != 1) {
PLOG(ERROR) << "Couldn't remove untrusted device " << devpath;
ret = EXIT_FAILURE;
}
}
// Deauthorize all thunderbolt devices.
base::FileEnumerator tbt_iter(base::FilePath("/sys/bus/thunderbolt/devices"),
false, base::FileEnumerator::DIRECTORIES);
for (auto devpath = tbt_iter.Next(); !devpath.empty();
devpath = tbt_iter.Next()) {
if (DeauthorizeThunderboltDev(devpath))
ret = EXIT_FAILURE;
}
return ret;
}
} // namespace pciguard