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