From fde01819955dbaec2afd38d774e50d647d4efcf6 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 23 Jul 2026 11:23:36 +0200 Subject: [PATCH] ipc4: helper: bound TLV length safely when parsing DMA configs The IPC4 fuzzer hit an out-of-bounds read in ipc4_find_all_dma_configs_tlvs_only() while parsing a copier gateway config blob as a TLV sequence. AddressSanitizer reports a SEGV on a wild address ~16 MB below the config buffer. Root cause: the per-TLV bounds check and the iterator both do pointer arithmetic on the host-controlled tlvs->length without guarding against overflow. On 32-bit targets (native_sim and the Xtensa DSPs) a large length wraps the pointer: if ((uintptr_t)tlvs->value + tlvs->length > end_addr) /* wraps */ return IPC4_INVALID_REQUEST; ... tlvs = tlv_next(tlvs); /* tlvs + 8 + length, also wraps */ With length = 0xff000000 and a 32-byte buffer, tlvs->value + length wraps to a value below end_addr, so the check passes; tlv_next() wraps to the same address, which is still below end_addr, so the loop continues and dereferences that wild pointer. config_length is already bounded to the init payload (so the buffer itself is small and valid) - this is a separate overflow inside the TLV walk, on the length field embedded in the blob. Compare tlvs->length against the remaining space using subtraction, which cannot overflow because the (tightened) loop condition guarantees the TLV header - and therefore tlvs->value - is within the buffer. tlv_next() is thus bounded by end_addr as well. The loop condition now also requires the full 8-byte header to fit before it is dereferenced. Signed-off-by: Tomasz Leman --- src/ipc/ipc4/helper.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index a5ba47426168..19cd13672e61 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -1482,10 +1482,19 @@ int ipc4_find_all_dma_configs_tlvs_only(struct ipc_config_dai *dai, struct ipc_dma_config *dma_cfg; int count = 0; - for (tlvs = (struct sof_tlv *)data_buffer; tlvs && (uintptr_t)tlvs < end_addr; + for (tlvs = (struct sof_tlv *)data_buffer; + tlvs && (uintptr_t)tlvs + sizeof(*tlvs) <= end_addr; tlvs = tlv_next(tlvs)) { - if ((uintptr_t)tlvs->value + tlvs->length > end_addr) { - tr_err(&ipc_tr, "Unexpected TLV length %d: exceeds buffer size", tlvs->length); + /* + * tlvs->length is host-controlled: computing + * "tlvs->value + tlvs->length" can overflow the pointer and + * wrap below end_addr, bypassing this check and letting + * tlv_next() advance to a wild address. Compare against the + * remaining space instead. The loop condition guarantees the + * TLV header fits, so end_addr - tlvs->value does not underflow. + */ + if (tlvs->length > end_addr - (uintptr_t)tlvs->value) { + tr_err(&ipc_tr, "Unexpected TLV length %u: exceeds buffer size", tlvs->length); return IPC4_INVALID_REQUEST; }