Received: by 2002:a05:6a10:f3d0:0:0:0:0 with SMTP id a16csp3731055pxv; Mon, 28 Jun 2021 11:22:20 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzMvlkvP9FKS/ztQihSVIL19PdoQ0Ga8VAtfxwvLyYp/Tx9xzaP9aK2xoXuGe7j/33yhMWX X-Received: by 2002:a17:906:9419:: with SMTP id q25mr25492030ejx.341.1624904540162; Mon, 28 Jun 2021 11:22:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1624904540; cv=none; d=google.com; s=arc-20160816; b=X1rmYZeHGkNxRAAnn3zyVeLxhdYLxVkw+S+JFqnh4gIbP+oLY6OMV1mzGJlAD47GpB EkKtd7WIBIXQwHHL7nOLeUfGST6sdInopyvLZ3gY9f16JNf7xFKYd4m+1LbTx0yzn9jE Mb/QQTxI3swR4a8s7GWrx8aiTzCEycDorIkor6w9L5A03dbdjitcFnNGqpfK0htmepOv m6IUBp4EowmD4BlQh13HXt2xlowWagEvHeVMpslYPiKE6SAVo22hNTSUWDZojKJgidVg N+0Ak33E6LartlfRQiaeLTchJZeMdKIahdVd06RZn/3eZdvdSYfyAMALGH4AzLyXmOmL Vm1A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=5mfWPeyPPlPnnQqcLlklnkoqikXtZjtMJOq0XvYwEXA=; b=qwFDKroUFYcfBPZOLt4Z+g7l27Hy+KpIAA9GgDROQpInYtQ3s8+JLFr8R/YnmlOA94 wT5CVCQYFSxug74AjQULkm4eBuB4yXqv7HNlW6GlbRXaq3BXOU0lICyDA9iKuCMXmBPg bhYVC7UHJJ+x1unXVUPg5pIPJgR4SwzKr+jNEkg8HTdFYmjVht2ayOjOzBam8Dz3IUES qaaQMolEUDLPfpwgTv9ALVcCufvgBQwiP0JyKUSaqf/azvSM5xxq9BQKE2jr9BFveIvv y+PjeIqiGvxD7pBiysciaBU3iN3S9SscB7f8g0lVsVL1N3KSOKuOMWXlFcaStv29CWA6 XLLw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=hrd5iuef; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id mm18si14533392ejb.584.2021.06.28.11.21.55; Mon, 28 Jun 2021 11:22:20 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=hrd5iuef; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234632AbhF1OjD (ORCPT + 99 others); Mon, 28 Jun 2021 10:39:03 -0400 Received: from mail.kernel.org ([198.145.29.99]:36752 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233456AbhF1Oan (ORCPT ); Mon, 28 Jun 2021 10:30:43 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 61EB961CCE; Mon, 28 Jun 2021 14:27:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1624890427; bh=mKg1sxCqUiBrCg3VPkgkt38kXG9lKQOs0VlwCP0QPAw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hrd5iuefL/XJDGh92w+9N4h5B4UfZH+2I1A6zUdxU3NNpzFJTr3JDA518Kc67DE1e D52mL44MUH9OzvgwLLiure+sUBxDl8a7nBYZ5cG0ggzm8JzV+3XXN+Ubp7HpNXtHn5 oUqB4/o+wFM4A9u8pErWE61XdGPD6/HNUN0W0Ze8s+xukhPn3wW0V9ai7RDKY9RgNK ME4XPhEOCYiUejSU5J7aGKrK81RBs+60J9zutFQ7a2qt2BuJ5+NJv19/jfc8bN8np2 Dw4pLPxK+xQ4PufwYa5jJptsybzpSvpftxH4EEMLVBVAqQorAwKqHDOnl4hNxrw4N3 EQr/JA1m/NQtg== From: Sasha Levin To: linux-kernel@vger.kernel.org, stable@vger.kernel.org Cc: Petr Mladek , Martin Liu , jenhaochen@google.com, Minchan Kim , Nathan Chancellor , Nick Desaulniers , Oleg Nesterov , Tejun Heo , Andrew Morton , Linus Torvalds , Greg Kroah-Hartman Subject: [PATCH 5.10 068/101] kthread: prevent deadlock when kthread_mod_delayed_work() races with kthread_cancel_delayed_work_sync() Date: Mon, 28 Jun 2021 10:25:34 -0400 Message-Id: <20210628142607.32218-69-sashal@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210628142607.32218-1-sashal@kernel.org> References: <20210628142607.32218-1-sashal@kernel.org> MIME-Version: 1.0 X-KernelTest-Patch: http://kernel.org/pub/linux/kernel/v5.x/stable-review/patch-5.10.47-rc1.gz X-KernelTest-Tree: git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable-rc.git X-KernelTest-Branch: linux-5.10.y X-KernelTest-Patches: git://git.kernel.org/pub/scm/linux/kernel/git/stable/stable-queue.git X-KernelTest-Version: 5.10.47-rc1 X-KernelTest-Deadline: 2021-06-30T14:25+00:00 X-stable: review X-Patchwork-Hint: Ignore Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Petr Mladek commit 5fa54346caf67b4b1b10b1f390316ae466da4d53 upstream. The system might hang with the following backtrace: schedule+0x80/0x100 schedule_timeout+0x48/0x138 wait_for_common+0xa4/0x134 wait_for_completion+0x1c/0x2c kthread_flush_work+0x114/0x1cc kthread_cancel_work_sync.llvm.16514401384283632983+0xe8/0x144 kthread_cancel_delayed_work_sync+0x18/0x2c xxxx_pm_notify+0xb0/0xd8 blocking_notifier_call_chain_robust+0x80/0x194 pm_notifier_call_chain_robust+0x28/0x4c suspend_prepare+0x40/0x260 enter_state+0x80/0x3f4 pm_suspend+0x60/0xdc state_store+0x108/0x144 kobj_attr_store+0x38/0x88 sysfs_kf_write+0x64/0xc0 kernfs_fop_write_iter+0x108/0x1d0 vfs_write+0x2f4/0x368 ksys_write+0x7c/0xec It is caused by the following race between kthread_mod_delayed_work() and kthread_cancel_delayed_work_sync(): CPU0 CPU1 Context: Thread A Context: Thread B kthread_mod_delayed_work() spin_lock() __kthread_cancel_work() spin_unlock() del_timer_sync() kthread_cancel_delayed_work_sync() spin_lock() __kthread_cancel_work() spin_unlock() del_timer_sync() spin_lock() work->canceling++ spin_unlock spin_lock() queue_delayed_work() // dwork is put into the worker->delayed_work_list spin_unlock() kthread_flush_work() // flush_work is put at the tail of the dwork wait_for_completion() Context: IRQ kthread_delayed_work_timer_fn() spin_lock() list_del_init(&work->node); spin_unlock() BANG: flush_work is not longer linked and will never get proceed. The problem is that kthread_mod_delayed_work() checks work->canceling flag before canceling the timer. A simple solution is to (re)check work->canceling after __kthread_cancel_work(). But then it is not clear what should be returned when __kthread_cancel_work() removed the work from the queue (list) and it can't queue it again with the new @delay. The return value might be used for reference counting. The caller has to know whether a new work has been queued or an existing one was replaced. The proper solution is that kthread_mod_delayed_work() will remove the work from the queue (list) _only_ when work->canceling is not set. The flag must be checked after the timer is stopped and the remaining operations can be done under worker->lock. Note that kthread_mod_delayed_work() could remove the timer and then bail out. It is fine. The other canceling caller needs to cancel the timer as well. The important thing is that the queue (list) manipulation is done atomically under worker->lock. Link: https://lkml.kernel.org/r/20210610133051.15337-3-pmladek@suse.com Fixes: 9a6b06c8d9a220860468a ("kthread: allow to modify delayed kthread work") Signed-off-by: Petr Mladek Reported-by: Martin Liu Cc: Cc: Minchan Kim Cc: Nathan Chancellor Cc: Nick Desaulniers Cc: Oleg Nesterov Cc: Tejun Heo Cc: Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- kernel/kthread.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/kernel/kthread.c b/kernel/kthread.c index 415417b76bfb..36be4364b313 100644 --- a/kernel/kthread.c +++ b/kernel/kthread.c @@ -1071,8 +1071,11 @@ static void kthread_cancel_delayed_work_timer(struct kthread_work *work, } /* - * This function removes the work from the worker queue. Also it makes sure - * that it won't get queued later via the delayed work's timer. + * This function removes the work from the worker queue. + * + * It is called under worker->lock. The caller must make sure that + * the timer used by delayed work is not running, e.g. by calling + * kthread_cancel_delayed_work_timer(). * * The work might still be in use when this function finishes. See the * current_work proceed by the worker. @@ -1080,13 +1083,8 @@ static void kthread_cancel_delayed_work_timer(struct kthread_work *work, * Return: %true if @work was pending and successfully canceled, * %false if @work was not pending */ -static bool __kthread_cancel_work(struct kthread_work *work, bool is_dwork, - unsigned long *flags) +static bool __kthread_cancel_work(struct kthread_work *work) { - /* Try to cancel the timer if exists. */ - if (is_dwork) - kthread_cancel_delayed_work_timer(work, flags); - /* * Try to remove the work from a worker list. It might either * be from worker->work_list or from worker->delayed_work_list. @@ -1139,11 +1137,23 @@ bool kthread_mod_delayed_work(struct kthread_worker *worker, /* Work must not be used with >1 worker, see kthread_queue_work() */ WARN_ON_ONCE(work->worker != worker); - /* Do not fight with another command that is canceling this work. */ + /* + * Temporary cancel the work but do not fight with another command + * that is canceling the work as well. + * + * It is a bit tricky because of possible races with another + * mod_delayed_work() and cancel_delayed_work() callers. + * + * The timer must be canceled first because worker->lock is released + * when doing so. But the work can be removed from the queue (list) + * only when it can be queued again so that the return value can + * be used for reference counting. + */ + kthread_cancel_delayed_work_timer(work, &flags); if (work->canceling) goto out; + ret = __kthread_cancel_work(work); - ret = __kthread_cancel_work(work, true, &flags); fast_queue: __kthread_queue_delayed_work(worker, dwork, delay); out: @@ -1165,7 +1175,10 @@ static bool __kthread_cancel_work_sync(struct kthread_work *work, bool is_dwork) /* Work must not be used with >1 worker, see kthread_queue_work(). */ WARN_ON_ONCE(work->worker != worker); - ret = __kthread_cancel_work(work, is_dwork, &flags); + if (is_dwork) + kthread_cancel_delayed_work_timer(work, &flags); + + ret = __kthread_cancel_work(work); if (worker->current_work != work) goto out_fast; -- 2.30.2