diff options
author | David Tolnay <dtolnay@chromium.org> | 2019-02-19 18:46:58 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-02-21 06:29:41 -0800 |
commit | 42e5fbd9f33eff538ac36fe0935e2973ede5c281 (patch) | |
tree | 9bb2f81f27ff009f0d7a289c7700d84f02183a57 /devices/src/virtio/tpm.rs | |
parent | 16d444563a539e8750cfae8198b48df50d033070 (diff) | |
download | crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.tar crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.tar.gz crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.tar.bz2 crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.tar.lz crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.tar.xz crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.tar.zst crosvm-42e5fbd9f33eff538ac36fe0935e2973ede5c281.zip |
tpm: Handle send+recv as a single descriptor chain
During review of CL:1387655 we observed that it shouldn't be necessary for both vtpm_op_send and vtpm_op_recv to perform virtqueue kicks. It should be sufficient for vtpm_op_send to place both an output buffer and an input buffer on the virtio queue as a single descriptor chain, and perform a single kick that executes both operations. This requires a larger virtio queue because a single virtio buffer cannot be both read and written. BUG=chromium:911799 TEST=run TPM playground program inside crosvm Change-Id: I6822efc3318a3952f91f64904e0434d916beae97 Reviewed-on: https://chromium-review.googlesource.com/1465642 Commit-Ready: David Tolnay <dtolnay@chromium.org> Tested-by: David Tolnay <dtolnay@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'devices/src/virtio/tpm.rs')
-rw-r--r-- | devices/src/virtio/tpm.rs | 108 |
1 files changed, 70 insertions, 38 deletions
diff --git a/devices/src/virtio/tpm.rs b/devices/src/virtio/tpm.rs index 35a1eb6..e5c17e9 100644 --- a/devices/src/virtio/tpm.rs +++ b/devices/src/virtio/tpm.rs @@ -16,9 +16,17 @@ use tpm2; use super::{DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_TPM}; -const QUEUE_SIZE: u16 = 1; +// A single queue of size 2. The guest kernel driver will enqueue a single +// descriptor chain containing one command buffer and one response buffer at a +// time. +const QUEUE_SIZE: u16 = 2; const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE]; +// Maximum command or response message size permitted by this device +// implementation. Named to match the equivalent constant in Linux's tpm.h. +// There is no hard requirement that the value is the same but it makes sense. +const TPM_BUFSIZE: usize = 4096; + // 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"; @@ -36,45 +44,60 @@ struct Worker { struct Device { simulator: tpm2::Simulator, - response: Option<Vec<u8>>, +} + +// Checks that the input descriptor chain holds a read-only descriptor followed +// by a write-only descriptor, as required of the guest's virtio tpm driver. +// +// Returns those descriptors as a tuple: `(read_desc, write_desc)`. +fn two_input_descriptors(desc: DescriptorChain) -> Result<(DescriptorChain, DescriptorChain)> { + let read_desc = desc; + if !read_desc.is_read_only() { + return Err(Error::ExpectedReadOnly); + } + + let write_desc = match read_desc.next_descriptor() { + Some(desc) => desc, + None => return Err(Error::ExpectedSecondBuffer), + }; + + if !write_desc.is_write_only() { + return Err(Error::ExpectedWriteOnly); + } + + Ok((read_desc, write_desc)) } impl Device { fn perform_work(&mut self, mem: &GuestMemory, desc: DescriptorChain) -> Result<u32> { - if desc.is_read_only() { - self.perform_send(&mem, desc) - } else if desc.is_write_only() { - self.perform_recv(&mem, desc) - } else { - Err(Error::ExpectedReadOrWrite) - } - } + let (read_desc, write_desc) = two_input_descriptors(desc)?; - fn perform_send(&mut self, mem: &GuestMemory, desc: DescriptorChain) -> Result<u32> { - if self.response.is_some() { - return Err(Error::UnexpectedSend); + if read_desc.len > TPM_BUFSIZE as u32 { + return Err(Error::CommandTooLong { + size: read_desc.len as usize, + }); } - let mut buf = vec![0u8; desc.len as usize]; - mem.read_exact_at_addr(&mut buf, desc.addr) + let mut command = vec![0u8; read_desc.len as usize]; + mem.read_exact_at_addr(&mut command, read_desc.addr) .map_err(Error::Read)?; - let response = self.simulator.execute_command(&buf); - self.response = Some(response.to_owned()); - Ok(desc.len) - } + let response = self.simulator.execute_command(&command); - fn perform_recv(&mut self, mem: &GuestMemory, desc: DescriptorChain) -> Result<u32> { - let response = self.response.take().ok_or(Error::UnexpectedRecv)?; + if response.len() > TPM_BUFSIZE { + return Err(Error::ResponseTooLong { + size: response.len(), + }); + } - if response.len() > desc.len as usize { - return Err(Error::SmallBuffer { - size: desc.len as usize, + if response.len() > write_desc.len as usize { + return Err(Error::BufferTooSmall { + size: write_desc.len as usize, required: response.len(), }); } - mem.write_all_at_addr(&response, desc.addr) + mem.write_all_at_addr(&response, write_desc.addr) .map_err(Error::Write)?; Ok(response.len() as u32) @@ -243,10 +266,7 @@ impl VirtioDevice for Tpm { interrupt_evt, interrupt_resample_evt, kill_evt, - device: Device { - simulator, - response: None, - }, + device: Device { simulator }, }; let worker_result = thread::Builder::new() @@ -277,11 +297,13 @@ impl BitOrAssign for NeedsInterrupt { type Result<T> = std::result::Result<T, Error>; enum Error { - ExpectedReadOrWrite, - UnexpectedSend, + ExpectedReadOnly, + ExpectedSecondBuffer, + ExpectedWriteOnly, + CommandTooLong { size: usize }, Read(GuestMemoryError), - UnexpectedRecv, - SmallBuffer { size: usize, required: usize }, + ResponseTooLong { size: usize }, + BufferTooSmall { size: usize, required: usize }, Write(GuestMemoryError), } @@ -290,13 +312,23 @@ impl Display for Error { use self::Error::*; match self { - ExpectedReadOrWrite => write!(f, "vtpm expected either read or write descriptor"), - UnexpectedSend => write!(f, "vtpm encountered unexpected send"), + ExpectedReadOnly => write!(f, "vtpm expected first descriptor to be read-only"), + ExpectedSecondBuffer => write!(f, "vtpm expected a second descriptor"), + ExpectedWriteOnly => write!(f, "vtpm expected second descriptor to be write-only"), + CommandTooLong { size } => write!( + f, + "vtpm command is too long: {} > {} bytes", + size, TPM_BUFSIZE + ), Read(e) => write!(f, "vtpm failed to read from guest memory: {}", e), - UnexpectedRecv => write!(f, "vtpm encountered unexpected recv"), - SmallBuffer { size, required } => write!( + ResponseTooLong { size } => write!( + f, + "vtpm simulator generated a response that is unexpectedly long: {} > {} bytes", + size, TPM_BUFSIZE + ), + BufferTooSmall { size, required } => write!( f, - "vtpm response buffer is too small: {} < {}", + "vtpm response buffer is too small: {} < {} bytes", size, required ), Write(e) => write!(f, "vtpm failed to write to guest memory: {}", e), |