From 7d237b5e20875783cbef2cf5e6beec54a60d3c8b Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Fri, 29 May 2026 15:41:40 -0700 Subject: [PATCH] devices: fix TPM CRB register byte-sized access Windows can access CRB registers with byte-sized writes and reads. The TPM device model used the byte offset as a u32 register index, which corrupted unaligned accesses and could expose invalid CRB state. Preserve the containing register on partial writes, read from the correct byte lane, allow exact-end buffer accesses. Assisted-by: Copilot:GPT-5.5 Signed-off-by: Wei Liu --- devices/src/tpm.rs | 58 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs index 2ecd62a17..a1ae13c0c 100644 --- a/devices/src/tpm.rs +++ b/devices/src/tpm.rs @@ -219,6 +219,19 @@ fn locality_from_addr(addr: u32) -> u8 { (addr >> 12) as u8 } +fn update_reg(regs: &mut [u32; TPM_CRB_R_MAX], offset: u32, data: &[u8]) -> u32 { + let reg_offset = (offset & !0x3) as usize; + let byte_offset = (offset & 0x3) as usize; + let mut input = [0; 4]; + + input[..data.len()].copy_from_slice(data); + let value = u32::from_le_bytes(input); + let mask = (u32::MAX >> (32 - data.len() * 8)) << (byte_offset * 8); + + regs[reg_offset] = (regs[reg_offset] & !mask) | ((value << (byte_offset * 8)) & mask); + regs[reg_offset] +} + pub struct Tpm { emulator: Emulator, regs: [u32; TPM_CRB_R_MAX], @@ -257,7 +270,7 @@ impl Tpm { } fn request_completed(&mut self, success: bool) { - self.regs[CRB_CTRL_START as usize] = !CRB_START_INVOKE; + self.regs[CRB_CTRL_START as usize] &= !CRB_START_INVOKE; if !success { set_reg_field( &mut self.regs, @@ -353,7 +366,7 @@ impl BusDevice for Tpm { let read_len: usize = data.len(); if offset >= CRB_DATA_BUFFER - && (offset + read_len as u32) < (CRB_DATA_BUFFER + self.data_buff.len() as u32) + && (offset + read_len as u32) <= (CRB_DATA_BUFFER + self.data_buff.len() as u32) { // Read from Data Buffer let start: usize = (offset as usize) - (CRB_DATA_BUFFER as usize); @@ -361,17 +374,21 @@ impl BusDevice for Tpm { data[..].clone_from_slice(&self.data_buff[start..end]); } else { offset &= 0xff; - let mut val = self.regs[offset as usize]; + let reg_offset = offset & !0x3; + let mut val = self.regs[reg_offset as usize]; // Per the TCG PC Client Platform TPM Profile (PTP) spec, bit 0 // of TPM_LOC_STATE_x is `tpmEstablished`. Reflect the live value // from the backend rather than the (zero-initialised) shadow. - if offset == CRB_LOC_STATE && self.emulator.get_established_bit() { + if reg_offset == CRB_LOC_STATE && self.emulator.get_established_bit() { val |= 0x1; } if data.len() <= 4 { - data.clone_from_slice(val.to_ne_bytes()[0..read_len].as_ref()); + let byte_offset = (offset & 0x3) as usize; + data.clone_from_slice( + (val >> (byte_offset * 8)).to_ne_bytes()[0..read_len].as_ref(), + ); } else { error!( "Invalid tpm read: offset {:#X}, data length {:?}", @@ -403,7 +420,7 @@ impl BusDevice for Tpm { let write_len = data.len(); if offset >= CRB_DATA_BUFFER - && (offset + write_len as u32) < (CRB_DATA_BUFFER + self.data_buff.len() as u32) + && (offset + write_len as u32) <= (CRB_DATA_BUFFER + self.data_buff.len() as u32) { let start: usize = (offset as usize) - (CRB_DATA_BUFFER as usize); if start == 0 { @@ -426,25 +443,28 @@ impl BusDevice for Tpm { return None; } - let mut input: [u8; 4] = [0; 4]; - input.copy_from_slice(&data[0..4]); - let v = u32::from_le_bytes(input); + let write_offset = offset; + let reg_data_len = cmp::min(write_len, 4 - (offset as usize & 0x3)); + let mut input = [0; 4]; + input[..reg_data_len].copy_from_slice(&data[..reg_data_len]); + let v = u32::from_le_bytes(input) << ((offset & 0x3) * 8); + offset &= !0x3; match offset { CRB_CTRL_CMD_SIZE_REG => { - self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = v; + update_reg(&mut self.regs, write_offset, &data[..reg_data_len]); } CRB_CTRL_CMD_LADDR => { - self.regs[CRB_CTRL_CMD_LADDR as usize] = v; + update_reg(&mut self.regs, write_offset, &data[..reg_data_len]); } CRB_CTRL_CMD_HADDR => { - self.regs[CRB_CTRL_CMD_HADDR as usize] = v; + update_reg(&mut self.regs, write_offset, &data[..reg_data_len]); } CRB_CTRL_RSP_SIZE => { - self.regs[CRB_CTRL_RSP_SIZE as usize] = v; + update_reg(&mut self.regs, write_offset, &data[..reg_data_len]); } CRB_CTRL_RSP_ADDR => { - self.regs[CRB_CTRL_RSP_ADDR as usize] = v; + update_reg(&mut self.regs, write_offset, &data[..reg_data_len]); } CRB_CTRL_REQ => match v { CRB_CTRL_REQ_CMD_READY => { @@ -556,4 +576,14 @@ mod unit_tests { concat!("Test: ", stringify!(set_get_reg_field)) ); } + + #[test] + fn test_update_reg_partial_write() { + let mut regs: [u32; TPM_CRB_R_MAX] = [0; TPM_CRB_R_MAX]; + + update_reg(&mut regs, CRB_CTRL_CMD_SIZE_REG, &[0x78, 0x56, 0x34, 0x12]); + update_reg(&mut regs, CRB_CTRL_CMD_SIZE_REG + 1, &[0xaa]); + + assert_eq!(regs[CRB_CTRL_CMD_SIZE_REG as usize], 0x1234_aa78); + } }