null pointer risk of kernel workqueue
Hi Tejun & Jiangshan,
I find an null pointer risk in the code of workqueue. Here is description:
If draining, __queue_work() will call the function is_chained_work() to do some checks. In is_chained_work(), worker->current_pwq is used directly. It should be not safe.
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L1384
If you check the thread function of this worker, worker_thread(), you will find worker->current_pwq is null when one work is done or ready to be processed.
This issue may happen only if we queue work during executing drain_workqueue().
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L2173
There are very few places to call drain_workqueue() in the whole linux kernel. I think that's why no one noticed this risk.
Xiaofei Tan
_______________________________________________
linuxarm mailing list
[email protected]
http://rnd-openeuler.huawei.com/mailman/listinfo/linuxarm
.
Tejun Heo
maintainerHello,
Quoted 4 lines · tanxiaofei
Hi Tejun & Jiangshan,
I find an null pointer risk in the code of workqueue. Here is description:
Quoted 8 lines
If draining,
__queue_work()will call the functionis_chained_work()to do some checks. Inis_chained_work(),worker->current_pwqis used directly. It should be not safe.
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L1384If you check the thread function of this worker,
worker_thread(), you will findworker->current_pwqis null when one work is done or ready to be processed.
This issue may happen only if we queue work during executingdrain_workqueue().
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L2173
Hmmm? I don't get it. worker->current_pwq is guaranteed to be set while a work function is being executed and the chained check can only get there iff the the worker is executing a work function.
Thanks.
Signature · 1 line
tejun
Quoted 11 lines · Tejun Heo
Hello,
Quoted 8 lines · tanxiaofei
Hi Tejun & Jiangshan,
I find an null pointer risk in the code of workqueue. Here is description:
If draining,
__queue_work()will call the functionis_chained_work()to do some checks. Inis_chained_work(),worker->current_pwqis used directly. It should be not safe.
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L1384
Quoted 9 lines
Quoted 4 lines
If you check the thread function of this worker,
worker_thread(), you will findworker->current_pwqis null when one work is done or ready to be processed.
This issue may happen only if we queue work during executingdrain_workqueue().
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L2173Hmmm? I don't get it.
worker->current_pwqis guaranteed to be set while a work function is being executed and the chained check can only get there iff the the worker is executing a work function.
Hi Tejun,
Thanks for your quick reply.
Hmm, but i think interrupt preemption could make this happen.
For example, the scenario that all following conditions are met.
1.The handler of an interrupt call queue_work() to queue some works, and the workqueue is draining. 2.An worker thread is interrupted by this interrupt.
3.The worker thread is being executed, and at the very moment,worker->current_pwq is set to null.(This is possible and please check function worker_thread() carefully).
Thanks.
Hi Tejun,
Any comments about this?
Thanks
Quoted 42 lines · tanxiaofei
Quoted 11 lines · Tejun Heo
Hello,
Quoted 8 lines · tanxiaofei
Hi Tejun & Jiangshan,
I find an null pointer risk in the code of workqueue. Here is description:
If draining,
__queue_work()will call the functionis_chained_work()to do some checks. Inis_chained_work(),worker->current_pwqis used directly. It should be not safe.
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L1384Quoted 9 lines
Quoted 4 lines
If you check the thread function of this worker,
worker_thread(), you will findworker->current_pwqis null when one work is done or ready to be processed.
This issue may happen only if we queue work during executingdrain_workqueue().
http://elixir.free-electrons.com/linux/latest/source/kernel/workqueue.c#L2173Hmmm? I don't get it.
worker->current_pwqis guaranteed to be set while a work function is being executed and the chained check can only get there iff the the worker is executing a work function.Hi Tejun,
Thanks for your quick reply.
Hmm, but i think interrupt preemption could make this happen.
For example, the scenario that all following conditions are met.
1.The handler of an interrupt callqueue_work()to queue some works, and the workqueue is draining. 2.An worker thread is interrupted by this interrupt.
3.The worker thread is being executed, and at the very moment,worker->current_pwq is set to null.(This is possible and please check functionworker_thread()carefully).Thanks.
.
Tejun Heo
maintainerQuoted 3 lines · tanxiaofei
Hi Tejun,
Any comments about this?
I think you're confused, or at least can't understand what you're trying to say. Can you create a rero?
Thanks.
Signature · 1 line
tejun
Tejun Heo
maintainerQuoted 7 lines · Tejun Heo
Quoted 3 lines · tanxiaofei
Hi Tejun,
Any comments about this?
I think you're confused, or at least can't understand what you're trying to say. Can you create a rero?
Sorry, I meant to write repro - a reproduction case.
Signature · 1 line
tejun
Quoted 8 lines · Tejun Heo
Quoted 3 lines · tanxiaofei
Hi Tejun,
Any comments about this?
I think you're confused, or at least can't understand what you're trying to say. Can you create a rero?
Hi Tejun,
The case is as following:
worker_thread()
|-spin_lock_irq()
|-process_one_work()
|-worker->current_pwq = pwq
|-spin_unlock_irq()
|-worker->current_func(work)
|-spin_lock_irq()
|-worker->current_pwq = NULL
|-spin_unlock_irq()
//interrupt here
|-irq_handler
|-__queue_work()
//assuming that the wq is draining
|-if (unlikely(wq->flags & __WQ_DRAINING) &&WARN_ON_ONCE(!is_chained_work(wq)))
|-is_chained_work(wq)
|-current_wq_worker() // Here, 'current' is the interrupted worker!
|-current->current_pwq is NULL here!|-schedule()
And I think the following patch can solve the bug, right?
kernel/workqueue_internal.h +1 -1
@@ -59,7 +59,7 @@ struct worker {*/ static inline struct worker *current_wq_worker (void ) { -if (current->flags &PF_WQ_WORKER ) +if (!in_irq () && (current->flags &PF_WQ_WORKER ))return kthread_data (current);return NULL ; }
Thanks,
Li Bin
Quoted 2 lines
Thanks.
Hi Bin,
Yes, that's it. thanks.
--
tanxiaofei
Quoted 60 lines · Li Bin
Quoted 8 lines · Tejun Heo
Quoted 3 lines · tanxiaofei
Hi Tejun,
Any comments about this?
I think you're confused, or at least can't understand what you're trying to say. Can you create a rero?
Hi Tejun,
The case is as following:
worker_thread()
|-spin_lock_irq()
|-process_one_work()|-worker->current_pwq = pwq |-spin_unlock_irq() |-worker->current_func(work) |-spin_lock_irq() |-worker->current_pwq = NULL |-spin_unlock_irq() //interrupt here |-irq_handler |-__queue_work() //assuming that the wq is draining |-if (unlikely(wq->flags & __WQ_DRAINING) &&WARN_ON_ONCE(!is_chained_work(wq))) |-is_chained_work(wq) |-current_wq_worker() // Here, 'current' is the interrupted worker! |-current->current_pwq is NULL here!|-schedule()
And I think the following patch can solve the bug, right?
kernel/workqueue_internal.h +1
-1@@ -59,7 +59,7 @@ struct worker {*/ static inline struct worker *current_wq_worker (void ) { -if (current->flags &PF_WQ_WORKER ) +if (!in_irq () && (current->flags &PF_WQ_WORKER ))return kthread_data (current);return NULL ; }Thanks,
Li BinQuoted 2 lines
Thanks.
.
Tejun Heo
maintainerHello,
Quoted 41 lines · Li Bin
Quoted 8 lines · Tejun Heo
Quoted 3 lines · tanxiaofei
Hi Tejun,
Any comments about this?
I think you're confused, or at least can't understand what you're trying to say. Can you create a rero?
Hi Tejun,
The case is as following:
worker_thread()
|-spin_lock_irq()
|-process_one_work()|-worker->current_pwq = pwq |-spin_unlock_irq() |-worker->current_func(work) |-spin_lock_irq() |-worker->current_pwq = NULL |-spin_unlock_irq() //interrupt here |-irq_handler |-__queue_work() //assuming that the wq is draining |-if (unlikely(wq->flags & __WQ_DRAINING) &&WARN_ON_ONCE(!is_chained_work(wq))) |-is_chained_work(wq) |-current_wq_worker() // Here, 'current' is the interrupted worker! |-current->current_pwq is NULL here!|-schedule()
And I think the following patch can solve the bug, right?
kernel/workqueue_internal.h +0
-0
kernel/workqueue_internal.h struct worker { · line 65
Quoted 8 lines
kernel/workqueue_internal.h +1
-1@@ -59,7 +59,7 @@ struct worker {*/ static inline struct worker *current_wq_worker (void ) { -if (current->flags &PF_WQ_WORKER ) +if (!in_irq () && (current->flags &PF_WQ_WORKER ))return kthread_data (current);return NULL ; }
Yeah, that makes sense to me. Can you please resend the patch with patch description and SOB?
Thanks.
Signature · 1 line
tejun
Quoted 19 lines · Tejun Heo
Quoted 8 lines
And I think the following patch can solve the bug, right?
kernel/workqueue_internal.h +0
-0kernel/workqueue_internal.h struct worker { · line 65
Quoted 8 lines
kernel/workqueue_internal.h +1
-1@@ -59,7 +59,7 @@ struct worker {*/ static inline struct worker *current_wq_worker (void ) { -if (current->flags &PF_WQ_WORKER ) +if (!in_irq () && (current->flags &PF_WQ_WORKER ))return kthread_data (current);return NULL ; }Yeah, that makes sense to me. Can you please resend the patch with patch description and SOB?
Ok, I will resend the patch soon.
Thanks,
Li Bin
Quoted 3 lines
Thanks.