Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 49 additions & 19 deletions zephyr/syscall/sof_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <sof/lib/dma.h>
#include <zephyr/kernel.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/sys/math_extras.h>

#ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA

Expand Down Expand Up @@ -110,27 +111,36 @@ 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;
int i = 0;
struct dma_block_config *kern_next, *user_next;
size_t alloc_size;
uint32_t i;

if (!cfg->block_count)
return NULL;

kern_cfg = rmalloc(0, sizeof(*kern_cfg) * cfg->block_count);
if (!kern_cfg)
/*
* 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;

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_cfg = rmalloc(0, alloc_size);
if (!kern_cfg)
return NULL;

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;
Expand Down Expand Up @@ -158,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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

you could remove the if and just put the else block after the loop? Or keep the if to stay correct and avoid a redundant assignment and just move the else out

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.

@lyakh worth it? this is not performance critical, so I'd rather keep the logic in the same place inside the for loop. Now it's easy to see that the unsafe "kern_next_block" is overwritten in all cases. It's run only once in either case. If more votes, I can move to outside for loop, agreed it will be functionally the same.

}

/* set transfer list to point to first kernel transfer config object */
Expand All @@ -179,7 +208,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));
Expand Down
Loading