| // 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 <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 <brillo/cryptohome.h> |
| #include <brillo/file_utils.h> |
| #include <brillo/userdb_utils.h> |
| #include <openssl/sha.h> |
| #include <session_manager/dbus-proxies.h> |
| #include <unistd.h> |
| |
| #include <cstring> |
| #include <vector> |
| |
| #include <usbguard/Rule.hpp> |
| |
| using brillo::cryptohome::home::GetHashedUserPath; |
| using org::chromium::SessionManagerInterfaceProxy; |
| |
| namespace usb_bouncer { |
| |
| namespace { |
| |
| constexpr int kDbPermissions = |
| base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER; |
| |
| constexpr uid_t kRootUid = 0; |
| |
| constexpr char kDefaultDbName[] = "devices.proto"; |
| constexpr char kUserDbParentDir[] = "/run/daemon-store/usb_bouncer/"; |
| |
| // 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); |
| } |
| |
| // As root this will create the necessary files with the required permissions, |
| // without root it will tried to create the files and verify the permissions are |
| // correct. |
| bool SetupPermissionsFor(const base::FilePath& path) { |
| 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 false; |
| } |
| // TODO(chromium:896337) Address TOCTOU here. |
| if (!brillo::TouchFile(path, kDbPermissions, uid, gid)) { |
| LOG(ERROR) << "Failed to touch file \"" << path.value() << "\""; |
| return false; |
| } |
| if (proc_uid == kRootUid && chown(path.value().c_str(), uid, gid) < 0) { |
| LOG(ERROR) << "chown for \"" << path.value() |
| << "\" failed because: " << std::strerror(errno); |
| return false; |
| } |
| if (!base::VerifyPathControlledByUser(path, path, uid, {gid})) { |
| LOG(ERROR) << "Wrong permissions \"" << path.value() << "\""; |
| } |
| return true; |
| } |
| |
| } // 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 white-listing rule is needed for a single device. |
| SHA256_Update(&ctx, "\n", 1); |
| } else { |
| first = false; |
| } |
| } |
| |
| SHA256_Final(digest.data(), &ctx); |
| return EncodeDigest(digest); |
| } |
| |
| std::unique_ptr<RuleDB> GetDBFromPath(const base::FilePath& parent_dir, |
| base::FilePath* db_path) { |
| *db_path = parent_dir.Append(kDefaultDbName); |
| // TODO(chromium:896337) Fix TOCTOU. |
| if (!SetupPermissionsFor(*db_path)) { |
| *db_path = base::FilePath(""); |
| return nullptr; |
| } |
| std::string data; |
| if (!base::ReadFileToString(*db_path, &data) || data.empty()) { |
| return std::make_unique<RuleDB>(); |
| } |
| |
| auto result = std::make_unique<RuleDB>(); |
| if (!result->ParseFromString(data)) { |
| LOG(ERROR) << "Error parsing db. Regenerating..."; |
| } |
| return result; |
| } |
| |
| std::string GetRuleFromDevPath(const std::string& devpath) { |
| LOG(FATAL) << "Not implemented!"; |
| return ""; |
| } |
| |
| bool ValidateRule(const std::string& rule) { |
| if (rule.empty()) { |
| return false; |
| } |
| return usbguard::Rule::fromString(rule); |
| } |
| |
| base::FilePath GetUserDBDir() { |
| 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(""); |
| } |
| |
| bool locked; |
| if (!session_manager_proxy->IsScreenLocked(&locked, &error)) { |
| LOG(ERROR) << "Failed to get lockscreen state."; |
| return base::FilePath(""); |
| } |
| if (locked) { |
| LOG(INFO) << "Device is locked."; |
| return base::FilePath(""); |
| } |
| |
| base::FilePath UserDir = |
| base::FilePath(kUserDbParentDir).Append(hashed_username); |
| if (!base::DirectoryExists(UserDir)) { |
| LOG(ERROR) << "User daemon-store directory doesn't exist."; |
| return base::FilePath(""); |
| } |
| |
| return UserDir; |
| } |
| |
| 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; |
| } |
| |
| bool WriteProtoToPath(const base::FilePath& db_path, |
| google::protobuf::MessageLite* rule_db) { |
| std::string serialized = rule_db->SerializeAsString(); |
| int result = base::WriteFile(db_path, serialized.data(), serialized.size()); |
| if (result != serialized.size()) { |
| LOG(ERROR) << "Failed to write proto to file!"; |
| return false; |
| } |
| return true; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Time related helper functions. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| 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; |
| } |
| |
| } // namespace usb_bouncer |