Skip to content

zephyr: sof_dma: validate syscalls against kernel-only DMA state - #11391

Merged
lgirdwood merged 1 commit into
thesofproject:mainfrom
kv2019i:202611-sofdma-harden-dma-checks
Nov 3, 2026
Merged

lgirdwood merged 1 commit into
thesofproject:mainfrom
kv2019i:202611-sofdma-harden-dma-checks

Conversation

@kv2019i

@kv2019i kv2019i commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

The dma[] array is APP_SYSUSER_DATA, so user threads can write every field of it. The DMA syscalls trusted it in several ways:

  • the implementations re-read dma->z_dev after the validator checked it, so a forged device and its driver API could be swapped in
  • sof_dma_put() freed dma->chan through dma->heap
  • channel numbers were passed to drivers that only assert on them, and asserts are compiled out in production builds
  • sof_dma_config() picked the address to validate from the user's channel_direction, while HDA DMA programs the buffer address that matches the device's own direction

Add a kernel-only shadow, sof_dma_kinfo, filled in dmac_init() before user threads run, and track the reference count and channel array there. The validators now look up the shadow, bound the channel by the driver's kernel-only dma_context, require the transfer direction to match the device, and call the Zephyr DMA API on the validated device. Peripheral directions are refused for non-HDA DMA until FIFO addresses can be checked against a kernel-side list.

Copilot AI balanced review requested due to automatic review settings November 2, 2026 18:02

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.

🟡 Changes recommended

Unresolved critical DMA flaws let user-writable pointers, heap state, and locks control kernel operations, enabling forged-pointer writes, invalid frees, and SMP races.

4 open issues
What changed in this PR

This PR hardens SOF’s Zephyr DMA syscall boundary by introducing kernel-only DMA state and reducing reliance on user-writable fields.

Changes:

  • Adds a DMA shadow initialized at boot for device metadata, reference counts, and channel allocation ownership.
  • Validates device access, channel bounds, and transfer directions before calling Zephyr DMA APIs.
  • Rejects non-HDA peripheral transfers until peripheral addresses can be validated.
File Description
zephyr/​syscall/​sof_dma.c Uses shadow-backed validation and calls DMA APIs on validated devices.
zephyr/​lib/​dma.c Adds shadow storage, pointer lookup, and boot-time initialization.
zephyr/​include/​sof/​lib/​sof_dma.h Defines the kernel-only shadow and its lookup interface.
src/​lib/​dma.c Moves reference counting and channel allocation ownership into the shadow.

Review effort: Balanced

Comment thread src/lib/dma.c Outdated
dma->heap = heap;
memset(dma->chan, 0, sizeof(struct dma_chan_data) * dma->plat_data.channels);
#ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA
sof_dma_kinfo_get(dma)->heap = heap;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in V2

Comment thread src/lib/dma.c Outdated
memset(dma->chan, 0, sizeof(struct dma_chan_data) * dma->plat_data.channels);
#ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA
sof_dma_kinfo_get(dma)->heap = heap;
sof_dma_kinfo_get(dma)->chan = dma->chan;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in V2

Comment thread zephyr/include/sof/lib/sof_dma.h Outdated
uint32_t dir; /**< SOF_DMA_DIR_* supported directions */
uint32_t caps; /**< SOF_DMA_CAP_* capabilities */
uint32_t channels; /**< size of the channel array */
int sref; /**< reference count, under dma->lock */

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in V2

Comment thread src/lib/dma.c Outdated
}

dmin->sref++;
dmin->sref = ++*dma_sref(dmin);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in V2

@intel-sofci

intel-sofci commented Nov 2, 2026 •

Copy link
Copy Markdown

PR 11391: test results

Run date: 2026-11-03 15:34 UTC

Tested commit: c038f33d5e84adadea0ee683af3dfc8a9ecd1112

mtl pass rate lnl pass rate ptl pass rate wcl pass rate nvl pass rate

Comment thread zephyr/include/sof/lib/sof_dma.h Outdated
struct dma_chan_data;

/**
* Kernel-only shadow of one struct sof_dma entry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So this is the source of all truth, the userspace object is really the shadow ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@lgirdwood Ack, this is misleading, fixed in V2.

@kv2019i
kv2019i force-pushed the 202611-sofdma-harden-dma-checks branch from fc2a1f7 to 33fd6a9 Compare November 3, 2026 14:41
@kv2019i

kv2019i commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

V2 pushed:

  • addressing code review comments
  • code conflict not yet resolved, will need another update

The dma[] array is APP_SYSUSER_DATA, so user threads can write every
field of it. The DMA syscalls trusted it in several ways:

- the implementations re-read dma->z_dev after the validator checked
  it, so a forged device and its driver API could be swapped in
- sof_dma_put() freed dma->chan through dma->heap, and the reference
  count was protected by dma->lock, all in user-writable memory
- dma_init() allocated the channel array from a heap pointer read from
  user-writable memory, and read the array pointer back from dma->chan
- channel numbers were passed to drivers that only assert on them, and
  asserts are compiled out in production builds
- sof_dma_config() picked the address to validate from the user's
  channel_direction, while HDA DMA programs the buffer address that
  matches the device's own direction

Add kernel-only state for each DMA controller, struct sof_dma_kinfo,
filled in dmac_init() before user threads run. It is the authoritative
state for the kernel, while dma[] stays as the untrusted user-visible
view. It holds the reference count, the lock protecting it, and the
channel array with its heap. With CONFIG_SOF_USERSPACE_LL, dma_init()
checks the heap with zephyr_ll_user_heap_verify(), and it publishes the
channel array only once it is initialized.

The validators now look up the kernel state, bound the channel by the
driver's kernel-only dma_context, require the transfer direction to
match the device, and call the Zephyr DMA API on the validated device.
Peripheral directions are refused for non-HDA DMA until FIFO addresses
can be checked against a kernel-side list.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
@kv2019i
kv2019i force-pushed the 202611-sofdma-harden-dma-checks branch from 33fd6a9 to c038f33 Compare November 3, 2026 14:53
@kv2019i

kv2019i commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

V3 pushed:

  • rebase on top of latest SOF main to resolve conflict, no other changes

@lgirdwood
lgirdwood merged commit acad546 into thesofproject:main Nov 3, 2026
45 checks passed
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.

4 participants