mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
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>
This commit is contained in:
@@ -254,12 +254,50 @@ impl Emulator {
|
||||
|
||||
let mut output = [0u8; 16];
|
||||
|
||||
// Every Control Cmd gets at least a result code in response. Read it
|
||||
let read_size = self.control_socket.read(&mut output).map_err(|e| {
|
||||
Error::RunControlCmd(anyhow!(
|
||||
"Failed while reading response for Control Cmd: {cmd:02X?}. Error: {e:?}"
|
||||
))
|
||||
})?;
|
||||
// Every Control Cmd gets at least a result code (4 bytes) in response.
|
||||
// The full response length is given by `msg_len_out`. On a SOCK_STREAM
|
||||
// socket the response may arrive in more than one chunk, so we cannot
|
||||
// rely on a single `read()` returning the full payload.
|
||||
//
|
||||
// Additionally, when swtpm encounters an error processing a control
|
||||
// command, the swtpm control protocol returns only the 4-byte result
|
||||
// code (e.g. before CMD_INIT some commands return PTM_BAD_ORDINAL =
|
||||
// 0x0A). In that case we must not block waiting for more bytes, so we
|
||||
// first read the 4-byte result code, and only read the remainder of
|
||||
// `msg_len_out` if the command succeeded.
|
||||
let result_len = mem::size_of::<u32>();
|
||||
self.control_socket
|
||||
.read_exact(&mut output, result_len)
|
||||
.map_err(|e| {
|
||||
Error::RunControlCmd(anyhow!(
|
||||
"Failed while reading result code for Control Cmd: {cmd:02X?}. Error: {e:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let result_code = u32::from_be_bytes(output[0..result_len].try_into().unwrap());
|
||||
if result_code != TPM_SUCCESS {
|
||||
// swtpm returns only the 4-byte result code on error. Propagate
|
||||
// the failure without attempting to read or parse a payload that
|
||||
// will never arrive.
|
||||
msg.set_member_type(MemberType::Response);
|
||||
msg.set_result_code(result_code);
|
||||
return Err(Error::RunControlCmd(anyhow!(
|
||||
"Control Cmd {cmd:02X?} returned error code : {result_code:#X}"
|
||||
)));
|
||||
}
|
||||
|
||||
let read_size = if msg_len_out > result_len {
|
||||
self.control_socket
|
||||
.read_exact(&mut output[result_len..], msg_len_out - result_len)
|
||||
.map_err(|e| {
|
||||
Error::RunControlCmd(anyhow!(
|
||||
"Failed while reading response for Control Cmd: {cmd:02X?}. Error: {e:?}"
|
||||
))
|
||||
})?;
|
||||
msg_len_out
|
||||
} else {
|
||||
result_len
|
||||
};
|
||||
|
||||
if msg_len_out != 0 {
|
||||
msg.update_ptm_with_response(&output[0..read_size])
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::io::Read;
|
||||
use std::io::{ErrorKind, Read};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::Path;
|
||||
@@ -137,4 +137,44 @@ impl SocketDev {
|
||||
})?;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user