]> git.baikalelectronics.ru Git - kernel.git/commit
workqueue: don't skip lockdep work dependency in cancel_work_sync()
authorTetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Fri, 29 Jul 2022 04:30:23 +0000 (13:30 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 28 Sep 2022 09:04:09 +0000 (11:04 +0200)
commitb46c3df97bfe255bb5d2a3c0ec79aacd9cb85ccb
tree1264052429db5c65e5cb7adc01425c53f3a37634
parent42d7fdfbb165fd51b6c35b2d1e91113eaa15c642
workqueue: don't skip lockdep work dependency in cancel_work_sync()

[ Upstream commit 804817ed5c3b3eed87b9c122ce4d13a524c1a1d7 ]

Like Hillf Danton mentioned

  syzbot should have been able to catch cancel_work_sync() in work context
  by checking lockdep_map in __flush_work() for both flush and cancel.

in [1], being unable to report an obvious deadlock scenario shown below is
broken. From locking dependency perspective, sync version of cancel request
should behave as if flush request, for it waits for completion of work if
that work has already started execution.

  ----------
  #include <linux/module.h>
  #include <linux/sched.h>
  static DEFINE_MUTEX(mutex);
  static void work_fn(struct work_struct *work)
  {
    schedule_timeout_uninterruptible(HZ / 5);
    mutex_lock(&mutex);
    mutex_unlock(&mutex);
  }
  static DECLARE_WORK(work, work_fn);
  static int __init test_init(void)
  {
    schedule_work(&work);
    schedule_timeout_uninterruptible(HZ / 10);
    mutex_lock(&mutex);
    cancel_work_sync(&work);
    mutex_unlock(&mutex);
    return -EINVAL;
  }
  module_init(test_init);
  MODULE_LICENSE("GPL");
  ----------

The check this patch restores was added by commit 00d9fb51861b21b5
("workqueue: Catch more locking problems with flush_work()").

Then, lockdep's crossrelease feature was added by commit 88a852e76563840c
("locking/lockdep: Implement the 'crossrelease' feature"). As a result,
this check was once removed by commit 2274720cd487079a ("workqueue: Remove
now redundant lock acquisitions wrt. workqueue flushes").

But lockdep's crossrelease feature was removed by commit 20e84d16b3b130d5
("locking/lockdep: Remove the cross-release locking checks"). At this
point, this check should have been restored.

Then, commit 8671b140e5311ae8 ("workqueue: skip lockdep wq dependency in
cancel_work_sync()") introduced a boolean flag in order to distinguish
flush_work() and cancel_work_sync(), for checking "struct workqueue_struct"
dependency when called from cancel_work_sync() was causing false positives.

Then, commit 50d79fccd2aacf6b ("workqueue: re-add lockdep dependencies for
flushing") tried to restore "struct work_struct" dependency check, but by
error checked this boolean flag. Like an example shown above indicates,
"struct work_struct" dependency needs to be checked for both flush_work()
and cancel_work_sync().

Link: https://lkml.kernel.org/r/20220504044800.4966-1-hdanton@sina.com
Reported-by: Hillf Danton <hdanton@sina.com>
Suggested-by: Lai Jiangshan <jiangshanlai@gmail.com>
Fixes: 50d79fccd2aacf6b ("workqueue: re-add lockdep dependencies for flushing")
Cc: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
kernel/workqueue.c