From ff5a6dcdb92b950ce94bafa003ce401d10a55e48 Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 29 May 2026 20:28:02 -0700 Subject: [PATCH] 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 --- tpm/src/emulator.rs | 50 +++++++++++++++++++++++++++++++++++++++------ tpm/src/socket.rs | 42 ++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/tpm/src/emulator.rs b/tpm/src/emulator.rs index 32721fdc4..42b210a58 100644 --- a/tpm/src/emulator.rs +++ b/tpm/src/emulator.rs @@ -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::(); + 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]) diff --git a/tpm/src/socket.rs b/tpm/src/socket.rs index b77768153..9acd9930f 100644 --- a/tpm/src/socket.rs +++ b/tpm/src/socket.rs @@ -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 { + 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) + } }