| // Copyright 2018 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 "usb_bouncer/util.h" |
| #include "usb_bouncer/util_internal.h" |
| |
| #include <fcntl.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/base64.h> |
| #include <base/files/file_util.h> |
| #include <base/process/launch.h> |
| #include <base/strings/string_piece.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/userdb_utils.h> |
| #include <openssl/sha.h> |
| #include <session_manager/dbus-proxies.h> |
| #include <usbguard/Device.hpp> |
| #include <usbguard/DeviceManager.hpp> |
| #include <usbguard/DeviceManagerHooks.hpp> |
| |
| using brillo::GetFDPath; |
| using brillo::SafeFD; |
| using brillo::ScopedDIR; |
| using brillo::cryptohome::home::GetHashedUserPath; |
| 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 int kMaxWriteAttempts = 10; |
| constexpr int kAttemptDelayMicroseconds = 10000; |
| |
| 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 result; |
| base::StringPiece digest_view(reinterpret_cast<const char*>(digest.data()), |
| digest.size()); |
| base::Base64Encode(digest_view, &result); |
| return result; |
| } |
| |
| 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_; |
| }; |
| |
| // |fd| is assumed to be non-blocking. |
| bool WriteWithTimeout(SafeFD* fd, |
| const std::string value, |
| size_t max_tries = kMaxWriteAttempts, |
| base::TimeDelta delay = base::TimeDelta::FromMicroseconds( |
| kAttemptDelayMicroseconds)) { |
| size_t tries = 0; |
| size_t total = 0; |
| int written = 0; |
| while (tries < max_tries) { |
| ++tries; |
| |
| written = write(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(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(fd->get(), value.size())) != 0) { |
| PLOG(ERROR) << "Failed to truncate '" << GetFDPath(fd->get()).value() |
| << "'"; |
| return false; |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool WriteWithTimeoutIfExists(SafeFD* dir, |
| const base::FilePath name, |
| const std::string& value) { |
| SafeFD::Error err; |
| SafeFD file; |
| 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)) { |
| LOG(ERROR) << "Failed to open authorized_default for '" |
| << 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(kSysFSAuthorizedDefault), |
| kSysFSEnabled)) { |
| success = false; |
| } |
| |
| if (!WriteWithTimeoutIfExists(dir, base::FilePath(kSysFSAuthorized), |
| kSysFSEnabled)) { |
| // 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. |
| if (errno == EPIPE || errno == ENODEV || errno == EPROTO) { |
| PLOG(WARNING) << "Failed to authorize USB device: '" |
| << GetFDPath(dir->get()).value() << "'"; |
| } else { |
| 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 false; |
| } |
| |
| ScopedDIR listing(fdopendir(dup_fd)); |
| if (!listing.is_valid()) { |
| PLOG(ERROR) << "fdopendir failed for '" << GetFDPath(dir->get()).value() |
| << "'"; |
| HANDLE_EINTR(close(dup_fd)); |
| return false; |
| } |
| |
| struct stat dir_info; |
| if (fstat(dir->get(), &dir_info) != 0) { |
| // If the directory no longer exists, skip it. |
| if (errno == ENOENT) { |
| return success; |
| } |
| return false; |
| } |
| |
| for (;;) { |
| errno = 0; |
| const dirent* entry = HANDLE_EINTR_IF_EQ(readdir(listing.get()), nullptr); |
| if (entry == nullptr) { |
| break; |
| } |
| |
| SafeFD::SafeFDResult subdir = OpenIfSubdirectory(dir, dir_info, *entry); |
| if (SafeFD::IsError(subdir.second)) { |
| 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 false; |
| } |
| |
| // 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; |
| } |
| |
| } // namespace |
| |
| 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; |
| } |
| |
| 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)); |
| } |
| |
| 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) { |
| 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, |
| bool lock) { |
| uid_t proc_uid = getuid(); |
| uid_t uid = proc_uid; |
| gid_t gid = getgid(); |
| if (uid == kRootUid && |
| !brillo::userdb::GetUserInfo(kUsbBouncerUser, &uid, &gid)) { |
| LOG(ERROR) << "Failed to get uid & gid for \"" << kUsbBouncerUser << "\""; |
| 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 |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| } // namespace usb_bouncer |