refactor(rust/trezor-thp): separate send/receive buffers for handshake
What changed, and why it matters
This commit is a code cleanup in the Trezor firmware's secure USB/Bluetooth transport layer (Trezor THP). It replaces a single shared buffer used during the cryptographic handshake with two separate buffers: one for messages being sent and one for messages being received. The change makes the code easier to reason about and removes some awkward buffer resizing and zeroing patterns, but it does not appear to fix any active security bug. There is no mention of a vulnerability, CVE, or security issue in the commit itself.
No immediate security action required. Treat as a normal code-quality refactor. If auditing, verify that the new send/receive buffers are correctly zeroed after use and that no stale handshake data remains in either buffer; the diff shows prepare_zeroed is still used but review whether all sensitive lifetimes are covered.
Security signals we found
Refactor only: no new security boundary or cryptographic primitive introduced
Buffer handling change: single shared buffer split into dedicated send/receive buffers
No explicit security relevance, CVE, or vulnerability disclosure in commit message or diff
No new input validation, length checks, or mitigations added beyond existing error paths
Evidence from the diff
The refactor splits the previous HANDSHAKE_BUFFER_LEN into direction-specific constants HANDSHAKE_BUFFER_HTD_LEN (host-to-device) and HANDSHAKE_BUFFER_DTH_LEN (device-to-host), then updates ChannelOpen in both device.rs and host.rs to use distinct send_buffer and receive_buffer heapless::Vec instances instead of one internal_buffer. The Noise handshake helpers in noise.rs are updated to return lengths rather than slices, and callers now pass explicit receive/send buffers. The patch also removes some now-redundant prepare_zeroed calls and the old in-place buffer reuse logic. The diff shows no new bounds checks, cryptographic changes, or vulnerability fixes—only clearer ownership of buffers.
Changed components
rust/trezor-thp/src/channel/device.rsrust/trezor-thp/src/channel/host.rsrust/trezor-thp/src/channel/mod.rsrust/trezor-thp/src/channel/noise.rsInspect captured patch +84 / −106
diff --git a/rust/trezor-thp/src/channel/device.rs b/rust/trezor-thp/src/channel/device.rs
index 1e297ad9..1a610567 100644
--- a/rust/trezor-thp/src/channel/device.rs
+++ b/rust/trezor-thp/src/channel/device.rs
@@ -4,8 +4,8 @@ use crate::{
Backend, ChannelIO, Device, Error,
alternating_bit::SyncBits,
channel::{
- ChannelState, HANDSHAKE_BUFFER_LEN, Nonce, PRIVKEY_LEN, PacketInResult, PairingState,
- noise::NoiseHandshake,
+ ChannelState, HANDSHAKE_BUFFER_DTH_LEN, HANDSHAKE_BUFFER_HTD_LEN, Nonce, PRIVKEY_LEN,
+ PacketInResult, PairingState, noise::NoiseHandshake,
},
credential::CredentialVerifier,
error::TransportError,
@@ -291,34 +291,35 @@ pub struct ChannelOpen<C: CredentialVerifier, B: Backend> {
channel: Channel<B>,
state: HandshakeState,
noise: NoiseHandshake<Device, B>,
- internal_buffer: heapless::Vec<u8, HANDSHAKE_BUFFER_LEN>,
+ send_buffer: heapless::Vec<u8, HANDSHAKE_BUFFER_DTH_LEN>,
+ receive_buffer: heapless::Vec<u8, HANDSHAKE_BUFFER_HTD_LEN>,
cred_verif: C,
}
impl<C: CredentialVerifier, B: Backend> ChannelOpen<C, B> {
fn new(channel_id: u16, nonce: Nonce, cred_verif: C) -> Result<Self, Error> {
- let mut internal_buffer = heapless::Vec::new();
- internal_buffer
+ let mut send_buffer = heapless::Vec::new();
+ send_buffer
.extend_from_slice(nonce.as_slice())
.map_err(|_| Error::insufficient_buffer())?;
- internal_buffer
+ send_buffer
.extend_from_slice(&channel_id.to_be_bytes())
.map_err(|_| Error::insufficient_buffer())?;
- internal_buffer
+ send_buffer
.extend_from_slice(cred_verif.device_properties())
.map_err(|_| Error::insufficient_buffer())?;
+ let mut receive_buffer = heapless::Vec::new();
+ prepare_zeroed(&mut receive_buffer);
// Sending `channel_allocation_response` on broadcast channel.
let mut channel = Channel::new(channel_id);
- channel.raw_in(
- Header::new_channel_response(&internal_buffer)?,
- &internal_buffer,
- )?;
+ channel.raw_in(Header::new_channel_response(&send_buffer)?, &send_buffer)?;
Ok(Self {
channel,
state: HandshakeState::SendingChannelResponse,
noise: NoiseHandshake::prepare_responder(cred_verif.device_properties()),
- internal_buffer,
+ send_buffer,
+ receive_buffer,
cred_verif,
})
}
@@ -329,14 +330,14 @@ impl<C: CredentialVerifier, B: Backend> ChannelOpen<C, B> {
};
let sync_bits = reassembler.sync_bits();
- let (header, len) = self.channel.raw_out(&self.internal_buffer)?;
- self.internal_buffer.truncate(len);
+ let (header, len) = self.channel.raw_out(&self.receive_buffer)?;
+ self.receive_buffer.truncate(len);
match (self.state, header.handshake_phase()) {
(HandshakeState::SendingChannelResponse, Some(HandshakeMessage::InitiationRequest)) => {
// enable ACK piggybacking if requested
self.enable_ack_piggybacking_if_requested(sync_bits);
- let try_to_unlock = self.noise.read_initiation_request(&self.internal_buffer)?;
+ let try_to_unlock = self.noise.read_initiation_request(&self.receive_buffer)?;
self.state = HandshakeState::StaticKeyRequired { try_to_unlock };
}
(
@@ -351,6 +352,7 @@ impl<C: CredentialVerifier, B: Backend> ChannelOpen<C, B> {
return Err(Error::unexpected_input());
}
}
+ prepare_zeroed(&mut self.receive_buffer);
Ok(())
}
@@ -364,38 +366,35 @@ impl<C: CredentialVerifier, B: Backend> ChannelOpen<C, B> {
&mut self,
static_privkey: &[u8; PRIVKEY_LEN],
) -> Result<(), Error> {
- prepare_zeroed(&mut self.internal_buffer);
- let msg = self
+ prepare_zeroed(&mut self.send_buffer);
+ let len = self
.noise
- .write_initiation_response(static_privkey, &mut self.internal_buffer)?;
+ .write_initiation_response(static_privkey, &mut self.send_buffer)?;
+ self.send_buffer.truncate(len);
let header = Header::new_handshake(
self.channel.channel_id,
HandshakeMessage::InitiationResponse,
- msg,
+ &self.send_buffer,
)?;
- self.channel.raw_in(header, msg)?;
- let len = msg.len();
- self.internal_buffer.truncate(len);
+ self.channel.raw_in(header, &self.send_buffer)?;
Ok(())
}
fn send_completion_response(&mut self) -> Result<PairingState, Error> {
- let payload = self.internal_buffer.clone();
- prepare_zeroed(&mut self.internal_buffer);
- let (nc, ps, msg) = self.noise.write_completion_response(
- &payload,
+ prepare_zeroed(&mut self.send_buffer);
+ let (nc, ps, len) = self.noise.write_completion_response(
+ &self.receive_buffer,
&self.cred_verif,
- &mut self.internal_buffer,
+ &mut self.send_buffer,
)?;
+ self.send_buffer.truncate(len);
self.channel.noise = Some(nc);
let header = Header::new_handshake(
self.channel.channel_id,
HandshakeMessage::CompletionResponse,
- msg,
+ &self.send_buffer,
)?;
- self.channel.raw_in(header, msg)?;
- let len = msg.len();
- self.internal_buffer.truncate(len);
+ self.channel.raw_in(header, &self.send_buffer)?;
Ok(ps)
}
@@ -488,7 +487,7 @@ where
fn packet_in(&mut self, packet_buffer: &[u8], _receive_buffer: &mut [u8]) -> PacketInResult {
let res = self
.channel
- .packet_in(packet_buffer, &mut self.internal_buffer);
+ .packet_in(packet_buffer, &mut self.receive_buffer);
if let PacketInResult::Accepted {
buffer_size: Some(s),
..
@@ -516,22 +515,12 @@ where
if let HandshakeState::StaticKeyRequired { try_to_unlock } = self.state {
return PacketInResult::HandshakeKeyRequired { try_to_unlock };
}
- } else if res.got_ack() {
- prepare_zeroed(&mut self.internal_buffer);
}
res
}
fn packet_out(&mut self, packet_buffer: &mut [u8], _send_buffer: &[u8]) -> Result<(), Error> {
- self.channel
- .packet_out(packet_buffer, &self.internal_buffer)?;
- // Do not wait for ack - `channel_allocation_response` is sent over broadcast channel.
- if matches!(self.state, HandshakeState::SendingChannelResponse)
- && matches!(self.channel.state, ChannelState::Idle)
- {
- prepare_zeroed(&mut self.internal_buffer);
- }
- Ok(())
+ self.channel.packet_out(packet_buffer, &self.send_buffer)
}
fn packet_out_ready(&self) -> bool {
diff --git a/rust/trezor-thp/src/channel/host.rs b/rust/trezor-thp/src/channel/host.rs
index 02c50ed8..14deb001 100644
--- a/rust/trezor-thp/src/channel/host.rs
+++ b/rust/trezor-thp/src/channel/host.rs
@@ -4,8 +4,8 @@ use crate::{
Backend, ChannelIO, Error, Host,
alternating_bit::SyncBits,
channel::{
- ChannelState, HANDSHAKE_BUFFER_LEN, MAX_ALLOC_RESPONSE_LEN, MAX_DEVICE_PROPERTIES_LEN,
- Nonce, PacketInResult, PairingState, noise::NoiseHandshake,
+ ChannelState, HANDSHAKE_BUFFER_DTH_LEN, HANDSHAKE_BUFFER_HTD_LEN, MAX_ALLOC_RESPONSE_LEN,
+ MAX_DEVICE_PROPERTIES_LEN, Nonce, PacketInResult, PairingState, noise::NoiseHandshake,
},
credential::CredentialStore,
fragment::{Fragmenter, Reassembler},
@@ -339,7 +339,8 @@ pub struct ChannelOpen<C: CredentialStore, B: Backend> {
channel: Channel<B>,
state: HandshakeState,
noise: NoiseHandshake<Host, B>,
- internal_buffer: heapless::Vec<u8, HANDSHAKE_BUFFER_LEN>,
+ send_buffer: heapless::Vec<u8, HANDSHAKE_BUFFER_HTD_LEN>,
+ receive_buffer: heapless::Vec<u8, HANDSHAKE_BUFFER_DTH_LEN>,
device_properties: heapless::Vec<u8, MAX_DEVICE_PROPERTIES_LEN>,
cred_store: C,
}
@@ -354,20 +355,22 @@ impl<C: CredentialStore, B: Backend> ChannelOpen<C, B> {
let device_properties = heapless::Vec::from_slice(device_properties)
.map_err(|_| Error::insufficient_buffer())?;
- let mut internal_buffer = heapless::Vec::new();
- prepare_zeroed(&mut internal_buffer);
- let (hss, msg) = NoiseHandshake::write_initiation_request(
+ let mut send_buffer = heapless::Vec::new();
+ prepare_zeroed(&mut send_buffer);
+ let (hss, len) = NoiseHandshake::write_initiation_request(
&device_properties,
try_to_unlock,
- &mut internal_buffer,
+ &mut send_buffer,
)?;
- let len = msg.len();
- internal_buffer.truncate(len);
+ send_buffer.truncate(len);
+ let mut receive_buffer = heapless::Vec::new();
+ prepare_zeroed(&mut receive_buffer);
let res = Self {
channel: Channel::new(channel_id),
state: HandshakeState::Initial,
noise: hss,
- internal_buffer,
+ send_buffer,
+ receive_buffer,
device_properties,
cred_store,
};
@@ -375,8 +378,8 @@ impl<C: CredentialStore, B: Backend> ChannelOpen<C, B> {
}
fn incoming_internal(&mut self) -> Result<(), Error> {
- let (header, len) = self.channel.raw_out(&self.internal_buffer)?;
- self.internal_buffer.truncate(len);
+ let (header, len) = self.channel.raw_out(&self.receive_buffer)?;
+ self.receive_buffer.truncate(len);
match (self.state, header.handshake_phase()) {
(
@@ -398,37 +401,32 @@ impl<C: CredentialStore, B: Backend> ChannelOpen<C, B> {
return Err(Error::unexpected_input());
}
}
+ prepare_zeroed(&mut self.receive_buffer);
Ok(())
}
fn continue_handshake(&mut self) -> Result<(), Error> {
- let payload_len = self.internal_buffer.len();
- // Buffer used both for input and output - pad with zeros.
- self.internal_buffer
- .resize(self.internal_buffer.capacity(), 0u8)
- .unwrap();
- let (nc, msg) = self.noise.write_completion_request(
+ prepare_zeroed(&mut self.send_buffer);
+ let (nc, len) = self.noise.write_completion_request(
&mut self.cred_store,
- &mut self.internal_buffer,
- payload_len,
+ &self.receive_buffer,
+ &mut self.send_buffer,
)?;
+ self.send_buffer.truncate(len);
self.channel.noise = Some(nc);
let header = Header::new_handshake(
self.channel.channel_id,
HandshakeMessage::CompletionRequest,
- msg,
+ &self.send_buffer,
)?;
- self.channel.raw_in(header, msg)?;
- let len = msg.len();
- self.internal_buffer.truncate(len);
+ self.channel.raw_in(header, &self.send_buffer)?;
Ok(())
}
fn finish_handshake(&mut self) -> Result<PairingState, Error> {
- let payload = &mut self.internal_buffer;
- let len = self.channel.noise()?.decrypt(payload.as_mut_slice())?;
- payload.truncate(len); // assumes tag at the end
- PairingState::try_from(payload.as_slice())
+ let len = self.channel.noise()?.decrypt(&mut self.receive_buffer)?;
+ let payload = &self.receive_buffer[..len]; // assumes tag at the end
+ PairingState::try_from(payload)
}
/// Returns device's `ThpDeviceProperties` protobuf structure.
@@ -509,7 +507,7 @@ where
}
let res = self
.channel
- .packet_in(packet_buffer, &mut self.internal_buffer);
+ .packet_in(packet_buffer, &mut self.receive_buffer);
if let PacketInResult::Accepted {
buffer_size: Some(s),
..
@@ -534,25 +532,21 @@ where
return PacketInResult::fail(e);
}
}
- } else if res.got_ack() {
- prepare_zeroed(&mut self.internal_buffer);
}
res
}
fn packet_out(&mut self, packet_buffer: &mut [u8], _send_buffer: &[u8]) -> Result<(), Error> {
- if matches!(self.state, HandshakeState::Initial) {
+ if let HandshakeState::Initial = self.state {
let header = Header::<Host>::new_handshake(
self.channel_id(),
HandshakeMessage::InitiationRequest,
- &self.internal_buffer,
+ &self.send_buffer,
)?;
- self.channel
- .raw_in_ext(header, &self.internal_buffer, true)?;
+ self.channel.raw_in_ext(header, &self.send_buffer, true)?;
self.state = HandshakeState::SendingInitiationRequest;
}
- self.channel
- .packet_out(packet_buffer, &self.internal_buffer)
+ self.channel.packet_out(packet_buffer, &self.send_buffer)
}
fn packet_out_ready(&self) -> bool {
diff --git a/rust/trezor-thp/src/channel/mod.rs b/rust/trezor-thp/src/channel/mod.rs
index 477c56ac..36c8a0aa 100644
--- a/rust/trezor-thp/src/channel/mod.rs
+++ b/rust/trezor-thp/src/channel/mod.rs
@@ -38,8 +38,6 @@ const HANDSHAKE_BUFFER_DTH_LEN: usize = max(
2 * PUBKEY_LEN + 2 * TAG_LEN + CHECKSUM_LEN, // HandshakeInitiationResponse
);
-const HANDSHAKE_BUFFER_LEN: usize = max(HANDSHAKE_BUFFER_HTD_LEN, HANDSHAKE_BUFFER_DTH_LEN);
-
const APP_HEADER_LEN: usize = 3; // session id (1) + message type (2)
/// Used during channel allocation on broadcast channel.
diff --git a/rust/trezor-thp/src/channel/noise.rs b/rust/trezor-thp/src/channel/noise.rs
index 53aebd67..1944c020 100644
--- a/rust/trezor-thp/src/channel/noise.rs
+++ b/rust/trezor-thp/src/channel/noise.rs
@@ -83,11 +83,11 @@ impl<B: Backend> NoiseCiphers<B> {
}
impl<B: Backend> NoiseHandshake<Host, B> {
- pub fn write_initiation_request<'a>(
+ pub fn write_initiation_request(
device_properties: &[u8],
try_to_unlock: bool,
- dest: &'a mut [u8],
- ) -> Result<(Self, &'a [u8]), Error> {
+ dest: &mut [u8],
+ ) -> Result<(Self, usize), Error> {
let payload = &[u8::from(try_to_unlock)];
let mut hss = HandshakeState::new(
noise_xx(),
@@ -105,23 +105,20 @@ impl<B: Backend> NoiseHandshake<Host, B> {
hss,
_phantom: PhantomData,
};
- Ok((new, dest))
+ Ok((new, len))
}
- pub fn write_completion_request<'a>(
+ pub fn write_completion_request(
&mut self,
cred_store: &mut impl CredentialStore,
- buffer: &'a mut [u8],
- incoming_len: usize,
- ) -> Result<(NoiseCiphers<B>, &'a [u8]), Error> {
- if incoming_len != self.hss.get_next_message_overhead() {
+ receive_buffer: &[u8],
+ send_buffer: &mut [u8],
+ ) -> Result<(NoiseCiphers<B>, usize), Error> {
+ if receive_buffer.len() != self.hss.get_next_message_overhead() {
log::error!("Unexpected message length during handshake.");
return Err(Error::malformed_data());
}
- let incoming = buffer
- .get(..incoming_len)
- .ok_or_else(Error::insufficient_buffer)?;
- self.hss.read_message(incoming, &mut [])?;
+ self.hss.read_message(receive_buffer, &mut [])?;
// Look up static key based on remote keys, or generate a new one.
let remote_static_pubkey = self.hss.get_rs().ok_or_else(Error::crypto_error)?;
@@ -133,9 +130,9 @@ impl<B: Backend> NoiseHandshake<Host, B> {
)?;
self.hss.set_s(local_static);
- buffer.fill(0);
+ send_buffer.fill(0);
let len = self.hss.get_next_message_overhead() + pairing_credential.len();
- let dest = buffer
+ let dest = send_buffer
.get_mut(..len)
.ok_or_else(Error::insufficient_buffer)?;
self.hss
@@ -152,7 +149,7 @@ impl<B: Backend> NoiseHandshake<Host, B> {
handshake_hash,
remote_static_pubkey: remote_static_pubkey.as_slice().try_into().unwrap(),
};
- Ok((nc, dest))
+ Ok((nc, len))
}
fn credential_from_store(
@@ -206,11 +203,11 @@ impl<B: Backend> NoiseHandshake<Device, B> {
Ok(try_to_unlock)
}
- pub fn write_initiation_response<'a>(
+ pub fn write_initiation_response(
&mut self,
static_privkey: &[u8; PRIVKEY_LEN],
- dest: &'a mut [u8],
- ) -> Result<&'a [u8], Error> {
+ dest: &mut [u8],
+ ) -> Result<usize, Error> {
let mk = Self::mask_key(static_privkey);
self.hss.set_s(mk.static_privkey);
self.hss.set_s_mask(mk.mask);
@@ -218,15 +215,15 @@ impl<B: Backend> NoiseHandshake<Device, B> {
let len = self.hss.get_next_message_overhead();
let dest = dest.get_mut(..len).ok_or_else(Error::insufficient_buffer)?;
self.hss.write_message(/*payload*/ &[], dest)?; // no outgoing payload
- Ok(dest)
+ Ok(len)
}
- pub fn write_completion_response<'a>(
+ pub fn write_completion_response(
&mut self,
incoming: &[u8],
cred_verifier: &impl CredentialVerifier,
- dest: &'a mut [u8],
- ) -> Result<(NoiseCiphers<B>, PairingState, &'a [u8]), Error> {
+ dest: &mut [u8],
+ ) -> Result<(NoiseCiphers<B>, PairingState, usize), Error> {
let overhead_len = self.hss.get_next_message_overhead();
if incoming.len() < overhead_len {
log::error!("Unexpected message length during handshake.");
@@ -265,7 +262,7 @@ impl<B: Backend> NoiseHandshake<Device, B> {
.ok_or_else(Error::insufficient_buffer)?;
dest[0..plaintext_len].copy_from_slice(payload);
nc.encrypt(dest, plaintext_len)?;
- Ok((nc, pairing_state, dest))
+ Ok((nc, pairing_state, dest.len()))
}
fn mask_key(static_privkey: &[u8; PRIVKEY_LEN]) -> MaskKeyResult<B>
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.