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:
Wei Liu
2026-05-29 20:28:02 -07:00
parent 362a9ecc4f
commit ff5a6dcdb9
2 changed files with 85 additions and 7 deletions

View File

@@ -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)
}
}