blob: 6b6b1c0a0aac774cccbe3e32aa8a0b63bc809d55 [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/sysfs_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 <set>
#include <string>
#include <sysexits.h>
namespace pciguard {
namespace {
// 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
"igb", // Intel Gigabit Ethernet driver
};
} // namespace
SysfsUtils::SysfsUtils() : SysfsUtils(FilePath("/")) {}
SysfsUtils::SysfsUtils(FilePath root)
: allowlist_path_(root.Append("sys/bus/pci/drivers_allowlist")),
pci_lockdown_path_(root.Append("sys/bus/pci/drivers_allowlist_lockdown")),
pci_rescan_path_(root.Append("sys/bus/pci/rescan")),
tbt_devices_path_(root.Append("sys/bus/thunderbolt/devices")),
pci_devices_path_(root.Append("sys/bus/pci/devices")) {}
int SysfsUtils::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 SysfsUtils::DeauthorizeThunderboltDev(base::FilePath devpath) {
return SetAuthorizedAttribute(devpath, false);
}
int SysfsUtils::OnInit(void) {
if (!base::PathIsWritable(allowlist_path_) ||
!base::PathIsWritable(pci_lockdown_path_)) {
PLOG(ERROR) << "Kernel is missing needed support for external PCI security";
return EX_OSFILE;
}
if (base::WriteFile(pci_lockdown_path_, "1", 1) != 1) {
PLOG(ERROR) << "Couldn't write 1 to " << pci_lockdown_path_;
return EX_IOERR;
}
for (auto drvr_name : kAllowlist) {
if (base::WriteFile(allowlist_path_, 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 SysfsUtils::AuthorizeThunderboltDev(base::FilePath devpath) {
return SetAuthorizedAttribute(devpath, true);
}
int SysfsUtils::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(pci_lockdown_path_, "0", 1) != 1) {
PLOG(ERROR) << "Couldn't write 0 to " << pci_lockdown_path_;
return EXIT_FAILURE;
}
int ret = EXIT_SUCCESS;
// Add any PCI devices that we removed when the user had logged off.
if (base::WriteFile(pci_rescan_path_, "1", 1) != 1) {
PLOG(ERROR) << "Couldn't write 1 to " << pci_rescan_path_;
ret = EXIT_FAILURE;
}
// Create an BFS ordered set of thunderbolt devices.
auto cmp = [](const base::FilePath& dev1, const base::FilePath& dev2) {
base::FilePath symlink1, symlink2;
(void)base::ReadSymbolicLink(dev1, &symlink1);
(void)base::ReadSymbolicLink(dev2, &symlink2);
return symlink1 < symlink2;
};
std::set<base::FilePath, decltype(cmp)> thunderbolt_devs(cmp);
base::FileEnumerator iter(tbt_devices_path_, false,
base::FileEnumerator::DIRECTORIES);
for (auto devpath = iter.Next(); !devpath.empty(); devpath = iter.Next())
thunderbolt_devs.insert(devpath);
// Authorize the thunderbolt devices in BFS order (sorting using the
// symlinks to which the devices point, gives us BFS). This is
// required because if a parent is deauthorized, the children are
// automatically deauthorized, but vice versa is not true.
for (auto dev : thunderbolt_devs) {
if (AuthorizeThunderboltDev(dev))
ret = EXIT_FAILURE;
}
return ret;
}
int SysfsUtils::DenyNewDevices(void) {
LOG(INFO) << "Will deny all new external PCI devices";
// Deny drivers to bind to any *new* external PCI devices.
if (base::WriteFile(pci_lockdown_path_, "1", 1) != 1) {
PLOG(ERROR) << "Couldn't write 1 to " << pci_lockdown_path_;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int SysfsUtils::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(pci_devices_path_, 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(tbt_devices_path_, 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