diff options
author | David Tolnay <dtolnay@chromium.org> | 2018-12-20 11:49:46 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-01-24 07:43:30 -0800 |
commit | de6b29ab9d1a90a56874e6b33d35b5bf077acbfc (patch) | |
tree | e513ca66f2c91366007925419ccf7a934a91b7c8 | |
parent | 5c8dae6ad7829b2db5317e0431d0ab2de5a9d725 (diff) | |
download | crosvm-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.lock | 25 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | devices/Cargo.toml | 2 | ||||
-rw-r--r-- | devices/src/lib.rs | 2 | ||||
-rw-r--r-- | devices/src/virtio/mod.rs | 7 | ||||
-rw-r--r-- | devices/src/virtio/tpm.rs | 265 | ||||
-rw-r--r-- | seccomp/arm/tpm_device.policy | 33 | ||||
-rw-r--r-- | seccomp/x86_64/tpm_device.policy | 34 | ||||
-rw-r--r-- | src/linux.rs | 15 | ||||
-rw-r--r-- | tpm2-sys/Cargo.toml | 2 | ||||
-rw-r--r-- | tpm2-sys/build.rs | 11 | ||||
-rw-r--r-- | tpm2/Cargo.toml | 2 | ||||
-rw-r--r-- | tpm2/src/lib.rs | 4 |
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 |