From f39f3f80dc9f79eea1229e3f710157acb942ee82 Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Tue, 30 Jun 2026 14:09:21 +0200 Subject: [PATCH] fix(vmcall_raw): chunk send to honor VMCALL buffer MTU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A single TDG.VP.VMCALL can only carry up to the same MTU the receive side advertises (MAX_VMCALL_RAW_STREAM_MTU, 64KB). With policy_v2's peer-data blob now carrying the signed policy plus issuer chain (~180KB in practice), the rebinding pre-session body send fails with TdVmcallError::Other right after hello and the 8-byte data header succeed. Chunk on the send path so any single VMCALL payload stays at MAX_VMCALL_RAW_STREAM_MTU - 12 bytes (room for the GHCI 1.5 status+length header prepended by vmcall_raw_transport_enqueue). Loop inside VmcallRaw::send and keep returning buf.len(); this keeps the AsyncWrite contract intact for callers that don't loop on partial writes (rustls SecureChannel, SpdmDeviceIo). The wire framing is unchanged — the peer's receive loop already reassembles multiple packets in VmcallRaw::recv. Promote MAX_VMCALL_RAW_STREAM_MTU to pub(crate) and add explicit VMCALL_RAW_GHCI_HEADER_LEN / VMCALL_RAW_SEND_PAYLOAD_MTU consts so the overhead arithmetic is named at one site. Signed-off-by: Stanislaw Grams --- src/devices/vmcall_raw/src/stream.rs | 14 ++++++++++++-- src/devices/vmcall_raw/src/transport/vmcall.rs | 11 ++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/devices/vmcall_raw/src/stream.rs b/src/devices/vmcall_raw/src/stream.rs index da641ca10..30c99afa7 100644 --- a/src/devices/vmcall_raw/src/stream.rs +++ b/src/devices/vmcall_raw/src/stream.rs @@ -4,7 +4,7 @@ use crate::transport::vmcall::{ vmcall_raw_transport_can_recv, vmcall_raw_transport_dequeue, vmcall_raw_transport_enqueue, - vmcall_raw_transport_init, VMCALL_MIG_CONTEXT_FLAGS, + vmcall_raw_transport_init, VMCALL_MIG_CONTEXT_FLAGS, VMCALL_RAW_SEND_PAYLOAD_MTU, }; use core::sync::atomic::AtomicBool; @@ -61,7 +61,17 @@ impl VmcallRaw { } pub async fn send(&mut self, buf: &[u8], _flags: u32) -> Result { - let _ = vmcall_raw_transport_enqueue(self, buf).await?; + // A single Service.MigTD.Send VMCALL can only carry one MTU-sized + // payload (the same cap the receive side advertises). Chunk here so + // callers that don't loop on partial writes (TLS, SPDM transport) + // keep working when the pre-session policy + issuer chain blob + // exceeds one VMCALL. + let mut sent = 0; + while sent < buf.len() { + let end = core::cmp::min(buf.len(), sent + VMCALL_RAW_SEND_PAYLOAD_MTU); + let _ = vmcall_raw_transport_enqueue(self, &buf[sent..end]).await?; + sent = end; + } Ok(buf.len()) } diff --git a/src/devices/vmcall_raw/src/transport/vmcall.rs b/src/devices/vmcall_raw/src/transport/vmcall.rs index 7835e6dae..7081dcd79 100644 --- a/src/devices/vmcall_raw/src/transport/vmcall.rs +++ b/src/devices/vmcall_raw/src/transport/vmcall.rs @@ -18,7 +18,16 @@ use td_payload::arch::idt::InterruptStack; use td_payload::mm::shared::SharedMemory; use tdx_tdcall::tdx; -const MAX_VMCALL_RAW_STREAM_MTU: usize = 0x1000 * 16; +pub(crate) const MAX_VMCALL_RAW_STREAM_MTU: usize = 0x1000 * 16; +/// GHCI 1.5 buffer header overhead (8-byte status + 4-byte length) prepended +/// by `vmcall_raw_transport_enqueue` to each VMCALL payload. +pub(crate) const VMCALL_RAW_GHCI_HEADER_LEN: usize = 12; +/// Largest payload that can ride in a single `Service.MigTD.Send` VMCALL. +/// The VMM-side buffer for the migration data channel matches the MTU the +/// receive side advertises; sending more than this in one VMCALL fails with +/// TDX_VMCALL_STATUS != SUCCESS. +pub(crate) const VMCALL_RAW_SEND_PAYLOAD_MTU: usize = + MAX_VMCALL_RAW_STREAM_MTU - VMCALL_RAW_GHCI_HEADER_LEN; const VMCALL_VECTOR: u8 = 0x52; lazy_static! {