blob: e5c17e9c7a35ce6a3dc779dcbd9427eb6eacb1c9 [file] [log] [blame]
// 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::fmt::{self, Display};
use std::fs;
use std::ops::BitOrAssign;
use std::os::unix::io::RawFd;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use sys_util::{EventFd, GuestMemory, GuestMemoryError, PollContext, PollToken};
use tpm2;
use super::{DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_TPM};
// 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";
struct Worker {
queue: Queue,
mem: GuestMemory,
interrupt_status: Arc<AtomicUsize>,
queue_evt: EventFd,
kill_evt: EventFd,
interrupt_evt: EventFd,
interrupt_resample_evt: EventFd,
device: Device,
}
struct Device {
simulator: tpm2::Simulator,
}
// 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> {
let (read_desc, write_desc) = two_input_descriptors(desc)?;
if read_desc.len > TPM_BUFSIZE as u32 {
return Err(Error::CommandTooLong {
size: read_desc.len as usize,
});
}
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(&command);
if response.len() > TPM_BUFSIZE {
return Err(Error::ResponseTooLong {
size: response.len(),
});
}
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, write_desc.addr)
.map_err(Error::Write)?;
Ok(response.len() as u32)
}
}
impl Worker {
fn process_queue(&mut self) -> NeedsInterrupt {
let avail_desc = match self.queue.iter(&self.mem).next() {
Some(avail_desc) => avail_desc,
None => return NeedsInterrupt::No,
};
let index = avail_desc.index;
let len = match self.device.perform_work(&self.mem, avail_desc) {
Ok(len) => len,
Err(err) => {
error!("{}", err);
0
}
};
self.queue.add_used(&self.mem, index, len);
NeedsInterrupt::Yes
}
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) {
#[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(&self.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(&self.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 = NeedsInterrupt::No;
for event in events.iter_readable() {
match event.token() {
Token::QueueAvailable => {
if let Err(e) = self.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 == NeedsInterrupt::Yes {
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,
interrupt_status: Arc<AtomicUsize>,
mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) {
if queues.len() != 1 || queue_evts.len() != 1 {
return;
}
let queue = queues.remove(0);
let queue_evt = queue_evts.remove(0);
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 worker = Worker {
queue,
mem,
interrupt_status,
queue_evt,
interrupt_evt,
interrupt_resample_evt,
kill_evt,
device: Device { simulator },
};
let worker_result = thread::Builder::new()
.name("virtio_tpm".to_string())
.spawn(|| worker.run());
if let Err(e) = worker_result {
error!("vtpm failed to spawn virtio_tpm worker: {}", e);
return;
}
}
}
#[derive(PartialEq)]
enum NeedsInterrupt {
Yes,
No,
}
impl BitOrAssign for NeedsInterrupt {
fn bitor_assign(&mut self, rhs: NeedsInterrupt) {
if rhs == NeedsInterrupt::Yes {
*self = NeedsInterrupt::Yes;
}
}
}
type Result<T> = std::result::Result<T, Error>;
enum Error {
ExpectedReadOnly,
ExpectedSecondBuffer,
ExpectedWriteOnly,
CommandTooLong { size: usize },
Read(GuestMemoryError),
ResponseTooLong { size: usize },
BufferTooSmall { size: usize, required: usize },
Write(GuestMemoryError),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
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),
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: {} < {} bytes",
size, required
),
Write(e) => write!(f, "vtpm failed to write to guest memory: {}", e),
}
}
}