If a kthread creation fails for some reason then uninitialized members of
the tasks array will be accessed on the error path since it is allocated
by kmalloc_array().
Limit the bound in such case.
Found by Linux Verification Center (linuxtesting.org).
Fixes: 65789daa8087 ("dma-mapping: add benchmark support for streaming DMA APIs")
Signed-off-by: Fedor Pchelkin <[email protected]>
---
kernel/dma/map_benchmark.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c
index 02205ab53b7e..ea938bc6c7e3 100644
--- a/kernel/dma/map_benchmark.c
+++ b/kernel/dma/map_benchmark.c
@@ -118,6 +118,7 @@ static int do_map_benchmark(struct map_benchmark_data *map)
if (IS_ERR(tsk[i])) {
pr_err("create dma_map thread failed\n");
ret = PTR_ERR(tsk[i]);
+ threads = i;
goto out;
}
--
2.45.0
On Fri, May 3, 2024 at 4:29 AM Fedor Pchelkin <[email protected]> wrote:
>
> If a kthread creation fails for some reason then uninitialized members of
> the tasks array will be accessed on the error path since it is allocated
> by kmalloc_array().
>
> Limit the bound in such case.
>
> Found by Linux Verification Center (linuxtesting.org).
>
> Fixes: 65789daa8087 ("dma-mapping: add benchmark support for streaming DMA APIs")
> Signed-off-by: Fedor Pchelkin <[email protected]>
> ---
Reviewed-by: Barry Song <[email protected]>
> kernel/dma/map_benchmark.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c
> index 02205ab53b7e..ea938bc6c7e3 100644
> --- a/kernel/dma/map_benchmark.c
> +++ b/kernel/dma/map_benchmark.c
> @@ -118,6 +118,7 @@ static int do_map_benchmark(struct map_benchmark_data *map)
> if (IS_ERR(tsk[i])) {
> pr_err("create dma_map thread failed\n");
> ret = PTR_ERR(tsk[i]);
> + threads = i;
> goto out;
> }
>
> --
> 2.45.0
>
>
On 2024-05-02 5:18 pm, Fedor Pchelkin wrote:
> If a kthread creation fails for some reason then uninitialized members of
> the tasks array will be accessed on the error path since it is allocated
> by kmalloc_array().
>
> Limit the bound in such case.
I don't think this is right... The put_task_struct() calls on the error
path are supposed to balance the get_task_struct() calls which only
happen *after* all the threads are successfully created - see commit
d17405d52bac ("dma-mapping: benchmark: fix kernel crash when
dma_map_single fails") - although I now wonder whether that might have
been better done by replacing kthread_stop() with kthread_stop_put(). It
doesn't look like we've ever actually tried to free any previous threads
from the point of allocation failure.
Thanks,
Robin.
> Found by Linux Verification Center (linuxtesting.org).
>
> Fixes: 65789daa8087 ("dma-mapping: add benchmark support for streaming DMA APIs")
> Signed-off-by: Fedor Pchelkin <[email protected]>
> ---
> kernel/dma/map_benchmark.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c
> index 02205ab53b7e..ea938bc6c7e3 100644
> --- a/kernel/dma/map_benchmark.c
> +++ b/kernel/dma/map_benchmark.c
> @@ -118,6 +118,7 @@ static int do_map_benchmark(struct map_benchmark_data *map)
> if (IS_ERR(tsk[i])) {
> pr_err("create dma_map thread failed\n");
> ret = PTR_ERR(tsk[i]);
> + threads = i;
> goto out;
> }
>
Robin Murphy wrote:
> On 2024-05-02 5:18 pm, Fedor Pchelkin wrote:
> > If a kthread creation fails for some reason then uninitialized members of
> > the tasks array will be accessed on the error path since it is allocated
> > by kmalloc_array().
> >
> > Limit the bound in such case.
>
> I don't think this is right... The put_task_struct() calls on the error
> path are supposed to balance the get_task_struct() calls which only
> happen *after* all the threads are successfully created - see commit
Thanks, Robin! You're right. Now I see this..
> d17405d52bac ("dma-mapping: benchmark: fix kernel crash when
> dma_map_single fails") - although I now wonder whether that might have
> been better done by replacing kthread_stop() with kthread_stop_put(). It
> doesn't look like we've ever actually tried to free any previous threads
> from the point of allocation failure.
It should have looked like the diff below, as you say. And it's probably
better to merge the two patches together so that we eliminate task leaks
in case kthread_stop_put() returns error like it is below.
Will rework that in v2.
diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c
index 02205ab53b7e..3b7658ce1599 100644
--- a/kernel/dma/map_benchmark.c
+++ b/kernel/dma/map_benchmark.c
@@ -118,6 +118,8 @@ static int do_map_benchmark(struct map_benchmark_data *map)
if (IS_ERR(tsk[i])) {
pr_err("create dma_map thread failed\n");
ret = PTR_ERR(tsk[i]);
+ while (--i >= 0)
+ kthread_stop(tsk[i]);
goto out;
}
@@ -141,7 +143,7 @@ static int do_map_benchmark(struct map_benchmark_data *map)
/* wait for the completion of benchmark threads */
for (i = 0; i < threads; i++) {
- ret = kthread_stop(tsk[i]);
+ ret = kthread_stop_put(tsk[i]);
if (ret)
goto out;
}
@@ -170,8 +172,6 @@ static int do_map_benchmark(struct map_benchmark_data *map)
}
out:
- for (i = 0; i < threads; i++)
- put_task_struct(tsk[i]);
put_device(map->dev);
kfree(tsk);
return ret;