diff mbox series

[OpenWrt-Devel] runqueue: Fix the callbacks order in runqueue_task_kill()

Message ID 20190621153828.20699-1-albeu@free.fr
State Accepted
Delegated to: Petr Štetiar
Headers show
Series [OpenWrt-Devel] runqueue: Fix the callbacks order in runqueue_task_kill() | expand

Commit Message

Alban June 21, 2019, 3:38 p.m. UTC
Since commit 11e8afea (runqueue should cal the complete handler from
more places) the call to the complete() callback has been moved to
runqueue_task_complete().  However in runqueue_task_kill()
runqueue_task_complete() is called before the kill() callback.
This will result in a use after free if the complete() callback free
the task struct.

Furthermore runqueue_start_next() is already called at the end of
runqueue_task_complete(), so there is no need to call it again in
runqueue_task_kill().

Signed-off-by: Alban Bedel <albeu@free.fr>
---
 runqueue.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

Comments

John Crispin July 1, 2019, 1:48 p.m. UTC | #1
On 21/06/2019 17:38, Alban Bedel wrote:
> Since commit 11e8afea (runqueue should cal the complete handler from
> more places) the call to the complete() callback has been moved to
> runqueue_task_complete().  However in runqueue_task_kill()
> runqueue_task_complete() is called before the kill() callback.
> This will result in a use after free if the complete() callback free
> the task struct.
>
> Furthermore runqueue_start_next() is already called at the end of
> runqueue_task_complete(), so there is no need to call it again in
> runqueue_task_kill().
>
> Signed-off-by: Alban Bedel <albeu@free.fr>
> ---
>   runqueue.c | 4 +---
>   1 file changed, 1 insertion(+), 3 deletions(-)
>
> diff --git a/runqueue.c b/runqueue.c
> index a1d0133..4c621aa 100644
> --- a/runqueue.c
> +++ b/runqueue.c
> @@ -196,11 +196,9 @@ void runqueue_task_kill(struct runqueue_task *t)
>   	if (!t->queued)
>   		return;
>   
> -	runqueue_task_complete(t);
>   	if (running && t->type->kill)
>   		t->type->kill(q, t);
> -
> -	runqueue_start_next(q);
> +	runqueue_task_complete(t);
>   }
>   
>   void runqueue_stop(struct runqueue *q)

Hi,

runqueue_task_complete() will decrement running which, if called after the kill clause might not even trigger the kill() call. I am assuming you are running a custom runqueue_task_type ?

  John
Alban July 1, 2019, 2:23 p.m. UTC | #2
On Mon, 1 Jul 2019 15:48:47 +0200
John Crispin <john@phrozen.org> wrote:

> On 21/06/2019 17:38, Alban Bedel wrote:
> > Since commit 11e8afea (runqueue should cal the complete handler from
> > more places) the call to the complete() callback has been moved to
> > runqueue_task_complete().  However in runqueue_task_kill()
> > runqueue_task_complete() is called before the kill() callback.
> > This will result in a use after free if the complete() callback free
> > the task struct.
> >
> > Furthermore runqueue_start_next() is already called at the end of
> > runqueue_task_complete(), so there is no need to call it again in
> > runqueue_task_kill().
> >
> > Signed-off-by: Alban Bedel <albeu@free.fr>
> > ---
> >   runqueue.c | 4 +---
> >   1 file changed, 1 insertion(+), 3 deletions(-)
> >
> > diff --git a/runqueue.c b/runqueue.c
> > index a1d0133..4c621aa 100644
> > --- a/runqueue.c
> > +++ b/runqueue.c
> > @@ -196,11 +196,9 @@ void runqueue_task_kill(struct runqueue_task
> > *t) if (!t->queued)
> >   		return;
> >   
> > -	runqueue_task_complete(t);
> >   	if (running && t->type->kill)
> >   		t->type->kill(q, t);
> > -
> > -	runqueue_start_next(q);
> > +	runqueue_task_complete(t);
> >   }
> >   
> >   void runqueue_stop(struct runqueue *q)  
> 
> Hi,
> 
> runqueue_task_complete() will decrement running which, if called
> after the kill clause might not even trigger the kill() call. I am
> assuming you are running a custom runqueue_task_type ?

No, TBH I haven't tested this, but as there is no documentation I had
to go through the code and noticed this suspicious construct. I then
saw commit 6a7fb7d8d (runqueue: fix use-after-free bug) which confirmed
that complete() is expected to free the task struct, which with the
above code will clearly lead to an access after free.

But I don't really see what you mean, 'running' is a boolean so it
can't be decremented. Did you mean 'running_tasks'?

Alban
John Crispin July 1, 2019, 2:54 p.m. UTC | #3
On 01/07/2019 16:23, Alban wrote:
> On Mon, 1 Jul 2019 15:48:47 +0200
> John Crispin <john@phrozen.org> wrote:
>
>> On 21/06/2019 17:38, Alban Bedel wrote:
>>> Since commit 11e8afea (runqueue should cal the complete handler from
>>> more places) the call to the complete() callback has been moved to
>>> runqueue_task_complete().  However in runqueue_task_kill()
>>> runqueue_task_complete() is called before the kill() callback.
>>> This will result in a use after free if the complete() callback free
>>> the task struct.
>>>
>>> Furthermore runqueue_start_next() is already called at the end of
>>> runqueue_task_complete(), so there is no need to call it again in
>>> runqueue_task_kill().
>>>
>>> Signed-off-by: Alban Bedel <albeu@free.fr>
>>> ---
>>>    runqueue.c | 4 +---
>>>    1 file changed, 1 insertion(+), 3 deletions(-)
>>>
>>> diff --git a/runqueue.c b/runqueue.c
>>> index a1d0133..4c621aa 100644
>>> --- a/runqueue.c
>>> +++ b/runqueue.c
>>> @@ -196,11 +196,9 @@ void runqueue_task_kill(struct runqueue_task
>>> *t) if (!t->queued)
>>>    		return;
>>>    
>>> -	runqueue_task_complete(t);
>>>    	if (running && t->type->kill)
>>>    		t->type->kill(q, t);
>>> -
>>> -	runqueue_start_next(q);
>>> +	runqueue_task_complete(t);
>>>    }
>>>    
>>>    void runqueue_stop(struct runqueue *q)
>> Hi,
>>
>> runqueue_task_complete() will decrement running which, if called
>> after the kill clause might not even trigger the kill() call. I am
>> assuming you are running a custom runqueue_task_type ?
> No, TBH I haven't tested this, but as there is no documentation I had
> to go through the code and noticed this suspicious construct. I then
> saw commit 6a7fb7d8d (runqueue: fix use-after-free bug) which confirmed
> that complete() is expected to free the task struct, which with the
> above code will clearly lead to an access after free.
>
> But I don't really see what you mean, 'running' is a boolean so it
> can't be decremented. Did you mean 'running_tasks'?
>
> Alban


i did actually mean running_task, I'll have another look later on today

     John
Petr Štetiar March 11, 2020, 2:54 p.m. UTC | #4
Alban <albeu@free.fr> [2019-07-01 16:23:42]:

Hi,

sorry for the late response, but I've just noticed, that it's related to
libubox and got interested. I would use "PATCH libubox" subject next time so
it's more clear at first sight.

> No, TBH I haven't tested this, but as there is no documentation I had
> to go through the code and noticed this suspicious construct. I then
> saw commit 6a7fb7d8d (runqueue: fix use-after-free bug) which confirmed
> that complete() is expected to free the task struct, which with the
> above code will clearly lead to an access after free.

Can I kindly ask you for additional favor, adding unit test case which would
expose this bug? It's going to help us in the future with possible regressions
etc. It's not mandatory (yet), but really nice and really helps
merging/reviewing the changes much faster (at least for me).

libubox contains unit tests already, tests are run on CI[1] automatically, one
of the test runs happens under Valgrind, another under various clang's
sanitizers, which should hopefully catch this use-after-free bugs.

You can find basic unit test for runqueue component in
`tests/test-runqueue.c`, perhaps you could adjust this test directly (or write
new one) in order to expose the bug.

Thanks!

1. http://lists.infradead.org/pipermail/openwrt-devel/2019-December/020831.html

-- ynezz
diff mbox series

Patch

diff --git a/runqueue.c b/runqueue.c
index a1d0133..4c621aa 100644
--- a/runqueue.c
+++ b/runqueue.c
@@ -196,11 +196,9 @@  void runqueue_task_kill(struct runqueue_task *t)
 	if (!t->queued)
 		return;
 
-	runqueue_task_complete(t);
 	if (running && t->type->kill)
 		t->type->kill(q, t);
-
-	runqueue_start_next(q);
+	runqueue_task_complete(t);
 }
 
 void runqueue_stop(struct runqueue *q)