blob: 450bb3edb2564a8d8725c65a3ca278318280caf4 [file] [log] [blame]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "usb_bouncer/util.h"
#include "usb_bouncer/util_internal.h"
#include <fcntl.h>
#include <sys/capability.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#include <cstring>
#include <string_view>
#include <utility>
#include <vector>
#include <base/base64.h>
#include <base/check.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/process/launch.h>
#include <base/scoped_generic.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <brillo/cryptohome.h>
#include <brillo/file_utils.h>
#include <brillo/files/file_util.h>
#include <brillo/files/scoped_dir.h>
#include <brillo/key_value_store.h>
#include <brillo/userdb_utils.h>
#include <metrics/structured_events.h>
#include <openssl/sha.h>
#include <re2/re2.h>
#include <session_manager/dbus-proxies.h>
#include <usbguard/Device.hpp>
#include <usbguard/DeviceManager.hpp>
#include <usbguard/DeviceManagerHooks.hpp>
#include "usb_bouncer/metrics_allowlist.h"
using brillo::GetFDPath;
using brillo::SafeFD;
using brillo::ScopedDIR;
using org::chromium::SessionManagerInterfaceProxy;
namespace usb_bouncer {
namespace {
constexpr int kDbPermissions = S_IRUSR | S_IWUSR;
constexpr int kDbDirPermissions = S_IRUSR | S_IWUSR | S_IXUSR;
constexpr char kSysFSAuthorizedDefault[] = "authorized_default";
constexpr char kSysFSAuthorized[] = "authorized";
constexpr char kSysFSEnabled[] = "1";
constexpr char kUmaDeviceAttachedHistogram[] = "ChromeOS.USB.DeviceAttached";
constexpr char kUmaExternalDeviceAttachedHistogram[] =
"ChromeOS.USB.ExternalDeviceAttached";
enum class Subsystem {
kNone,
kUsb,
};
// Returns base64 encoded strings since proto strings must be valid UTF-8.
std::string EncodeDigest(const std::vector<uint8_t>& digest) {
std::string_view digest_view(reinterpret_cast<const char*>(digest.data()),
digest.size());
return base::Base64Encode(digest_view);
}
std::unique_ptr<SessionManagerInterfaceProxy> SetUpDBus(
scoped_refptr<dbus::Bus> bus) {
if (!bus) {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus = new dbus::Bus(options);
CHECK(bus->Connect());
}
return std::make_unique<SessionManagerInterfaceProxy>(bus);
}
class UsbguardDeviceManagerHooksImpl : public usbguard::DeviceManagerHooks {
public:
void dmHookDeviceEvent(usbguard::DeviceManager::EventType event,
std::shared_ptr<usbguard::Device> device) override {
lastRule_ = *device->getDeviceRule(false /*include_port*/,
false /*with_parent_hash*/);
// If usbguard-daemon is running when a device is connected, it might have
// blocked the particular device in which case this will be a block rule.
// For the purpose of allow-listing, this needs to be an Allow rule.
lastRule_.setTarget(usbguard::Rule::Target::Allow);
}
uint32_t dmHookAssignID() override {
static uint32_t id = 0;
return id++;
}
void dmHookDeviceException(const std::string& message) override {
LOG(ERROR) << message;
}
std::string getLastRule() {
if (!lastRule_) {
return "";
}
return lastRule_.toString();
}
private:
usbguard::Rule lastRule_;
};
constexpr bool IsSkippableFailure(int err) {
// EPIPE: wireless USB device that fails in usb_get_device_descriptor().
// ENODEV: device that disappears before they can be authorized or fails
// during usb_autoresume_device()
// EPROTO: usb_set_configuration() failed, but the device is still
// authorized. This is often caused by the device not having adequate
// power.
// ENOENT: the path does not exist.
return (err == EPIPE || err == ENODEV || err == EPROTO || err == ENOENT);
}
bool WriteWithTimeoutIfExists(SafeFD* dir,
const base::FilePath name,
const std::string& value) {
SafeFD::Error err;
SafeFD file;
errno = 0;
std::tie(file, err) =
dir->OpenExistingFile(name, O_CLOEXEC | O_RDWR | O_NONBLOCK);
if (err == SafeFD::Error::kDoesNotExist) {
return true;
} else if (SafeFD::IsError(err)) {
PLOG(ERROR) << "Failed to open '" << GetFDPath(dir->get()).value() << "'";
return false;
}
return WriteWithTimeout(&file, value);
}
// This opens a subdirectory represented by a directory entry if it points to a
// subdirectory.
SafeFD::SafeFDResult OpenIfSubdirectory(SafeFD* parent,
const struct stat& parent_info,
const dirent& entry) {
if (strcmp(entry.d_name, ".") == 0 || strcmp(entry.d_name, "..") == 0) {
return std::make_pair(SafeFD(), SafeFD::Error::kNoError);
}
if (entry.d_type != DT_DIR) {
return std::make_pair(SafeFD(), SafeFD::Error::kNoError);
}
struct stat child_info;
if (fstatat(parent->get(), entry.d_name, &child_info,
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW) != 0) {
PLOG(ERROR) << "fstatat failed for '" << GetFDPath(parent->get()).value()
<< "/" << entry.d_name << "'";
return std::make_pair(SafeFD(), SafeFD::Error::kIOError);
}
if (child_info.st_dev != parent_info.st_dev) {
// Do not cross file system boundary.
return std::make_pair(SafeFD(), SafeFD::Error::kBoundaryDetected);
}
SafeFD::SafeFDResult subdir =
parent->OpenExistingDir(base::FilePath(entry.d_name));
if (SafeFD::IsError(subdir.second)) {
LOG(ERROR) << "Failed to open '" << GetFDPath(parent->get()).value() << "/"
<< entry.d_name << "'";
}
return subdir;
}
// dir is the path being walked.
// sub is used to exclude authorized attributes for devices that shouldn't be
// touched.
// max_depth is used to limit the recursion.
bool AuthorizeAllImpl(SafeFD* dir,
Subsystem subsystem = Subsystem::kNone,
size_t max_depth = SafeFD::kDefaultMaxPathDepth) {
if (max_depth == 0) {
LOG(ERROR) << "AuthorizeAll read max depth at '"
<< GetFDPath(dir->get()).value() << "'";
return false;
}
bool success = true;
if (subsystem == Subsystem::kUsb) {
if (!WriteWithTimeoutIfExists(dir, base::FilePath(kSysFSAuthorized),
kSysFSEnabled)) {
if (!IsSkippableFailure(errno)) {
PLOG(ERROR) << "Failed to authorize USB device: '"
<< GetFDPath(dir->get()).value() << "'";
success = false;
}
}
if (!WriteWithTimeoutIfExists(dir, base::FilePath(kSysFSAuthorizedDefault),
kSysFSEnabled)) {
if (!IsSkippableFailure(errno)) {
success = false;
}
}
}
// The ScopedDIR takes ownership of this so dup_fd is not scoped on its own.
int dup_fd = dup(dir->get());
if (dup_fd < 0) {
PLOG(ERROR) << "dup failed for '" << GetFDPath(dir->get()).value() << "'";
return success && IsSkippableFailure(errno);
}
ScopedDIR listing(fdopendir(dup_fd));
if (!listing.is_valid()) {
PLOG(ERROR) << "fdopendir failed for '" << GetFDPath(dir->get()).value()
<< "'";
IGNORE_EINTR(close(dup_fd));
return success && IsSkippableFailure(errno);
}
struct stat dir_info;
if (fstat(dir->get(), &dir_info) != 0) {
return success && IsSkippableFailure(errno);
}
for (;;) {
errno = 0;
const dirent* entry = HANDLE_EINTR_IF_EQ(readdir(listing.get()), nullptr);
if (entry == nullptr) {
break;
}
errno = 0;
SafeFD::SafeFDResult subdir = OpenIfSubdirectory(dir, dir_info, *entry);
if (SafeFD::IsError(subdir.second) && !IsSkippableFailure(errno)) {
success = false;
}
Subsystem child_subsystem = subsystem;
if (base::StartsWith(entry->d_name, "usb", base::CompareCase::SENSITIVE)) {
child_subsystem = Subsystem::kUsb;
}
if (subdir.first.is_valid()) {
if (!AuthorizeAllImpl(&subdir.first, child_subsystem, max_depth - 1)) {
success = false;
}
}
}
if (errno != 0) {
PLOG(ERROR) << "readdir failed for '" << GetFDPath(dir->get()).value()
<< "'";
return success && IsSkippableFailure(errno);
}
// Check sub directories
return success;
}
UMADeviceClass GetClassEnumFromValue(
const usbguard::USBInterfaceType& interface) {
const struct {
uint8_t raw;
UMADeviceClass typed;
} mapping[] = {
// clang-format off
{0x01, UMADeviceClass::kAudio},
{0x03, UMADeviceClass::kHID},
{0x02, UMADeviceClass::kComm},
{0x05, UMADeviceClass::kPhys},
{0x06, UMADeviceClass::kImage},
{0x07, UMADeviceClass::kPrint},
{0x08, UMADeviceClass::kStorage},
{0x09, UMADeviceClass::kHub},
{0x0A, UMADeviceClass::kComm},
{0x0B, UMADeviceClass::kCard},
{0x0D, UMADeviceClass::kSec},
{0x0E, UMADeviceClass::kVideo},
{0x0F, UMADeviceClass::kHealth},
{0x10, UMADeviceClass::kAV},
{0xE0, UMADeviceClass::kWireless},
{0xEF, UMADeviceClass::kMisc},
{0xFE, UMADeviceClass::kApp},
{0xFF, UMADeviceClass::kVendor},
// clang-format on
};
for (const auto& m : mapping) {
if (usbguard::USBInterfaceType(m.raw, 0, 0,
usbguard::USBInterfaceType::MatchClass)
.appliesTo(interface)) {
return m.typed;
}
}
return UMADeviceClass::kOther;
}
UMADeviceClass MergeClasses(UMADeviceClass a, UMADeviceClass b) {
if (a == b) {
return a;
}
if ((a == UMADeviceClass::kAV || a == UMADeviceClass::kAudio ||
a == UMADeviceClass::kVideo) &&
(b == UMADeviceClass::kAV || b == UMADeviceClass::kAudio ||
b == UMADeviceClass::kVideo)) {
return UMADeviceClass::kAV;
}
return UMADeviceClass::kOther;
}
struct ScopedCapTTraits {
static cap_t InvalidValue() { return nullptr; }
static void Free(cap_t cap_ptr) { cap_free(cap_ptr); }
};
typedef base::ScopedGeneric<cap_t, ScopedCapTTraits> ScopedCapT;
} // namespace
bool CanChown() {
ScopedCapT caps(cap_get_pid(0));
if (!caps.is_valid()) {
return false;
}
cap_flag_value_t value;
if (cap_get_flag(caps.get(), CAP_CHOWN, CAP_EFFECTIVE, &value) == -1) {
return false;
}
return value == CAP_SET;
}
// |fd| is assumed to be non-blocking.
bool WriteWithTimeout(SafeFD* fd,
const std::string& value,
size_t max_tries,
base::TimeDelta delay,
ssize_t (*write_func)(int, const void*, size_t),
int (*usleep_func)(useconds_t),
int (*ftruncate_func)(int, off_t)) {
size_t tries = 0;
size_t total = 0;
int written = 0;
while (tries < max_tries) {
++tries;
errno = 0;
written =
write_func(fd->get(), value.c_str() + total, value.size() - total);
if (written < 0) {
if (errno == EAGAIN) {
// Writing would block. Wait and try again.
HANDLE_EINTR(usleep_func(delay.InMicroseconds()));
continue;
} else if (errno == EINTR) {
// Count EINTR against the tries.
continue;
} else {
PLOG(ERROR) << "Failed to write '" << GetFDPath(fd->get()).value()
<< "'";
return false;
}
}
total += written;
if (total == value.size()) {
if (HANDLE_EINTR(ftruncate_func(fd->get(), value.size())) != 0) {
PLOG(ERROR) << "Failed to truncate '" << GetFDPath(fd->get()).value()
<< "'";
return false;
}
return true;
}
}
return false;
}
std::string Hash(const std::string& content) {
std::vector<uint8_t> digest(SHA256_DIGEST_LENGTH, 0);
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, content.data(), content.size());
SHA256_Final(digest.data(), &ctx);
return EncodeDigest(digest);
}
std::string Hash(const google::protobuf::RepeatedPtrField<std::string>& rules) {
std::vector<uint8_t> digest(SHA256_DIGEST_LENGTH, 0);
SHA256_CTX ctx;
SHA256_Init(&ctx);
// This extra logic is needed for consistency with
// Hash(const std::string& content)
bool first = true;
for (const auto& rule : rules) {
SHA256_Update(&ctx, rule.data(), rule.size());
if (!first) {
// Add a end of line to delimit rules for the mode switching case when
// more than one allow-listing rule is needed for a single device.
SHA256_Update(&ctx, "\n", 1);
} else {
first = false;
}
}
SHA256_Final(digest.data(), &ctx);
return EncodeDigest(digest);
}
bool AuthorizeAll(const std::string& devpath) {
if (devpath.front() != '/') {
return false;
}
SafeFD::Error err;
SafeFD dir;
std::tie(dir, err) =
SafeFD::Root().first.OpenExistingDir(base::FilePath(devpath.substr(1)));
if (SafeFD::IsError(err)) {
LOG(ERROR) << "Failed to open '" << GetFDPath(dir.get()).value() << "'.";
return false;
}
return AuthorizeAllImpl(&dir);
}
std::string GetRuleFromDevPath(const std::string& devpath) {
UsbguardDeviceManagerHooksImpl hooks;
auto device_manager = usbguard::DeviceManager::create(hooks, "uevent");
device_manager->setEnumerationOnlyMode(true);
device_manager->scan(devpath);
return hooks.getLastRule();
}
bool IncludeRuleAtLockscreen(const std::string& rule) {
const usbguard::Rule filter_rule = usbguard::Rule::fromString(
"block with-interface one-of { 05:*:* 06:*:* 07:*:* 08:*:* }");
usbguard::Rule parsed_rule = GetRuleFromString(rule);
if (!parsed_rule) {
return false;
}
return !filter_rule.appliesTo(parsed_rule);
}
bool ValidateRule(const std::string& rule) {
if (rule.empty()) {
return false;
}
return usbguard::Rule::fromString(rule);
}
void UMALogDeviceAttached(MetricsLibrary* metrics,
const std::string& rule,
UMADeviceRecognized recognized,
UMAEventTiming timing) {
usbguard::Rule parsed_rule = GetRuleFromString(rule);
if (!parsed_rule) {
return;
}
// TODO(crbug.com/1218246) Change UMA enum names kUmaDeviceAttachedHistogram.*
// if new enums for UMAEventTiming are added to avoid data discontinuity, then
// use kMaxValue+1 rather than kMaxValue (or templated SendEnumToUMA()).
metrics->SendEnumToUMA(
base::StringPrintf("%s.%s.%s", kUmaDeviceAttachedHistogram,
to_string(recognized).c_str(),
to_string(GetClassFromRule(parsed_rule)).c_str()),
static_cast<int>(timing), static_cast<int>(UMAEventTiming::kMaxValue));
}
void UMALogExternalDeviceAttached(MetricsLibrary* metrics,
const std::string& rule,
UMADeviceRecognized recognized,
UMAEventTiming timing,
UMAPortType port,
UMADeviceSpeed speed) {
usbguard::Rule parsed_rule = GetRuleFromString(rule);
if (!parsed_rule) {
return;
}
metrics->SendEnumToUMA(
base::StringPrintf("%s.%s.%s", kUmaExternalDeviceAttachedHistogram,
to_string(recognized).c_str(),
to_string(GetClassFromRule(parsed_rule)).c_str()),
static_cast<int>(timing), static_cast<int>(UMAEventTiming::kMaxValue));
// Another metrics on device class categorized by port type.
// Report this separately since port type is not related to
// Recongnized/Unrecognized and Event Timing.
metrics->SendEnumToUMA(base::StringPrintf("%s.%s.DeviceClass",
kUmaExternalDeviceAttachedHistogram,
to_string(port).c_str()),
static_cast<int>(GetClassFromRule(parsed_rule)),
static_cast<int>(UMADeviceClass::kMaxValue));
metrics->SendEnumToUMA(base::StringPrintf("%s.%s.DeviceSpeed",
kUmaExternalDeviceAttachedHistogram,
to_string(port).c_str()),
static_cast<int>(speed),
static_cast<int>(UMADeviceSpeed::kMaxValue));
}
void StructuredMetricsExternalDeviceAttached(
int VendorId,
std::string VendorName,
int ProductId,
std::string ProductName,
int DeviceClass,
std::vector<int64_t> InterfaceClass) {
// Limit string length to prevent badly behaving device from creating huge
// metrics packet.
int string_len_limit = 200;
VendorName = VendorName.substr(0, string_len_limit);
ProductName = ProductName.substr(0, string_len_limit);
// In case the size of InterfaceClass exceed the max number of interfaces
// supported by the UsbDeviceInfo metrics, just slice the vector and report.
// The max length supported is large enough that this is quite unlikely.
int max_interface = metrics::structured::events::usb_device::UsbDeviceInfo::
GetInterfaceClassMaxLength();
if (InterfaceClass.size() > max_interface) {
InterfaceClass.resize(max_interface);
}
metrics::structured::events::usb_device::UsbDeviceInfo()
.SetVendorId(VendorId)
.SetVendorName(VendorName)
.SetProductId(ProductId)
.SetProductName(ProductName)
.SetDeviceClass(DeviceClass)
.SetInterfaceClass(std::move(InterfaceClass))
.Record();
}
void StructuredMetricsInternalCameraModule(int VendorId,
std::string VendorName,
int ProductId,
std::string ProductName,
int BcdDevice) {
// Limit string length to prevent badly behaving device from creating huge
// metrics packet.
int string_len_limit = 200;
VendorName = VendorName.substr(0, string_len_limit);
ProductName = ProductName.substr(0, string_len_limit);
metrics::structured::events::usb_camera_module::UsbCameraModuleInfo()
.SetVendorId(VendorId)
.SetVendorName(VendorName)
.SetProductId(ProductId)
.SetProductName(ProductName)
.SetBcdDevice(BcdDevice)
.Record();
}
void StructuredMetricsUsbSessionEvent(UsbSessionMetric session_metric) {
// Only record UsbSessionEvents for devices in the USB metrics allowlist.
if (!DeviceInMetricsAllowlist(session_metric.vid, session_metric.pid))
return;
metrics::structured::events::usb_session::UsbSessionEvent()
.SetBootId(std::move(session_metric.boot_id))
.SetSystemTime(std::move(session_metric.system_time))
.SetAction(session_metric.action)
.SetDeviceNum(session_metric.devnum)
.SetBusNum(session_metric.busnum)
.SetDepth(session_metric.depth)
.SetVendorId(session_metric.vid)
.SetProductId(session_metric.pid)
.Record();
}
void StructuredMetricsHubError(int ErrorCode,
int VendorId,
int ProductId,
int DeviceClass,
std::string UsbTreePath,
int ConnectedDuration) {
// Limit string length.
int string_len_limit = 20;
UsbTreePath = UsbTreePath.substr(0, string_len_limit);
// Mask VID/PID if the error was reported about an obscure device.
if (!DeviceInMetricsAllowlist(VendorId, ProductId)) {
VendorId = 0;
ProductId = 0;
}
metrics::structured::events::usb_error::HubError()
.SetErrorCode(ErrorCode)
.SetVendorId(VendorId)
.SetProductId(ProductId)
.SetDeviceClass(DeviceClass)
.SetDevicePath(UsbTreePath)
.SetConnectedDuration(ConnectedDuration)
.Record();
}
void StructuredMetricsXhciError(int ErrorCode, int DeviceClass) {
metrics::structured::events::usb_error::XhciError()
.SetErrorCode(ErrorCode)
.SetDeviceClass(DeviceClass)
.Record();
}
base::FilePath GetUserDBDir() {
// Usb_bouncer is called by udev even during early boot. If D-Bus is
// inaccessible, it is early boot and the user hasn't logged in.
if (!base::PathExists(base::FilePath(kDBusPath))) {
return base::FilePath("");
}
scoped_refptr<dbus::Bus> bus;
auto session_manager_proxy = SetUpDBus(bus);
brillo::ErrorPtr error;
std::string username, hashed_username;
session_manager_proxy->RetrievePrimarySession(&username, &hashed_username,
&error);
if (hashed_username.empty()) {
LOG(ERROR) << "No active user session.";
return base::FilePath("");
}
base::FilePath UserDir =
base::FilePath(kUserDbBaseDir).Append(hashed_username);
if (!base::DirectoryExists(UserDir)) {
LOG(ERROR) << "User daemon-store directory doesn't exist.";
return base::FilePath("");
}
// A sub directory is used so permissions can be enforced by usb_bouncer
// without affecting the daemon-store mount point.
UserDir = UserDir.Append(kUserDbParentDir);
return UserDir;
}
bool IsGuestSession() {
// Usb_bouncer is called by udev even during early boot. If D-Bus is
// inaccessible, it is early boot and a guest hasn't logged in.
if (!base::PathExists(base::FilePath(kDBusPath))) {
return false;
}
scoped_refptr<dbus::Bus> bus;
auto session_manager_proxy = SetUpDBus(bus);
bool is_guest = false;
brillo::ErrorPtr error;
session_manager_proxy->IsGuestSessionActive(&is_guest, &error);
return is_guest;
}
bool IsLockscreenShown() {
// Usb_bouncer is called by udev even during early boot. If D-Bus is
// inaccessible, it is early boot and the lock-screen isn't shown.
if (!base::PathExists(base::FilePath(kDBusPath))) {
return false;
}
scoped_refptr<dbus::Bus> bus;
auto session_manager_proxy = SetUpDBus(bus);
brillo::ErrorPtr error;
bool locked;
if (!session_manager_proxy->IsScreenLocked(&locked, &error)) {
LOG(ERROR) << "Failed to get lockscreen state.";
locked = true;
}
return locked;
}
std::string StripLeadingPathSeparators(const std::string& path) {
if (path.find_first_not_of('/') == std::string::npos)
return std::string();
return path.substr(path.find_first_not_of('/'));
}
std::unordered_set<std::string> UniqueRules(const EntryMap& entries) {
std::unordered_set<std::string> aggregated_rules;
for (const auto& entry_itr : entries) {
for (const auto& rule : entry_itr.second.rules()) {
if (!rule.empty()) {
aggregated_rules.insert(rule);
}
}
}
return aggregated_rules;
}
SafeFD OpenStateFile(const base::FilePath& base_path,
const std::string& parent_dir,
const std::string& state_file_name,
const std::string& username,
bool lock) {
uid_t proc_uid = getuid();
uid_t uid = proc_uid;
gid_t gid = getgid();
if (CanChown() && !brillo::userdb::GetUserInfo(username, &uid, &gid)) {
LOG(ERROR) << "Failed to get uid & gid for \"" << username << "\"";
return SafeFD();
}
// Don't enforce permissions on the |base_path|. It is handled by the system.
SafeFD::Error err;
SafeFD base_fd;
std::tie(base_fd, err) = SafeFD::Root().first.OpenExistingDir(base_path);
if (!base_fd.is_valid()) {
LOG(ERROR) << "\"" << base_path.value() << "\" does not exist!";
return SafeFD();
}
// Acquire an exclusive lock on the base path to avoid races when creating
// the sub directories. This lock is released when base_fd goes out of scope.
if (HANDLE_EINTR(flock(base_fd.get(), LOCK_EX)) < 0) {
PLOG(ERROR) << "Failed to lock \"" << base_path.value() << '"';
return SafeFD();
}
// Ensure the parent directory has the correct permissions.
SafeFD parent_fd;
std::tie(parent_fd, err) =
OpenOrRemakeDir(&base_fd, parent_dir, kDbDirPermissions, uid, gid);
if (!parent_fd.is_valid()) {
auto parent_path = base_path.Append(parent_dir);
LOG(ERROR) << "Failed to validate '" << parent_path.value() << "'";
return SafeFD();
}
// Create the DB file with the correct permissions.
SafeFD fd;
std::tie(fd, err) =
OpenOrRemakeFile(&parent_fd, state_file_name, kDbPermissions, uid, gid);
if (!fd.is_valid()) {
auto full_path = base_path.Append(parent_dir).Append(state_file_name);
LOG(ERROR) << "Failed to validate '" << full_path.value() << "'";
return SafeFD();
}
if (lock) {
if (HANDLE_EINTR(flock(fd.get(), LOCK_EX)) < 0) {
auto full_path = base_path.Append(parent_dir).Append(state_file_name);
PLOG(ERROR) << "Failed to lock \"" << full_path.value() << '"';
return SafeFD();
}
}
return fd;
}
void UpdateTimestamp(Timestamp* timestamp) {
auto time = (base::Time::Now() - base::Time::UnixEpoch()).ToTimeSpec();
timestamp->set_seconds(time.tv_sec);
timestamp->set_nanos(time.tv_nsec);
}
size_t RemoveEntriesOlderThan(base::TimeDelta cutoff, EntryMap* map) {
size_t num_removed = 0;
auto itr = map->begin();
auto cuttoff_time =
(base::Time::Now() - base::Time::UnixEpoch() - cutoff).ToTimeSpec();
while (itr != map->end()) {
const Timestamp& entry_timestamp = itr->second.last_used();
if (entry_timestamp.seconds() < cuttoff_time.tv_sec ||
(entry_timestamp.seconds() == cuttoff_time.tv_sec &&
entry_timestamp.nanos() < cuttoff_time.tv_nsec)) {
++num_removed;
map->erase(itr++);
} else {
++itr;
}
}
return num_removed;
}
void Daemonize() {
pid_t result = fork();
if (result < 0) {
PLOG(FATAL) << "First fork failed";
}
if (result != 0) {
exit(0);
}
setsid();
result = fork();
if (result < 0) {
PLOG(FATAL) << "Second fork failed";
}
if (result != 0) {
exit(0);
}
// Since we're demonizing we don't expect to ever read or write from the
// standard file descriptors. Also, udev waits for the hangup before
// continuing to execute on the same event, so this is necessary to unblock
// udev.
if (freopen("/dev/null", "a+", stdout) == nullptr) {
LOG(FATAL) << "Failed to replace stdout.";
}
if (freopen("/dev/null", "a+", stderr) == nullptr) {
LOG(FATAL) << "Failed to replace stdout.";
}
if (fclose(stdin) != 0) {
LOG(FATAL) << "Failed to close stdin.";
}
}
#define TO_STRING_HELPER(x) \
case UMADeviceClass::k##x: \
return #x
const std::string to_string(UMADeviceClass device_class) {
switch (device_class) {
TO_STRING_HELPER(App);
TO_STRING_HELPER(Audio);
TO_STRING_HELPER(AV);
TO_STRING_HELPER(Card);
TO_STRING_HELPER(Comm);
TO_STRING_HELPER(Health);
TO_STRING_HELPER(HID);
TO_STRING_HELPER(Hub);
TO_STRING_HELPER(Image);
TO_STRING_HELPER(Misc);
TO_STRING_HELPER(Other);
TO_STRING_HELPER(Phys);
TO_STRING_HELPER(Print);
TO_STRING_HELPER(Sec);
TO_STRING_HELPER(Storage);
TO_STRING_HELPER(Vendor);
TO_STRING_HELPER(Video);
TO_STRING_HELPER(Wireless);
}
}
#undef TO_STRING_HELPER
#define TO_STRING_HELPER(x) \
case UMADeviceRecognized::k##x: \
return #x
const std::string to_string(UMADeviceRecognized recognized) {
switch (recognized) {
TO_STRING_HELPER(Recognized);
TO_STRING_HELPER(Unrecognized);
}
}
#undef TO_STRING_HELPER
#define TO_STRING_HELPER(x) \
case UMAPortType::k##x: \
return #x
const std::string to_string(UMAPortType port) {
switch (port) {
TO_STRING_HELPER(TypeC);
TO_STRING_HELPER(TypeA);
}
}
#undef TO_STRING_HELPER
std::ostream& operator<<(std::ostream& out, UMADeviceClass device_class) {
out << to_string(device_class);
return out;
}
std::ostream& operator<<(std::ostream& out, UMADeviceRecognized recognized) {
out << to_string(recognized);
return out;
}
std::ostream& operator<<(std::ostream& out, UMAPortType port) {
out << to_string(port);
return out;
}
bool IsCamera(std::vector<int64_t> interfaces) {
for (auto& interface : interfaces) {
if (interface == 0xe)
return true;
}
return false;
}
int GetBcdDevice(base::FilePath normalized_devpath) {
std::string bcdDevice;
int bcdDevice_int;
if (base::ReadFileToString(normalized_devpath.Append("bcdDevice"),
&bcdDevice)) {
base::TrimWhitespaceASCII(bcdDevice, base::TRIM_ALL, &bcdDevice);
if (base::HexStringToInt(bcdDevice, &bcdDevice_int)) {
return bcdDevice_int;
}
}
return 0;
}
usbguard::Rule GetRuleFromString(const std::string& to_parse) {
usbguard::Rule parsed_rule;
parsed_rule.setTarget(usbguard::Rule::Target::Invalid);
if (to_parse.empty()) {
return parsed_rule;
}
try {
parsed_rule = usbguard::Rule::fromString(to_parse);
} catch (std::exception ex) {
// RuleParseException isn't exported by libusbguard.
LOG(ERROR) << "Failed parse (exception) '" << to_parse << "'.";
}
return parsed_rule;
}
UMADeviceClass GetClassFromRule(const usbguard::Rule& rule) {
const auto& interfaces = rule.attributeWithInterface();
if (interfaces.empty()) {
return UMADeviceClass::kOther;
}
UMADeviceClass device_class = GetClassEnumFromValue(interfaces.get(0));
for (int x = 1; x < interfaces.count(); ++x) {
device_class =
MergeClasses(device_class, GetClassEnumFromValue(interfaces.get(x)));
}
return device_class;
}
base::FilePath GetRootDevice(base::FilePath dev) {
auto dev_components = dev.GetComponents();
auto it = dev_components.begin();
base::FilePath root_dev(*it++);
for (; it != dev_components.end(); it++) {
root_dev = root_dev.Append(*it);
if (RE2::FullMatch(*it, R"((\d+)-(\d+))")) {
break;
}
}
return root_dev;
}
base::FilePath GetInterfaceDevice(base::FilePath intf) {
std::string dev;
if (!RE2::PartialMatch(intf.value(), R"((.*\/).*)", &dev))
return base::FilePath();
return base::FilePath(dev);
}
bool IsExternalDevice(base::FilePath normalized_devpath) {
std::string removable;
if (base::ReadFileToString(normalized_devpath.Append("removable"),
&removable)) {
base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
if (removable == "removable")
return true;
}
auto dev_components = normalized_devpath.GetComponents();
auto it = dev_components.begin();
base::FilePath dev(*it++);
for (; it != dev_components.end(); it++) {
dev = dev.Append(*it);
if (base::ReadFileToString(dev.Append("removable"), &removable)) {
base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
if (removable == "removable")
return true;
}
}
std::string panel;
if (base::ReadFileToString(
normalized_devpath.Append("physical_location/panel"), &panel)) {
base::TrimWhitespaceASCII(panel, base::TRIM_ALL, &panel);
if (panel != "unknown")
return true;
}
return false;
}
bool IsFlexBoard() {
brillo::KeyValueStore store;
if (!store.Load(base::FilePath("/etc/lsb-release"))) {
LOG(WARNING) << "Could not read lsb-release";
return true;
}
std::string value;
if (!store.GetString("CHROMEOS_RELEASE_BOARD", &value)) {
LOG(WARNING) << "Could not determine board";
return true;
}
return value.find("reven") != std::string::npos;
}
UMAPortType GetPortType(base::FilePath normalized_devpath) {
std::string connector_uevent;
std::string devtype;
if (base::ReadFileToString(normalized_devpath.Append("port/connector/uevent"),
&connector_uevent) &&
RE2::PartialMatch(connector_uevent, R"(DEVTYPE=(\w+))", &devtype) &&
devtype == "typec_port") {
return UMAPortType::kTypeC;
}
return UMAPortType::kTypeA;
}
UMADeviceSpeed GetDeviceSpeed(base::FilePath normalized_devpath) {
std::string speed;
if (base::ReadFileToString(normalized_devpath.Append("speed"), &speed)) {
base::TrimWhitespaceASCII(speed, base::TRIM_ALL, &speed);
}
std::string version;
if (base::ReadFileToString(normalized_devpath.Append("version"), &version)) {
base::TrimWhitespaceASCII(version, base::TRIM_ALL, &version);
}
if (speed == "20000") {
return UMADeviceSpeed::k20000;
} else if (speed == "10000") {
return UMADeviceSpeed::k10000;
} else if (speed == "5000") {
return UMADeviceSpeed::k5000;
} else if (speed == "480") {
if (version == "2.10") {
return UMADeviceSpeed::k480Fallback;
} else {
return UMADeviceSpeed::k480;
}
} else if (speed == "12") {
return UMADeviceSpeed::k12;
} else if (speed == "1.5") {
return UMADeviceSpeed::k1_5;
} else {
return UMADeviceSpeed::kOther;
}
}
int GetVendorId(base::FilePath normalized_devpath) {
std::string vendor_id;
int vendor_id_int;
if (base::ReadFileToString(normalized_devpath.Append("idVendor"),
&vendor_id)) {
base::TrimWhitespaceASCII(vendor_id, base::TRIM_ALL, &vendor_id);
if (base::HexStringToInt(vendor_id, &vendor_id_int)) {
return vendor_id_int;
}
}
return 0;
}
std::string GetVendorName(base::FilePath normalized_devpath) {
std::string vendor_name;
if (base::ReadFileToString(normalized_devpath.Append("manufacturer"),
&vendor_name)) {
base::TrimWhitespaceASCII(vendor_name, base::TRIM_ALL, &vendor_name);
return vendor_name;
}
return std::string();
}
int GetProductId(base::FilePath normalized_devpath) {
std::string product_id;
int product_id_int;
if (base::ReadFileToString(normalized_devpath.Append("idProduct"),
&product_id)) {
base::TrimWhitespaceASCII(product_id, base::TRIM_ALL, &product_id);
if (base::HexStringToInt(product_id, &product_id_int)) {
return product_id_int;
}
}
return 0;
}
std::string GetProductName(base::FilePath normalized_devpath) {
std::string product_name;
if (base::ReadFileToString(normalized_devpath.Append("product"),
&product_name)) {
base::TrimWhitespaceASCII(product_name, base::TRIM_ALL, &product_name);
return product_name;
}
return std::string();
}
void GetVidPidFromEnvVar(std::string product, int* vendor_id, int* product_id) {
*vendor_id = 0;
*product_id = 0;
std::size_t index1 = product.find('/');
std::size_t index2 = product.find('/', index1 + 1);
if (index1 == std::string::npos || index2 == std::string::npos)
return;
base::HexStringToInt(product.substr(0, index1), vendor_id);
base::HexStringToInt(product.substr(index1 + 1, index2 - index1 - 1),
product_id);
}
int GetDeviceClass(base::FilePath normalized_devpath) {
std::string device_class;
int device_class_int;
if (base::ReadFileToString(normalized_devpath.Append("bDeviceClass"),
&device_class)) {
base::TrimWhitespaceASCII(device_class, base::TRIM_ALL, &device_class);
if (base::HexStringToInt(device_class, &device_class_int) &&
device_class_int != 0) {
return device_class_int;
}
}
return 0;
}
std::vector<int64_t> GetInterfaceClass(base::FilePath normalized_devpath) {
std::vector<int64_t> ret;
base::FileEnumerator enumerator(normalized_devpath, false,
base::FileEnumerator::DIRECTORIES);
for (auto intf_path = enumerator.Next(); !intf_path.empty();
intf_path = enumerator.Next()) {
std::string intf_class;
int64_t intf_class_int;
if (!base::ReadFileToString(intf_path.Append("bInterfaceClass"),
&intf_class)) {
continue;
}
base::TrimWhitespaceASCII(intf_class, base::TRIM_ALL, &intf_class);
if (base::HexStringToInt64(intf_class, &intf_class_int)) {
ret.push_back(intf_class_int);
}
}
return ret;
}
std::string GetUsbTreePath(base::FilePath normalized_devpath) {
std::string device_path;
if (base::ReadFileToString(normalized_devpath.Append("devpath"),
&device_path)) {
base::TrimWhitespaceASCII(device_path, base::TRIM_ALL, &device_path);
return device_path;
}
return std::string();
}
int GetUsbTreeDepth(base::FilePath normalized_devpath) {
std::string devpath = GetUsbTreePath(normalized_devpath);
return std::count(devpath.begin(), devpath.end(), '.');
}
int GetConnectedDuration(base::FilePath normalized_devpath) {
std::string connected_duration;
int connected_duration_int;
if (base::ReadFileToString(
normalized_devpath.Append("power/connected_duration"),
&connected_duration)) {
base::TrimWhitespaceASCII(connected_duration, base::TRIM_ALL,
&connected_duration);
if (base::StringToInt(connected_duration, &connected_duration_int)) {
return connected_duration_int;
}
}
return 0;
}
int GetPciDeviceClass(base::FilePath normalized_devpath) {
std::string device_class;
int device_class_int;
if (base::ReadFileToString(normalized_devpath.Append("class"),
&device_class)) {
base::TrimWhitespaceASCII(device_class, base::TRIM_ALL, &device_class);
if (base::HexStringToInt(device_class, &device_class_int)) {
// The sysfs "class" file includes class, subclass and interface
// information. Shifting 16 bits to return only the device class.
return (device_class_int >> 16);
}
}
return 0;
}
std::string GetBootId() {
std::string boot_id;
if (base::ReadFileToString(base::FilePath("/proc/sys/kernel/random/boot_id"),
&boot_id)) {
base::TrimWhitespaceASCII(boot_id, base::TRIM_ALL, &boot_id);
return boot_id;
}
return std::string();
}
int64_t GetSystemTime() {
struct timespec ts;
clock_gettime(CLOCK_BOOTTIME, &ts);
return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
} // namespace usb_bouncer