Skip to content

ipc4: helper: bound TLV length safely when parsing DMA configs#11028

Open
tmleman wants to merge 1 commit into
thesofproject:mainfrom
tmleman:topic/upstream/pr/ipc4/helper/tlv_length_check
Open

ipc4: helper: bound TLV length safely when parsing DMA configs#11028
tmleman wants to merge 1 commit into
thesofproject:mainfrom
tmleman:topic/upstream/pr/ipc4/helper/tlv_length_check

Conversation

@tmleman

@tmleman tmleman commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

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.

Copilot AI review requested due to automatic review settings July 23, 2026 09:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens IPC4 TLV parsing in ipc4_find_all_dma_configs_tlvs_only() to prevent pointer-overflow-induced out-of-bounds reads when walking host-controlled TLV sequences in copier gateway config blobs (notably on 32-bit targets).

Changes:

  • Tighten the TLV-walk loop condition to require the full TLV header to fit before dereferencing.
  • Replace overflow-prone value + length pointer arithmetic with a subtraction-based bounds check against remaining buffer space.
  • Add clarifying comments documenting the overflow/wraparound failure mode and the rationale for the new checks.

Comment thread src/ipc/ipc4/helper.c Outdated
@tmleman
tmleman force-pushed the topic/upstream/pr/ipc4/helper/tlv_length_check branch from 9351677 to fde0181 Compare July 23, 2026 10:50
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 <tomasz.m.leman@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants