Files
cloud-hypervisor/tpm/src/socket.rs
Wei Liu ff5a6dcdb9 tpm: read swtpm control responses in full and handle short error replies
The swtpm control socket is a Unix SOCK_STREAM, so a single read() is
not guaranteed to return the full response in one shot. swtpm may
split a response into multiple writes, in which case the existing
single read() returns only the first chunk and subsequent parsing
fails with "Response for ... cmd is of incorrect length". This has
been observed on Azure Linux during emulator initialization.

In addition, when swtpm encounters an error processing a control
command (e.g. PTM_BAD_ORDINAL = 0x0A returned for commands issued
before CMD_INIT), the swtpm protocol returns only the 4-byte result
code instead of the full response. Blindly looping until msg_len_out
bytes arrive would deadlock in that case.

Add SocketDev::read_exact() that loops until the requested number of
bytes has been received (retrying on EINTR), and rework
run_control_cmd() to:

  * read_exact the 4-byte result code first;
  * on error, set the result code on the PTM message and return a
    clean error without waiting for a payload that will never arrive;
  * on success, read_exact the remaining (msg_len_out - 4) payload
    bytes.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-01 17:46:00 +00:00

181 lines
5.5 KiB
Rust

// Copyright © 2022, Microsoft Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use std::io::{ErrorKind, Read};
use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::path::Path;
use anyhow::anyhow;
use log::debug;
use thiserror::Error;
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
#[derive(Error, Debug)]
pub enum Error {
#[error("Cannot connect to tpm Socket")]
ConnectToSocket(#[source] anyhow::Error),
#[error("Failed to read from socket")]
ReadFromSocket(#[source] anyhow::Error),
#[error("Failed to write to socket")]
WriteToSocket(#[source] anyhow::Error),
}
type Result<T> = anyhow::Result<T, Error>;
#[derive(PartialEq)]
enum SocketDevState {
Disconnected,
Connecting,
Connected,
}
pub struct SocketDev {
state: SocketDevState,
stream: Option<UnixStream>,
// Fd sent to swtpm process for Data Channel
write_msgfd: RawFd,
// Data Channel used by Cloud-Hypervisor
data_fd: RawFd,
// Control Channel used by Cloud-Hypervisor
control_fd: RawFd,
}
impl Default for SocketDev {
fn default() -> Self {
Self::new()
}
}
impl SocketDev {
pub fn new() -> Self {
Self {
state: SocketDevState::Disconnected,
stream: None,
write_msgfd: -1,
control_fd: -1,
data_fd: -1,
}
}
pub fn init(&mut self, path: impl AsRef<Path>) -> Result<()> {
self.connect(path)?;
Ok(())
}
pub fn connect(&mut self, socket_path: impl AsRef<Path>) -> Result<()> {
let socket_path = socket_path.as_ref();
self.state = SocketDevState::Connecting;
let socket_path_s = socket_path.to_str().unwrap();
let s = UnixStream::connect(socket_path).map_err(|e| {
Error::ConnectToSocket(anyhow!("Failed to connect to tpm Socket. Error: {e:?}"))
})?;
self.control_fd = s.as_raw_fd();
self.stream = Some(s);
self.state = SocketDevState::Connected;
debug!("Connected to tpm socket path : {socket_path_s:?}");
Ok(())
}
pub fn set_datafd(&mut self, fd: RawFd) {
self.data_fd = fd;
}
pub fn set_msgfd(&mut self, fd: RawFd) {
self.write_msgfd = fd;
}
pub fn send_full(&self, buf: &[u8]) -> Result<usize> {
let write_fd = self.write_msgfd;
let size = self
.stream
.as_ref()
.unwrap()
.send_with_fd(buf, write_fd)
.map_err(|e| {
Error::WriteToSocket(anyhow!("Failed to write to Socket. Error: {e:?}"))
})?;
Ok(size)
}
pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
if self.stream.is_none() {
return Err(Error::WriteToSocket(anyhow!(
"Stream for tpm socket was not initialized"
)));
}
if matches!(self.state, SocketDevState::Connected) {
let ret = self.send_full(buf)?;
// swtpm will receive data Fd after a successful send
// Reset cached write_msgfd after a successful send
// Ideally, write_msgfd is reset after first Ctrl Command
if ret > 0 && self.write_msgfd != 0 {
self.write_msgfd = 0;
}
Ok(ret)
} else {
Err(Error::WriteToSocket(anyhow!(
"TPM Socket was not in Connected State"
)))
}
}
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.stream.is_none() {
return Err(Error::ReadFromSocket(anyhow!(
"Stream for tpm socket was not initialized"
)));
}
let mut socket = self.stream.as_ref().unwrap();
let size: usize = socket.read(buf).map_err(|e| {
Error::ReadFromSocket(anyhow!("Failed to read from socket. Error Code {e:?}"))
})?;
Ok(size)
}
/// Read exactly `expected` bytes from the socket into `buf[0..expected]`.
///
/// swtpm may split a control response into multiple writes on the
/// underlying SOCK_STREAM, so a single `read()` is not guaranteed to
/// return the full response in one shot. This helper loops until the
/// expected number of bytes have been collected (or an error is hit).
pub fn read_exact(&mut self, buf: &mut [u8], expected: usize) -> Result<usize> {
if self.stream.is_none() {
return Err(Error::ReadFromSocket(anyhow!(
"Stream for tpm socket was not initialized"
)));
}
if expected > buf.len() {
return Err(Error::ReadFromSocket(anyhow!(
"Buffer too small: have {} bytes, need {}",
buf.len(),
expected
)));
}
let mut socket = self.stream.as_ref().unwrap();
let mut total = 0usize;
while total < expected {
match socket.read(&mut buf[total..expected]) {
Ok(0) => {
return Err(Error::ReadFromSocket(anyhow!(
"Unexpected EOF while reading from socket: got {total} bytes, expected {expected}"
)));
}
Ok(n) => total += n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => {
return Err(Error::ReadFromSocket(anyhow!(
"Failed to read from socket. Error Code {e:?}"
)));
}
}
}
Ok(total)
}
}