From 0812ccf9234bb954f08b103bb66c7afacd2094e9 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 12:30:29 +0300 Subject: [PATCH 1/3] zephyr: syscall: sof_dma: handle overflow in deep_copy_dma_blk_cfg_list() deep_copy_dma_blk_cfg_list() is used to verify the syscall arguments. Fix an issue with possible overflow when calculating the alloc size for DMA blocks. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index f12a29aa1efb..ef8ec635479a 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA @@ -111,12 +112,22 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con { struct dma_block_config *kern_cfg; struct dma_block_config *kern_prev = NULL, *kern_next, *user_next; + size_t alloc_size; int i = 0; if (!cfg->block_count) return NULL; - kern_cfg = rmalloc(0, sizeof(*kern_cfg) * cfg->block_count); + /* + * block_count is user-controlled, so compute the allocation size + * with an overflow check. Without it, a large block_count would + * wrap the product on 32-bit size_t, yield an undersized buffer, + * and let the copy loop below overflow the kernel heap. + */ + if (size_mul_overflow(sizeof(*kern_cfg), cfg->block_count, &alloc_size)) + return NULL; + + kern_cfg = rmalloc(0, alloc_size); if (!kern_cfg) return NULL; From 8e16b2fd24d899ba0c1a760f8271d724524c0394 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 13:45:22 +0300 Subject: [PATCH 2/3] zephyr: syscall: sof_dma: initialize all fields of dma_config z_vrfy_sof_dma_config() only passes allowed fields of dma_config struct to kernel. Add initialization of the stack dma_config object kern_cfg to ensure the skipped fields are set to a known state. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index ef8ec635479a..9c5d635cb394 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -190,7 +190,8 @@ static inline int z_vrfy_sof_dma_config(struct sof_dma *dma, uint32_t channel, struct dma_config *config) { struct dma_block_config *blk_cfgs; - struct dma_config kern_cfg, user_cfg; + struct dma_config kern_cfg = { 0 }; + struct dma_config user_cfg; int ret; K_OOPS(!sof_dma_is_valid(dma)); From 856ecf3d1218245700efd68f454018d8d7c5e57a Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Thu, 11 Jun 2026 14:06:22 +0300 Subject: [PATCH 3/3] zephyr: syscall: sof_dma: fix block count validation in dma blk copy deep_copy_dma_blk_cfg_list() does not check that number of entries in the linked list of DMA blocks matches cfg->block_count. This could be used to make kernel read from unvalidated user memory. Fix the issue by limiting list traversal to cfg->block_count. Signed-off-by: Kai Vehmanen --- zephyr/syscall/sof_dma.c | 52 +++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index 9c5d635cb394..05d6df377c95 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -111,9 +111,9 @@ static inline void z_vrfy_sof_dma_release_channel(struct sof_dma *dma, static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_config *cfg) { struct dma_block_config *kern_cfg; - struct dma_block_config *kern_prev = NULL, *kern_next, *user_next; + struct dma_block_config *kern_next, *user_next; size_t alloc_size; - int i = 0; + uint32_t i; if (!cfg->block_count) return NULL; @@ -131,17 +131,16 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con if (!kern_cfg) return NULL; - for (user_next = cfg->head_block, kern_next = kern_cfg; - user_next; - user_next = user_next->next_block, kern_next++, i++) { - if (i == cfg->block_count) { - /* last block can point to first one */ - if (user_next != cfg->head_block) - goto err; - - kern_prev->next_block = kern_cfg; - break; - } + user_next = cfg->head_block; + for (i = 0, kern_next = kern_cfg; i < cfg->block_count; i++, kern_next++) { + /* + * The user list must contain exactly block_count entries. + * A list that terminates early (NULL before the count is + * reached) is rejected so the kernel copy and block_count + * stay consistent and the driver never walks past the copy. + */ + if (!user_next) + goto err; if (k_usermode_from_copy(kern_next, user_next, sizeof(*kern_next))) goto err; @@ -169,10 +168,29 @@ static inline struct dma_block_config *deep_copy_dma_blk_cfg_list(struct dma_con goto err; } - if (kern_prev) - kern_prev->next_block = kern_next; - - kern_prev = kern_next; + /* + * kern_next->next_block now holds the untrusted user-space + * pointer copied above. Never let the kernel walk it: relink + * every block to the kernel copy so the list can never point + * outside the kern_cfg array. + */ + user_next = kern_next->next_block; + + if (i + 1 < cfg->block_count) { + /* link to the next kernel block */ + kern_next->next_block = kern_next + 1; + } else { + /* + * Last block: the user list must end here, either + * NULL-terminated or cyclic back to the first block. + */ + if (user_next == cfg->head_block) + kern_next->next_block = kern_cfg; + else if (!user_next) + kern_next->next_block = NULL; + else + goto err; + } } /* set transfer list to point to first kernel transfer config object */