pciguard: daemon to secure external PCI devices

Add a daemon to secure external PCI devices. It reacts to different
user session management events (login/logoff/screen locks/unlock),
thunderbolt device udev events, and chrome flag changed events.
We have different policies for authorizing external PCI devices
depending on whether the user is away from chromebook.

BUG=b:167448768,b:171541662,b:172397647
TEST=Tested along with kernel changes that
1) Devices plugged in while system was booted do not show up on PCI bus.
2) Devices plugged in before login do not show up on PCI bus.
3) When user logins, such devices now show up and drivers are bound to
them.
4) When screen locked, the existing devices keep working, but no new
thunderbolt devices are authorized.
5) When screen unlocked they begin to work again.
6) When log off, everything is gone.

Cq-Depend: chromium:2543285,chromium:2551845
Change-Id: I2d306765f2fc186602782de85f45fb6ba4fbd89a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2591812
Reviewed-by: Todd Broch <tbroch@chromium.org>
Commit-Queue: Rajat Jain <rajatja@google.com>
Tested-by: Rajat Jain <rajatja@google.com>
diff --git a/README.md b/README.md
index 7f51ccb..d1da367 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@
 | [oobe_config](./oobe_config/) | Utilities for saving and restoring OOBE config state |
 | [p2p](./p2p/) | Service for sharing files between CrOS devices (e.g. updates) |
 | [patchpanel](./patchpanel/) | Platform networking daemons |
+| [pciguard](./pciguard/) | Daemon to secure external PCI devices (thunderbolt etc) |
 | [permission_broker](./permission_broker/) ||
 | [policy_proto](./policy_proto/) | Build file to compile policy proto file |
 | [policy_utils](./policy_utils/) | Tools and related library to set or override device policies |
diff --git a/pciguard/BUILD.gn b/pciguard/BUILD.gn
new file mode 100644
index 0000000..3ce837f
--- /dev/null
+++ b/pciguard/BUILD.gn
@@ -0,0 +1,32 @@
+# 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.
+
+import("//common-mk/pkg_config.gni")
+
+group("all") {
+  deps = [ ":pciguard" ]
+}
+
+pkg_config("target_defaults") {
+  pkg_deps = [
+    "libbrillo",
+    "libchrome",
+    "libsession_manager-client",
+    "libudev",
+  ]
+}
+
+executable("pciguard") {
+  configs += [ ":target_defaults" ]
+  sources = [
+    "authorizer.cc",
+    "daemon.cc",
+    "event_handler.cc",
+    "main.cc",
+    "pciguard_utils.cc",
+    "session_monitor.cc",
+    "tbt_udev_monitor.cc",
+  ]
+  libs = [ "pthread" ]
+}
diff --git a/pciguard/OWNERS b/pciguard/OWNERS
new file mode 100644
index 0000000..d3fc078
--- /dev/null
+++ b/pciguard/OWNERS
@@ -0,0 +1,5 @@
+aashay@google.com
+bleung@chromium.org
+pmalani@chromium.org
+rajatja@google.com
+tbroch@google.com
diff --git a/pciguard/README.md b/pciguard/README.md
new file mode 100644
index 0000000..7615909
--- /dev/null
+++ b/pciguard/README.md
@@ -0,0 +1,18 @@
+# pciguard: Chromeos security tool for external PCI devices
+
+## ABOUT
+
+pciguard is daemon that is listens to following events:
+ - session events: such as user login / logoff and screen lock / unlock,
+ - udev events: plugging in of new thunderbolt devices.
+ - Chrome flag changes: for user permission flag changes.
+
+These events change the security policies around external PCI devices. This
+mostly concerns thunderbolt / USB4 peripherals that allow PCIe tunnels to be
+established, but can also be used for any other technologies that allow external
+PCI devices, e.g. SD Express cards.
+
+In short, this daemon implements the security policy of allowing external PCIe
+devices only when a user is signed in, and when user has opted for it using
+the appropriate [chrome://flags](chrome://flags) setting.
+See flag details [here](https://buganizer.corp.google.com/issues/172397647)
diff --git a/pciguard/authorizer.cc b/pciguard/authorizer.cc
new file mode 100644
index 0000000..c18a3c3
--- /dev/null
+++ b/pciguard/authorizer.cc
@@ -0,0 +1,91 @@
+// 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/authorizer.h"
+#include "pciguard/pciguard_utils.h"
+
+#include <sysexits.h>
+
+namespace pciguard {
+
+void* Authorizer::AuthorizerThread(void* ptr) {
+  if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) ||
+      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)) {
+    PLOG(ERROR) << __func__ << ": Can't set thread cancel state or type.";
+    exit(EX_OSERR);
+  }
+
+  Authorizer* authorizer = static_cast<Authorizer*>(ptr);
+  Job job;
+  while (authorizer->GetNextJob(&job)) {
+    if (job.type_ == AUTHORIZE_ALL_DEVICES)
+      AuthorizeAllDevices();
+    else
+      AuthorizeThunderboltDev(job.syspath_);
+  }
+  return NULL;
+}
+
+Authorizer::Authorizer()
+    : mutex_(PTHREAD_MUTEX_INITIALIZER),
+      job_available_(PTHREAD_COND_INITIALIZER) {
+  if (pthread_create(&authorizer_thread_, NULL, &Authorizer::AuthorizerThread,
+                     this)) {
+    PLOG(ERROR) << __func__ << ": Problem creating thread. Exiting now";
+    exit(EX_OSERR);
+  }
+  LOG(INFO) << "Created new authorizer object";
+}
+
+Authorizer::~Authorizer() {
+  pthread_cancel(authorizer_thread_);
+  pthread_join(authorizer_thread_, NULL);
+  LOG(INFO) << "Destroyed authorizer object";
+}
+
+void Authorizer::SubmitJob(JobType type, base::FilePath path) {
+  Job job = {type, path};
+
+  if (pthread_mutex_lock(&mutex_)) {
+    PLOG(ERROR) << "Mutex lock issue while submitting job";
+    return;
+  }
+  queue_.push(job);
+  LOG(INFO) << "Inserted authorization job (" << queue_.back().type_ << ","
+            << queue_.back().syspath_ << ")";
+  pthread_cond_signal(&job_available_);
+
+  if (pthread_mutex_unlock(&mutex_)) {
+    PLOG(ERROR) << "Mutex unlock issue while submitting job";
+    return;
+  }
+}
+
+// Pops and returns next authorization job. If no job, then blocks until
+// next job is available
+bool Authorizer::GetNextJob(Job* job) {
+  if (pthread_mutex_lock(&mutex_)) {
+    PLOG(ERROR) << "Mutex lock issue while retrieving job";
+    return false;
+  }
+
+  do {
+    if (!queue_.empty()) {
+      *job = queue_.front();
+      queue_.pop();
+      LOG(INFO) << "Fetched authorization job (" << job->type_ << ","
+                << job->syspath_ << ")";
+
+      if (pthread_mutex_unlock(&mutex_))
+        PLOG(ERROR) << "Mutex unlock issue while retrieving job";
+      return true;
+    }
+  } while (!pthread_cond_wait(&job_available_, &mutex_));
+
+  if (pthread_mutex_unlock(&mutex_))
+    PLOG(ERROR) << "Mutex unlock issue while retrieving job";
+  return false;
+}
+
+}  // namespace pciguard
diff --git a/pciguard/authorizer.h b/pciguard/authorizer.h
new file mode 100644
index 0000000..b97bf3a
--- /dev/null
+++ b/pciguard/authorizer.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef PCIGUARD_AUTHORIZER_H_
+#define PCIGUARD_AUTHORIZER_H_
+
+#include <base/files/file_path.h>
+#include <queue>
+
+namespace pciguard {
+
+// A class for handling all authorization jobs. It maintains a queue of jobs
+// and forks a thread to process that queue (that can be killed when needed)
+class Authorizer {
+ public:
+  Authorizer();
+  Authorizer(const Authorizer&) = delete;
+  Authorizer& operator=(const Authorizer&) = delete;
+  ~Authorizer();
+
+  enum JobType {
+    AUTHORIZE_ALL_DEVICES,
+    AUTHORIZE_1_DEVICE,
+  };
+
+  void SubmitJob(JobType type, base::FilePath path);
+
+ private:
+  struct Job {
+    JobType type_;
+    base::FilePath syspath_;  // syspath for AUTHORIZE_1_DEVICE
+  };
+
+  std::queue<Job> queue_;  // Queue of authorization jobs
+  pthread_mutex_t mutex_;  // To protect the queue
+  pthread_cond_t job_available_;
+
+  pthread_t authorizer_thread_;
+  static void* AuthorizerThread(void* ptr);
+  bool GetNextJob(Job* job);
+};
+
+}  // namespace pciguard
+
+#endif  // PCIGUARD_AUTHORIZER_H_
diff --git a/pciguard/daemon.cc b/pciguard/daemon.cc
new file mode 100644
index 0000000..2ce0284
--- /dev/null
+++ b/pciguard/daemon.cc
@@ -0,0 +1,36 @@
+// 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/daemon.h"
+#include "pciguard/pciguard_utils.h"
+
+#include <sysexits.h>
+
+namespace pciguard {
+
+int Daemon::OnInit() {
+  LOG(INFO) << "pciguard daemon starting...";
+
+  int exit_code = DBusDaemon::OnInit();
+  if (exit_code != EX_OK)
+    return exit_code;
+
+  exit_code = pciguard::OnInit();
+  if (exit_code != EX_OK)
+    return exit_code;
+
+  event_handler_ = std::make_shared<EventHandler>();
+
+  // Begin monitoring the session events
+  session_monitor_ = std::make_unique<SessionMonitor>(bus_, event_handler_);
+
+  // Begin monitoring the thunderbolt udev events
+  tbt_udev_monitor_ = std::make_unique<TbtUdevMonitor>(event_handler_);
+
+  LOG(INFO) << "pciguard daemon started";
+
+  return EX_OK;
+}
+
+}  // namespace pciguard
diff --git a/pciguard/daemon.h b/pciguard/daemon.h
new file mode 100644
index 0000000..fe9d1b2
--- /dev/null
+++ b/pciguard/daemon.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef PCIGUARD_DAEMON_H_
+#define PCIGUARD_DAEMON_H_
+
+#include "pciguard/session_monitor.h"
+#include "pciguard/tbt_udev_monitor.h"
+
+#include <brillo/daemons/dbus_daemon.h>
+#include <memory>
+
+namespace pciguard {
+
+class Daemon : public brillo::DBusDaemon {
+ public:
+  Daemon() = default;
+  Daemon(const Daemon&) = delete;
+  Daemon& operator=(const Daemon&) = delete;
+  ~Daemon() = default;
+
+ protected:
+  int OnInit() override;
+
+ private:
+  std::shared_ptr<EventHandler> event_handler_;
+  std::unique_ptr<SessionMonitor> session_monitor_;
+  std::unique_ptr<TbtUdevMonitor> tbt_udev_monitor_;
+};
+
+}  // namespace pciguard
+
+#endif  // PCIGUARD_DAEMON_H__
diff --git a/pciguard/event_handler.cc b/pciguard/event_handler.cc
new file mode 100644
index 0000000..96c5316
--- /dev/null
+++ b/pciguard/event_handler.cc
@@ -0,0 +1,137 @@
+// 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/event_handler.h"
+#include "pciguard/pciguard_utils.h"
+
+namespace pciguard {
+
+// TODO(b/176184431): Don't assume NO_USER_LOGGED_IN on init.
+EventHandler::EventHandler()
+    : state_(NO_USER_LOGGED_IN), authorizer_(nullptr) {}
+
+void EventHandler::LogEvent(const char ev[]) {
+  const char* states[] = {
+      [NO_USER_LOGGED_IN] = "NO_USER_LOGGED_IN",
+      [USER_LOGGED_IN_BUT_SCREEN_LOCKED] = "USER_LOGGED_IN_BUT_SCREEN_LOCKED",
+      [USER_LOGGED_IN_SCREEN_UNLOCKED] = "USER_LOGGED_IN_SCREEN_UNLOCKED",
+  };
+
+  LOG(INFO) << "CurrentState= " << states[state_] << ", received event=" << ev;
+}
+
+// In a multiuser login scenario, session manager sends session-starting once
+// for every time a user is logged in. So this function could get called
+// multiple times before a single call to OnUserLogout() logs out all the users.
+void EventHandler::OnUserLogin() {
+  DCHECK(!authorizer_);
+  bool user_permission = UserPermissionOK();
+
+  std::lock_guard<std::mutex> lock(lock_);
+  LogEvent("User-Login");
+  LOG(INFO) << "User-Permission = " << user_permission;
+
+  // It is important to have this state check, whenever we go from a more
+  // restrictive state to a less restrictive state to ensure that we always
+  // err on the cautious side should the events arrive out of order or are
+  // processed out of order.
+  if (state_ == NO_USER_LOGGED_IN) {
+    state_ = USER_LOGGED_IN_SCREEN_UNLOCKED;
+    if (user_permission) {
+      authorizer_ = std::make_unique<Authorizer>();
+      authorizer_->SubmitJob(Authorizer::AUTHORIZE_ALL_DEVICES,
+                             base::FilePath(""));
+    }
+  }
+}
+
+void EventHandler::OnUserLogout() {
+  std::lock_guard<std::mutex> lock(lock_);
+  LogEvent("User-Logout");
+
+  // Don't check for current state when going to a super restrictive state.
+  state_ = NO_USER_LOGGED_IN;
+  authorizer_.reset();
+
+  DeauthorizeAllDevices();
+}
+
+void EventHandler::OnScreenLocked() {
+  std::lock_guard<std::mutex> lock(lock_);
+  LogEvent("Screen-Locked");
+
+  // Check to ensure we only allow to go from less restrictive state to more
+  // restrictive state.
+  if (state_ == USER_LOGGED_IN_SCREEN_UNLOCKED)
+    state_ = USER_LOGGED_IN_BUT_SCREEN_LOCKED;
+
+  authorizer_.reset();
+
+  DenyNewDevices();
+}
+
+void EventHandler::OnScreenUnlocked() {
+  DCHECK(!authorizer_);
+  bool user_permission = UserPermissionOK();
+
+  std::lock_guard<std::mutex> lock(lock_);
+  LogEvent("Screen-Unlocked");
+  LOG(INFO) << "User-Permission = " << user_permission;
+
+  // It is important to have this state check, whenever we go from a more
+  // restrictive state to a less restrictive state to ensure that we always
+  // err on the cautious side should the events arrive or are processed out
+  // of order.
+  if (state_ == USER_LOGGED_IN_BUT_SCREEN_LOCKED) {
+    state_ = USER_LOGGED_IN_SCREEN_UNLOCKED;
+    if (user_permission) {
+      authorizer_ = std::make_unique<Authorizer>();
+      authorizer_->SubmitJob(Authorizer::AUTHORIZE_ALL_DEVICES,
+                             base::FilePath(""));
+    }
+  }
+}
+
+void EventHandler::OnNewThunderboltDev(base::FilePath path) {
+  std::lock_guard<std::mutex> lock(lock_);
+  LogEvent("New-Thunderbolt-Dev");
+  LOG(INFO) << "path = " << path;
+
+  if (authorizer_)
+    authorizer_->SubmitJob(Authorizer::AUTHORIZE_1_DEVICE, path);
+}
+
+void EventHandler::OnUserPermissionChanged() {
+  bool user_permission = UserPermissionOK();
+
+  std::lock_guard<std::mutex> lock(lock_);
+
+  if (user_permission) {
+    LogEvent("User-Permission-Allowed");
+    // It is important to have this state check, whenever we go from a more
+    // restrictive state to a less restrictive state to ensure that we always
+    // err on the cautious side should the events arrive or are processed out
+    // of order.
+    if (state_ == USER_LOGGED_IN_SCREEN_UNLOCKED) {
+      if (!authorizer_) {
+        authorizer_ = std::make_unique<Authorizer>();
+        authorizer_->SubmitJob(Authorizer::AUTHORIZE_ALL_DEVICES,
+                               base::FilePath(""));
+      }
+    }
+  } else {
+    LogEvent("User-Permission-Denied");
+    // No state check needed.
+    authorizer_.reset();
+    DeauthorizeAllDevices();
+  }
+}
+
+bool EventHandler::UserPermissionOK() {
+  // TODO(b/172397647): Actually talk to chrome to determine chrome flag value
+  // after chrome flag is implemented.
+  return true;
+}
+
+}  // namespace pciguard
diff --git a/pciguard/event_handler.h b/pciguard/event_handler.h
new file mode 100644
index 0000000..0d9dc71
--- /dev/null
+++ b/pciguard/event_handler.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef PCIGUARD_EVENT_HANDLER_H_
+#define PCIGUARD_EVENT_HANDLER_H_
+
+#include "pciguard/authorizer.h"
+
+#include <memory>
+
+namespace pciguard {
+
+// A class for handling all events.
+class EventHandler {
+ public:
+  EventHandler();
+  EventHandler(const EventHandler&) = delete;
+  EventHandler& operator=(const EventHandler&) = delete;
+  ~EventHandler() = default;
+
+  void OnUserLogin();
+
+  void OnUserLogout();
+
+  void OnScreenLocked();
+
+  void OnScreenUnlocked();
+
+  void OnUserPermissionChanged();
+
+  void OnNewThunderboltDev(base::FilePath path);
+
+ private:
+  enum {
+    NO_USER_LOGGED_IN,
+    USER_LOGGED_IN_BUT_SCREEN_LOCKED,
+    USER_LOGGED_IN_SCREEN_UNLOCKED,
+  } state_;
+
+  std::unique_ptr<Authorizer> authorizer_;
+
+  // Protects concurrent access to state_ and authorizer_
+  std::mutex lock_;
+
+  // Checks for the User Permission from chrome browser
+  bool UserPermissionOK();
+
+  // Logs the event
+  void LogEvent(const char ev[]);
+};
+
+}  // namespace pciguard
+
+#endif  // PCIGUARD_EVENT_HANDLER_H_
diff --git a/pciguard/init/pciguard.conf b/pciguard/init/pciguard.conf
new file mode 100644
index 0000000..e9e00d7
--- /dev/null
+++ b/pciguard/init/pciguard.conf
@@ -0,0 +1,53 @@
+# 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.
+
+description "Daemon to control authorization of external PCI devices"
+author      "chromium-os-dev@chromium.org"
+
+start on started boot-services and started syslog
+stop on stopping system-services
+
+expect fork
+respawn
+respawn limit 3 10
+# TODO(b/175884884): figure out what to do if pciguard crashes
+
+oom score -200
+
+# minijail0 args:
+#  -u -g            : Run as specified user
+#  -c 2             : CAP_DAC_OVERRIDE
+#  -l               : new IPC namespace
+#  -p               : new PID namespace
+#  --uts            : new UTS/hostname namespace
+#  -n               : Set the no_new_privs bit
+#  -v               : new VFS namespace
+#  -P /mnt/empty    : Start with an empty mount
+#  -b /dev/log      : for logging
+#  -b /run/dbus     : for communication over dbus
+#  -b /run/udev     : for receiving udev events
+#  -b /sys/devices/pci0000:00: Needed because devices under /sys/bus/pci/devices
+#                     are symlinks to here.
+#  -b /sys/bus/pci  : to manage the allowlist and the devices
+#  -b /sys/bus/thunderbolt: to auth / deauth thunderbolt devices
+#  -b /usr/lib64    : to link with shared libraries
+#  -b /usr/sbin/pciguard: to access the daemon's binary
+#  -S <policy>      : Use the specified seccomp policy
+
+script
+
+minijail0 -u pciguard -g pciguard -c 2 -l -p --uts -n -v \
+    -P /mnt/empty \
+    -b /dev/log \
+    -b /run/dbus \
+    -b /run/udev \
+    -b /sys/devices/pci0000:00,,1 \
+    -b /sys/bus/pci,,1 \
+    -b /sys/bus/thunderbolt/devices,,1 \
+    -b /usr/lib64 \
+    -b /usr/sbin/pciguard \
+    -S /usr/share/policy/pciguard-seccomp.policy \
+    /usr/sbin/pciguard
+
+end script
diff --git a/pciguard/main.cc b/pciguard/main.cc
new file mode 100644
index 0000000..ab78cd8
--- /dev/null
+++ b/pciguard/main.cc
@@ -0,0 +1,19 @@
+// 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 <base/logging.h>
+#include <brillo/syslog_logging.h>
+
+#include "pciguard/daemon.h"
+
+int main(int argc, char* argv[]) {
+  brillo::InitLog(brillo::kLogToSyslog | brillo::kLogHeader |
+                  brillo::kLogToStderrIfTty);
+
+  LOG(INFO) << "Starting pciguard daemon.\n";
+  pciguard::Daemon daemon;
+
+  daemon.Run();
+  return 0;
+}
diff --git a/pciguard/pciguard_utils.cc b/pciguard/pciguard_utils.cc
new file mode 100644
index 0000000..8abdb0e
--- /dev/null
+++ b/pciguard/pciguard_utils.cc
@@ -0,0 +1,208 @@
+// 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
diff --git a/pciguard/pciguard_utils.h b/pciguard/pciguard_utils.h
new file mode 100644
index 0000000..499e792
--- /dev/null
+++ b/pciguard/pciguard_utils.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef PCIGUARD_PCIGUARD_UTILS_H_
+#define PCIGUARD_PCIGUARD_UTILS_H_
+
+#include <base/files/file_util.h>
+
+namespace pciguard {
+
+int OnInit(void);
+int DeauthorizeAllDevices(void);
+int AuthorizeThunderboltDev(base::FilePath devpath);
+int AuthorizeAllDevices(void);
+int DenyNewDevices(void);
+
+}  // namespace pciguard
+
+#endif  // PCIGUARD_PCIGUARD_UTILS_H_
diff --git a/pciguard/seccomp/pciguard-seccomp-amd64.policy b/pciguard/seccomp/pciguard-seccomp-amd64.policy
new file mode 100644
index 0000000..7ca4bea
--- /dev/null
+++ b/pciguard/seccomp/pciguard-seccomp-amd64.policy
@@ -0,0 +1,58 @@
+# 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.
+
+# Generated using "instructions for generating a seccomp policy" at:
+# https://chromium.googlesource.com/chromiumos/docs/+/master/sandboxing.md
+
+access: 1
+bind: 1
+brk: 1
+clock_getres: 1
+clone: 1
+close: 1
+connect: 1
+creat: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
+exit: 1
+exit_group: 1
+fcntl: 1
+fstat: 1
+futex: 1
+getdents: 1
+getegid: 1
+geteuid: 1
+getgid: 1
+getpid: 1
+getrandom: 1
+getresgid: 1
+getresuid: 1
+getsockname: 1
+gettid: 1
+getuid: 1
+ioctl: arg1 == TCGETS
+lseek: 1
+madvise: 1
+mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+munmap: 1
+openat: 1
+pipe2: 1
+poll: 1
+read: 1
+readlink: 1
+recvmsg: 1
+restart_syscall: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+setsockopt: 1
+signalfd4: 1
+socket: arg0 == AF_UNIX || arg0 == AF_NETLINK
+stat: 1
+tgkill: 1
+write: 1
diff --git a/pciguard/session_monitor.cc b/pciguard/session_monitor.cc
new file mode 100644
index 0000000..a710ae8
--- /dev/null
+++ b/pciguard/session_monitor.cc
@@ -0,0 +1,57 @@
+// 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/session_monitor.h"
+#include <base/bind.h>
+
+namespace pciguard {
+
+namespace {
+
+void OnSignalConnected(const std::string& interface,
+                       const std::string& signal,
+                       bool success) {
+  if (!success) {
+    LOG(ERROR) << "Could not connect to session signal " << signal
+               << "on interface " << interface;
+    exit(EXIT_FAILURE);
+  }
+}
+
+}  // namespace
+
+SessionMonitor::SessionMonitor(scoped_refptr<dbus::Bus> bus,
+                               std::shared_ptr<EventHandler> ev_handler)
+    : proxy_(bus), event_handler_(ev_handler), weak_ptr_factory_(this) {
+  proxy_.RegisterScreenIsLockedSignalHandler(
+      base::BindRepeating(&SessionMonitor::OnScreenIsLocked,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&OnSignalConnected));
+  proxy_.RegisterScreenIsUnlockedSignalHandler(
+      base::BindRepeating(&SessionMonitor::OnScreenIsUnlocked,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&OnSignalConnected));
+  proxy_.RegisterSessionStateChangedSignalHandler(
+      base::BindRepeating(&SessionMonitor::OnSessionStateChanged,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&OnSignalConnected));
+}
+
+void SessionMonitor::OnScreenIsLocked() {
+  event_handler_->OnScreenLocked();
+}
+
+void SessionMonitor::OnScreenIsUnlocked() {
+  event_handler_->OnScreenUnlocked();
+}
+
+void SessionMonitor::OnSessionStateChanged(const std::string& state) {
+  LOG(INFO) << __func__ << "Session state changed, new state = " << state;
+  if (state == "started")
+    event_handler_->OnUserLogin();
+  else if (state == "stopping")
+    event_handler_->OnUserLogout();
+}
+
+}  // namespace pciguard
diff --git a/pciguard/session_monitor.h b/pciguard/session_monitor.h
new file mode 100644
index 0000000..cf61ee3
--- /dev/null
+++ b/pciguard/session_monitor.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef PCIGUARD_SESSION_MONITOR_H_
+#define PCIGUARD_SESSION_MONITOR_H_
+
+#include "pciguard/event_handler.h"
+
+#include <session_manager/dbus-proxies.h>
+#include <memory>
+#include <string>
+
+namespace pciguard {
+
+// A class for monitoring events from the session manager. It is a dumb class
+// and contains the boiler plate code to forward the session events to the
+// event_handler class.
+class SessionMonitor {
+ public:
+  explicit SessionMonitor(scoped_refptr<dbus::Bus> bus,
+                          std::shared_ptr<EventHandler> ev_handler);
+  SessionMonitor(const SessionMonitor&) = delete;
+  SessionMonitor& operator=(const SessionMonitor&) = delete;
+  ~SessionMonitor() = default;
+
+ private:
+  // Handles the ScreenIsLocked DBus signal.
+  void OnScreenIsLocked();
+
+  // Handles the ScreenIsUnlocked DBus signal.
+  void OnScreenIsUnlocked();
+
+  // Handles the SessionStateChanged DBus signal.
+  void OnSessionStateChanged(const std::string& state);
+
+  org::chromium::SessionManagerInterfaceProxy proxy_;
+  std::shared_ptr<EventHandler> event_handler_;
+  base::WeakPtrFactory<SessionMonitor> weak_ptr_factory_;
+};
+
+}  // namespace pciguard
+
+#endif  // PCIGUARD_SESSION_MONITOR_H_
diff --git a/pciguard/tbt_udev_monitor.cc b/pciguard/tbt_udev_monitor.cc
new file mode 100644
index 0000000..6e43759
--- /dev/null
+++ b/pciguard/tbt_udev_monitor.cc
@@ -0,0 +1,81 @@
+// 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/tbt_udev_monitor.h"
+
+namespace pciguard {
+
+namespace {
+
+const char kUdev[] = "udev";
+const char kThunderboltSubsystem[] = "thunderbolt";
+const char kThunderboltDevice[] = "thunderbolt_device";
+
+}  // namespace
+
+TbtUdevMonitor::TbtUdevMonitor(std::shared_ptr<EventHandler> ev_handler)
+    : event_handler_(ev_handler) {
+  udev_ = brillo::Udev::Create();
+  if (!udev_) {
+    PLOG(ERROR) << "Failed to initialize udev object.";
+    exit(EXIT_FAILURE);
+  }
+
+  udev_monitor_ = udev_->CreateMonitorFromNetlink(kUdev);
+  if (!udev_monitor_) {
+    PLOG(ERROR) << "Failed to create udev monitor.";
+    exit(EXIT_FAILURE);
+  }
+
+  if (!udev_monitor_->FilterAddMatchSubsystemDeviceType(kThunderboltSubsystem,
+                                                        kThunderboltDevice)) {
+    PLOG(ERROR) << "Failed to add thunderbolt subsystem to udev monitor.";
+    exit(EXIT_FAILURE);
+  }
+
+  if (!udev_monitor_->EnableReceiving()) {
+    PLOG(ERROR) << "Failed to enable receiving for udev monitor.";
+    exit(EXIT_FAILURE);
+  }
+
+  int fd = udev_monitor_->GetFileDescriptor();
+  if (fd == brillo::UdevMonitor::kInvalidFileDescriptor) {
+    PLOG(ERROR) << "Failed to get udev monitor fd.";
+    exit(EXIT_FAILURE);
+  }
+
+  udev_monitor_watcher_ = base::FileDescriptorWatcher::WatchReadable(
+      fd, base::BindRepeating(&TbtUdevMonitor::OnThunderboltUdevEvent,
+                              base::Unretained(this)));
+  if (!udev_monitor_watcher_) {
+    PLOG(ERROR) << "Failed to start watcher for udev monitor fd.";
+    exit(EXIT_FAILURE);
+  }
+}
+
+void TbtUdevMonitor::OnThunderboltUdevEvent() {
+  auto device = udev_monitor_->ReceiveDevice();
+  if (!device) {
+    LOG(ERROR) << "Udev receive device failed.";
+    return;
+  }
+
+  auto path = base::FilePath(device->GetSysPath());
+  if (path.empty()) {
+    LOG(ERROR) << "Failed to get device syspath.";
+    return;
+  }
+
+  auto action = std::string(device->GetAction());
+  if (action.empty()) {
+    LOG(ERROR) << "Failed to get device action.";
+    return;
+  }
+
+  if (action == "add") {
+    event_handler_->OnNewThunderboltDev(path);
+  }
+}
+
+}  // namespace pciguard
diff --git a/pciguard/tbt_udev_monitor.h b/pciguard/tbt_udev_monitor.h
new file mode 100644
index 0000000..ebc1856
--- /dev/null
+++ b/pciguard/tbt_udev_monitor.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef PCIGUARD_TBT_UDEV_MONITOR_H_
+#define PCIGUARD_TBT_UDEV_MONITOR_H_
+
+#include <libudev.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <base/files/file_descriptor_watcher_posix.h>
+#include <base/files/file_path.h>
+#include <brillo/udev/udev.h>
+#include <brillo/udev/udev_device.h>
+#include <brillo/udev/udev_monitor.h>
+
+#include "pciguard/event_handler.h"
+
+namespace pciguard {
+
+// Class to monitor thunderbolt udev events
+class TbtUdevMonitor {
+ public:
+  explicit TbtUdevMonitor(std::shared_ptr<EventHandler> ev_handler);
+  TbtUdevMonitor(const TbtUdevMonitor&) = delete;
+  TbtUdevMonitor& operator=(const TbtUdevMonitor&) = delete;
+  ~TbtUdevMonitor() = default;
+
+ private:
+  // Handle Udev events emanating from |udev_monitor_watcher_|.
+  void OnThunderboltUdevEvent();
+
+  std::unique_ptr<brillo::Udev> udev_;
+  std::unique_ptr<brillo::UdevMonitor> udev_monitor_;
+  std::unique_ptr<base::FileDescriptorWatcher::Controller>
+      udev_monitor_watcher_;
+  std::shared_ptr<EventHandler> event_handler_;
+};
+
+}  // namespace pciguard
+
+#endif  // PCIGUARD_TBT_UDEV_MONITOR_H_