Commit 33e3f0a3 authored by Richard Clark's avatar Richard Clark Committed by Tejun Heo
Browse files

workqueue: Add a new flag to spot the potential UAF error



Currently if the user queues a new work item unintentionally
into a wq after the destroy_workqueue(wq), the work still can
be queued and scheduled without any noticeable kernel message
before the end of a RCU grace period.

As a debug-aid facility, this commit adds a new flag
__WQ_DESTROYING to spot that issue by triggering a kernel WARN
message.

Signed-off-by: default avatarRichard Clark <richard.xnu.clark@gmail.com>
Reviewed-by: default avatarLai Jiangshan <jiangshanlai@gmail.com>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
parent 512dee0c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -335,6 +335,7 @@ enum {
	 */
	WQ_POWER_EFFICIENT	= 1 << 7,

	__WQ_DESTROYING		= 1 << 15, /* internal: workqueue is destroying */
	__WQ_DRAINING		= 1 << 16, /* internal: workqueue is draining */
	__WQ_ORDERED		= 1 << 17, /* internal: workqueue is ordered */
	__WQ_LEGACY		= 1 << 18, /* internal: create*_workqueue() */
+12 −3
Original line number Diff line number Diff line
@@ -1433,9 +1433,13 @@ static void __queue_work(int cpu, struct workqueue_struct *wq,
	lockdep_assert_irqs_disabled();


	/* if draining, only works from the same workqueue are allowed */
	if (unlikely(wq->flags & __WQ_DRAINING) &&
	    WARN_ON_ONCE(!is_chained_work(wq)))
	/*
	 * For a draining wq, only works from the same workqueue are
	 * allowed. The __WQ_DESTROYING helps to spot the issue that
	 * queues a new work item to a wq after destroy_workqueue(wq).
	 */
	if (unlikely(wq->flags & (__WQ_DESTROYING | __WQ_DRAINING) &&
		     WARN_ON_ONCE(!is_chained_work(wq))))
		return;
	rcu_read_lock();
retry:
@@ -4414,6 +4418,11 @@ void destroy_workqueue(struct workqueue_struct *wq)
	 */
	workqueue_sysfs_unregister(wq);

	/* mark the workqueue destruction is in progress */
	mutex_lock(&wq->mutex);
	wq->flags |= __WQ_DESTROYING;
	mutex_unlock(&wq->mutex);

	/* drain it before proceeding with destruction */
	drain_workqueue(wq);