summary refs log tree commit diff
diff options
context:
space:
mode:
authorDavid Tolnay <dtolnay@chromium.org>2018-12-20 11:49:46 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-01-24 07:43:30 -0800
commitde6b29ab9d1a90a56874e6b33d35b5bf077acbfc (patch)
treee513ca66f2c91366007925419ccf7a934a91b7c8
parent5c8dae6ad7829b2db5317e0431d0ab2de5a9d725 (diff)
downloadcrosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.tar
crosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.tar.gz
crosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.tar.bz2
crosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.tar.lz
crosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.tar.xz
crosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.tar.zst
crosvm-de6b29ab9d1a90a56874e6b33d35b5bf077acbfc.zip
tpm: Virtio tpm device
This CL adds a "tpm" Cargo cfg to crosvm which enables a TPM device
backed by libtpm2 simulator.

Tested by running the following inside cros_sdk:

    LIBRARY_PATH=~/src/minijail LD_LIBRARY_PATH=~/src/minijail \
        cargo run --release \
        --features tpm \
        -- \
        run \
        -r rootfs.ext4 \
        --seccomp-policy-dir seccomp/x86_64/ \
        -p init=/bin/bash \
        -p panic=-1 \
        --disable-sandbox \
        vmlinux.bin

with a Linux image built from CL:1387655.

The TPM self test completes successfully with the following output:

    https://paste.googleplex.com/5996075978588160?raw

Justin's TPM playground runs with the following trace output.

    https://paste.googleplex.com/4909751007707136?raw

Design doc: go/vtpm-for-glinux

TEST=ran TPM playground program inside crosvm
TEST=local kokoro
BUG=chromium:911799

Change-Id: I2feb24a3e38cba91f62c6d2cd1f378de4dd03ecf
Reviewed-on: https://chromium-review.googlesource.com/1387624
Commit-Ready: David Tolnay <dtolnay@chromium.org>
Tested-by: David Tolnay <dtolnay@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
-rw-r--r--Cargo.lock25
-rw-r--r--Cargo.toml1
-rw-r--r--devices/Cargo.toml2
-rw-r--r--devices/src/lib.rs2
-rw-r--r--devices/src/virtio/mod.rs7
-rw-r--r--devices/src/virtio/tpm.rs265
-rw-r--r--seccomp/arm/tpm_device.policy33
-rw-r--r--seccomp/x86_64/tpm_device.policy34
-rw-r--r--src/linux.rs15
-rw-r--r--tpm2-sys/Cargo.toml2
-rw-r--r--tpm2-sys/build.rs11
-rw-r--r--tpm2/Cargo.toml2
-rw-r--r--tpm2/src/lib.rs4
13 files changed, 392 insertions, 11 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 86ba831..2217466 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -139,6 +139,7 @@ dependencies = [
  "resources 0.1.0",
  "sync 0.1.0",
  "sys_util 0.1.0",
+ "tpm2 0.1.0",
  "vhost 0.1.0",
  "virtio_sys 0.1.0",
  "vm_control 0.1.0",
@@ -267,6 +268,14 @@ dependencies = [
 ]
 
 [[package]]
+name = "num_cpus"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "p9"
 version = "0.1.0"
 dependencies = [
@@ -407,6 +416,21 @@ dependencies = [
 ]
 
 [[package]]
+name = "tpm2"
+version = "0.1.0"
+dependencies = [
+ "tpm2-sys 0.1.0",
+]
+
+[[package]]
+name = "tpm2-sys"
+version = "0.1.0"
+dependencies = [
+ "num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "unicode-width"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -492,6 +516,7 @@ dependencies = [
 "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
 "checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311"
 "checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
+"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
 "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
 "checksum proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ab2fc21ba78ac73e4ff6b3818ece00be4e175ffbef4d0a717d978b48b24150c4"
 "checksum protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bec26e67194b7d991908145fdf21b7cae8b08423d96dcb9e860cd31f854b9506"
diff --git a/Cargo.toml b/Cargo.toml
index d255e9d..dd720fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"]
 gpu = ["devices/gpu"]
 usb-emulation = ["usb_util"]
 sandboxed-libusb = ["usb_util/sandboxed-libusb"]
+tpm = ["devices/tpm"]
 
 [dependencies]
 arch = { path = "arch" }
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 46f326d..58877d4 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -6,6 +6,7 @@ authors = ["The Chromium OS Authors"]
 [features]
 wl-dmabuf = []
 gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"]
+tpm = ["tpm2"]
 
 [dependencies]
 bit_field = { path = "../bit_field" }
@@ -25,6 +26,7 @@ p9 = { path = "../p9" }
 resources = { path = "../resources" }
 sync = { path = "../sync" }
 sys_util = { path = "../sys_util" }
+tpm2 = { path = "../tpm2", optional = true }
 vhost = { path = "../vhost" }
 virtio_sys = { path = "../virtio_sys" }
 vm_control = { path = "../vm_control" }
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index b39116e..edc886a 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -19,6 +19,8 @@ extern crate resources;
 extern crate sync;
 #[macro_use]
 extern crate sys_util;
+#[cfg(feature = "tpm")]
+extern crate tpm2;
 extern crate vhost;
 extern crate virtio_sys;
 extern crate vm_control;
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index ed152b2..5a46bea 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -12,6 +12,8 @@ mod net;
 mod p9;
 mod queue;
 mod rng;
+#[cfg(feature = "tpm")]
+mod tpm;
 mod virtio_device;
 mod virtio_pci_common_config;
 mod virtio_pci_device;
@@ -28,6 +30,8 @@ pub use self::net::*;
 pub use self::p9::*;
 pub use self::queue::*;
 pub use self::rng::*;
+#[cfg(feature = "tpm")]
+pub use self::tpm::*;
 pub use self::virtio_device::*;
 pub use self::virtio_pci_device::*;
 pub use self::wl::*;
@@ -47,7 +51,10 @@ const TYPE_BALLOON: u32 = 5;
 const TYPE_GPU: u32 = 16;
 const TYPE_9P: u32 = 9;
 const TYPE_VSOCK: u32 = 19;
+// Additional types invented by crosvm
 const TYPE_WL: u32 = 30;
+#[cfg(feature = "tpm")]
+const TYPE_TPM: u32 = 31;
 
 const VIRTIO_F_VERSION_1: u32 = 32;
 
diff --git a/devices/src/virtio/tpm.rs b/devices/src/virtio/tpm.rs
new file mode 100644
index 0000000..66574e2
--- /dev/null
+++ b/devices/src/virtio/tpm.rs
@@ -0,0 +1,265 @@
+// 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.
+
+use std::env;
+use std::fs;
+use std::os::unix::io::RawFd;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+use std::thread;
+
+use sys_util::{EventFd, GuestMemory, PollContext, PollToken};
+use tpm2;
+
+use super::{DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_TPM};
+
+const QUEUE_SIZE: u16 = 1;
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
+
+// Simply store TPM state in /tmp/tpm-simulator. Before shipping this feature,
+// will need to move state under /run/vm instead. https://crbug.com/921841
+const SIMULATOR_DIR: &str = "/tmp/tpm-simulator";
+
+struct Worker {
+    queue: Queue,
+    mem: GuestMemory,
+    interrupt_status: Arc<AtomicUsize>,
+    interrupt_evt: EventFd,
+    interrupt_resample_evt: EventFd,
+    device: Device,
+}
+
+struct Device {
+    simulator: tpm2::Simulator,
+    response: Option<Vec<u8>>,
+}
+
+impl Device {
+    fn perform_send(&mut self, mem: &GuestMemory, avail_desc: DescriptorChain) -> u32 {
+        if self.response.is_some() {
+            error!("vtpm encountered unexpected send");
+            return 0;
+        }
+
+        let mut len = 0;
+        let mut buf = vec![0u8; avail_desc.len as usize];
+        match mem.read_exact_at_addr(&mut buf, avail_desc.addr) {
+            Ok(()) => {
+                let response = self.simulator.execute_command(&buf);
+                self.response = Some(response.to_owned());
+                len = avail_desc.len;
+            }
+            Err(err) => {
+                error!("vtpm failed read from guest memory: {}", err);
+            }
+        }
+        len
+    }
+
+    fn perform_recv(&mut self, mem: &GuestMemory, avail_desc: DescriptorChain) -> u32 {
+        let buf = match self.response.take() {
+            Some(buf) => buf,
+            None => {
+                error!("vtpm encountered unexpected recv");
+                return 0;
+            }
+        };
+
+        assert!(buf.len() <= avail_desc.len as usize);
+
+        let mut len = 0;
+        match mem.write_all_at_addr(&buf, avail_desc.addr) {
+            Ok(()) => len = buf.len() as u32,
+            Err(err) => {
+                error!("vtpm failed write to guest memory: {}", err);
+            }
+        }
+        len
+    }
+}
+
+impl Worker {
+    fn process_queue(&mut self) -> bool {
+        let queue = &mut self.queue;
+
+        let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
+        let mut used_count = 0;
+        for avail_desc in queue.iter(&self.mem) {
+            let index = avail_desc.index;
+            let len = if avail_desc.is_read_only() {
+                self.device.perform_send(&self.mem, avail_desc)
+            } else if avail_desc.is_write_only() {
+                self.device.perform_recv(&self.mem, avail_desc)
+            } else {
+                error!("vtpm expected either read or write descriptor");
+                0
+            };
+
+            used_desc_heads[used_count] = (index, len);
+            used_count += 1;
+        }
+
+        for &(desc_index, len) in &used_desc_heads[..used_count] {
+            queue.add_used(&self.mem, desc_index, len);
+        }
+        used_count > 0
+    }
+
+    fn signal_used_queue(&self) {
+        self.interrupt_status
+            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
+        let _ = self.interrupt_evt.write(1);
+    }
+
+    fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) {
+        #[derive(PollToken, Debug)]
+        enum Token {
+            // A request is ready on the queue.
+            QueueAvailable,
+            // Check if any interrupts need to be re-asserted.
+            InterruptResample,
+            // The parent thread requested an exit.
+            Kill,
+        }
+
+        let poll_ctx = match PollContext::new()
+            .and_then(|pc| pc.add(&queue_evt, Token::QueueAvailable).and(Ok(pc)))
+            .and_then(|pc| {
+                pc.add(&self.interrupt_resample_evt, Token::InterruptResample)
+                    .and(Ok(pc))
+            })
+            .and_then(|pc| pc.add(&kill_evt, Token::Kill).and(Ok(pc)))
+        {
+            Ok(pc) => pc,
+            Err(e) => {
+                error!("vtpm failed creating PollContext: {:?}", e);
+                return;
+            }
+        };
+
+        'poll: loop {
+            let events = match poll_ctx.wait() {
+                Ok(v) => v,
+                Err(e) => {
+                    error!("vtpm failed polling for events: {:?}", e);
+                    break;
+                }
+            };
+
+            let mut needs_interrupt = false;
+            for event in events.iter_readable() {
+                match event.token() {
+                    Token::QueueAvailable => {
+                        if let Err(e) = queue_evt.read() {
+                            error!("vtpm failed reading queue EventFd: {:?}", e);
+                            break 'poll;
+                        }
+                        needs_interrupt |= self.process_queue();
+                    }
+                    Token::InterruptResample => {
+                        let _ = self.interrupt_resample_evt.read();
+                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
+                            let _ = self.interrupt_evt.write(1);
+                        }
+                    }
+                    Token::Kill => break 'poll,
+                }
+            }
+            if needs_interrupt {
+                self.signal_used_queue();
+            }
+        }
+    }
+}
+
+/// Virtio vTPM device.
+pub struct Tpm {
+    kill_evt: Option<EventFd>,
+}
+
+impl Tpm {
+    pub fn new() -> Tpm {
+        Tpm { kill_evt: None }
+    }
+}
+
+impl Drop for Tpm {
+    fn drop(&mut self) {
+        if let Some(kill_evt) = self.kill_evt.take() {
+            let _ = kill_evt.write(1);
+        }
+    }
+}
+
+impl VirtioDevice for Tpm {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        Vec::new()
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_TPM
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn activate(
+        &mut self,
+        mem: GuestMemory,
+        interrupt_evt: EventFd,
+        interrupt_resample_evt: EventFd,
+        status: Arc<AtomicUsize>,
+        mut queues: Vec<Queue>,
+        mut queue_evts: Vec<EventFd>,
+    ) {
+        if queues.len() != 1 || queue_evts.len() != 1 {
+            return;
+        }
+
+        if let Err(err) = fs::create_dir_all(SIMULATOR_DIR) {
+            error!("vtpm failed to create directory for simulator: {}", err);
+            return;
+        }
+        if let Err(err) = env::set_current_dir(SIMULATOR_DIR) {
+            error!("vtpm failed to change into simulator directory: {}", err);
+            return;
+        }
+        let simulator = tpm2::Simulator::singleton_in_current_directory();
+
+        let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
+            Ok(v) => v,
+            Err(err) => {
+                error!("vtpm failed to create kill EventFd pair: {:?}", err);
+                return;
+            }
+        };
+        self.kill_evt = Some(self_kill_evt);
+
+        let queue = queues.remove(0);
+
+        let worker_result =
+            thread::Builder::new()
+                .name("virtio_tpm".to_string())
+                .spawn(move || {
+                    let mut worker = Worker {
+                        queue,
+                        mem,
+                        interrupt_status: status,
+                        interrupt_evt,
+                        interrupt_resample_evt,
+                        device: Device {
+                            simulator,
+                            response: None,
+                        },
+                    };
+                    worker.run(queue_evts.remove(0), kill_evt);
+                });
+
+        if let Err(e) = worker_result {
+            error!("vtpm failed to spawn virtio_tpm worker: {}", e);
+            return;
+        }
+    }
+}
diff --git a/seccomp/arm/tpm_device.policy b/seccomp/arm/tpm_device.policy
new file mode 100644
index 0000000..7d2df8f
--- /dev/null
+++ b/seccomp/arm/tpm_device.policy
@@ -0,0 +1,33 @@
+# 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.
+
+close: 1
+dup: 1
+dup2: 1
+exit_group: 1
+futex: 1
+gettimeofday: 1
+# Disallow mmap with PROT_EXEC set.  The syntax here doesn't allow bit
+# negation, thus the manually negated mask constant.
+mmap2: arg2 in 0xfffffffb
+mprotect: arg2 in 0xfffffffb
+munmap: 1
+read: 1
+recv: 1
+sched_getaffinity: 1
+set_robust_list: 1
+sigaltstack: 1
+# Disallow clone's other than new threads.
+clone: arg0 & 0x00010000
+write: 1
+eventfd2: 1
+poll: 1
+ppoll: 1
+getpid: 1
+# Allow PR_SET_NAME only.
+prctl: arg0 == 15
+restart_syscall: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
diff --git a/seccomp/x86_64/tpm_device.policy b/seccomp/x86_64/tpm_device.policy
new file mode 100644
index 0000000..0a44ecd
--- /dev/null
+++ b/seccomp/x86_64/tpm_device.policy
@@ -0,0 +1,34 @@
+# 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.
+
+close: 1
+dup: 1
+dup2: 1
+exit_group: 1
+futex: 1
+# Disallow mmap with PROT_EXEC set.  The syntax here doesn't allow bit
+# negation, thus the manually negated mask constant.
+mmap: arg2 in 0xfffffffb
+mprotect: arg2 in 0xfffffffb
+# Allow MADV_DONTDUMP only.
+madvise: arg2 == 0x00000010
+munmap: 1
+read: 1
+recvfrom: 1
+sched_getaffinity: 1
+set_robust_list: 1
+sigaltstack: 1
+# Disallow clone's other than new threads.
+clone: arg0 & 0x00010000
+write: 1
+eventfd2: 1
+poll: 1
+ppoll: 1
+getpid: 1
+# Allow PR_SET_NAME only.
+prctl: arg0 == 15
+restart_syscall: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
diff --git a/src/linux.rs b/src/linux.rs
index ff6b133..1dcedbe 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -304,6 +304,21 @@ fn create_virtio_devs(
         jail: rng_jail,
     });
 
+    #[cfg(feature = "tpm")]
+    {
+        let tpm_box = Box::new(devices::virtio::Tpm::new());
+        let tpm_jail = if cfg.multiprocess {
+            let policy_path = cfg.seccomp_policy_dir.join("tpm_device.policy");
+            Some(create_base_minijail(empty_root_path, &policy_path)?)
+        } else {
+            None
+        };
+        devs.push(VirtioDeviceStub {
+            dev: tpm_box,
+            jail: tpm_jail,
+        });
+    }
+
     let balloon_box = Box::new(
         devices::virtio::Balloon::new(balloon_device_socket).map_err(Error::BalloonDeviceNew)?,
     );
diff --git a/tpm2-sys/Cargo.toml b/tpm2-sys/Cargo.toml
index 0297bf0..12dc5ac 100644
--- a/tpm2-sys/Cargo.toml
+++ b/tpm2-sys/Cargo.toml
@@ -8,5 +8,3 @@ links = "tpm2"
 [build-dependencies]
 num_cpus = "*"
 pkg-config = "*"
-
-[workspace]
diff --git a/tpm2-sys/build.rs b/tpm2-sys/build.rs
index f1abd4a..604d936 100644
--- a/tpm2-sys/build.rs
+++ b/tpm2-sys/build.rs
@@ -8,10 +8,11 @@ use std::path::Path;
 use std::process::{self, Command};
 
 fn main() -> io::Result<()> {
-    println!("cargo:rustc-link-lib=ssl");
-    println!("cargo:rustc-link-lib=crypto");
-
-    if pkg_config::probe_library("libtpm2").is_ok() {
+    if pkg_config::Config::new()
+        .statik(true)
+        .probe("libtpm2")
+        .is_ok()
+    {
         // Use tpm2 package from the standard system location if available.
         return Ok(());
     }
@@ -44,5 +45,7 @@ fn main() -> io::Result<()> {
     let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
     println!("cargo:rustc-link-search={}/libtpm2/build", dir);
     println!("cargo:rustc-link-lib=static=tpm2");
+    println!("cargo:rustc-link-lib=ssl");
+    println!("cargo:rustc-link-lib=crypto");
     Ok(())
 }
diff --git a/tpm2/Cargo.toml b/tpm2/Cargo.toml
index 80a43bc..56ee5fb 100644
--- a/tpm2/Cargo.toml
+++ b/tpm2/Cargo.toml
@@ -6,5 +6,3 @@ edition = "2018"
 
 [dependencies]
 tpm2-sys = { path = "../tpm2-sys" }
-
-[workspace]
diff --git a/tpm2/src/lib.rs b/tpm2/src/lib.rs
index 53022c6..4de849c 100644
--- a/tpm2/src/lib.rs
+++ b/tpm2/src/lib.rs
@@ -165,9 +165,7 @@ fn tpm_manufacture(first_time: bool) {
     // Unsafe only because this is over FFI and we need to know that the
     // signature declared by tpm2-sys is ABI-compatible with the symbol provided
     // by libtpm2. There are no other invariants to uphold.
-    let ret: c_int = unsafe {
-        tpm2_sys::TPM_Manufacture(first_time as c_int)
-    };
+    let ret: c_int = unsafe { tpm2_sys::TPM_Manufacture(first_time as c_int) };
 
     // We expect that the TPM must not already have been manufactured. The
     // SIMULATOR_EXISTS atomic flag guards calls to this function such that only