From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.7 required=3.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4F677C32771 for ; Tue, 21 Jan 2020 01:31:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2E9EC22527 for ; Tue, 21 Jan 2020 01:31:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728896AbgAUBa7 (ORCPT ); Mon, 20 Jan 2020 20:30:59 -0500 Received: from lgeamrelo11.lge.com ([156.147.23.51]:47973 "EHLO lgeamrelo11.lge.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727009AbgAUBa7 (ORCPT ); Mon, 20 Jan 2020 20:30:59 -0500 X-Greylist: delayed 1799 seconds by postgrey-1.27 at vger.kernel.org; Mon, 20 Jan 2020 20:30:57 EST Received: from unknown (HELO lgeamrelo04.lge.com) (156.147.1.127) by 156.147.23.51 with ESMTP; 21 Jan 2020 10:00:55 +0900 X-Original-SENDERIP: 156.147.1.127 X-Original-MAILFROM: chanho.min@lge.com Received: from unknown (HELO localhost.localdomain) (10.178.31.96) by 156.147.1.127 with ESMTP; 21 Jan 2020 10:00:55 +0900 X-Original-SENDERIP: 10.178.31.96 X-Original-MAILFROM: chanho.min@lge.com From: Chanho Min To: "Rafael J. Wysocki" , Pavel Machek , Len Brown , Greg Kroah-Hartman Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, Daewoong Kim , Seokjoo Lee , Lee Gunho , Chanho Min Subject: [PATCH] PM / sleep: fix use-after-free on async resume Date: Tue, 21 Jan 2020 10:00:52 +0900 Message-Id: <1579568452-27253-1-git-send-email-chanho.min@lge.com> X-Mailer: git-send-email 2.7.4 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Some device can be released during suspend (e.g. usb disconnection). But, Its child device still use dev->parent's lock in dpm_wait(). It can be ocurred use-after-free as bellows. This is happened during usb resume in practice. device hierarchy: "1-1" <- "1-1:1.2" <- "ep83" device_resume("1-1:1.2") dpm_wait("1-1") device_resume("ep_83"); dpm_wait("1-1:1.2"); usb_disconnect put_device("1-1:1.2") put_device("1-1:1.2") usb_release_interface kfree(intf) <- "1-1:1.2"'s struct device is freed wait_for_common do { ... spin_lock_irq(&x->wait.lock); <- "1-1:1-2"'s lock } while (!x->done && timeout); This is call stack of the system hang caused by freed lock value in practice. Call trace: [] _raw_spin_lock_irq+0x38/0x80 [] wait_for_common+0x12c/0x140 [] wait_for_completion+0x14/0x20 [] dpm_wait+0x5c/0xb0 [] device_resume+0x78/0x320 [] async_resume+0x24/0xe0 [] async_run_entry_fn+0x54/0x158 [] process_one_work+0x1e8/0x4b0 [] worker_thread+0x128/0x4b8 [] kthread+0x10c/0x110 [] ret_from_fork+0x10/0x40 To prevent such use-after-free, dpm_wait_for_parent() keeps parent's reference using get/put_device even if it is disconnected. Signed-off-by: Chanho Min Signed-off-by: Daewoong Kim --- drivers/base/power/main.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index f946511..95a7499 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -234,13 +234,29 @@ static void initcall_debug_report(struct device *dev, ktime_t calltime, * @dev: Device to wait for. * @async: If unset, wait only if the device's power.async_suspend flag is set. */ +static void _dpm_wait(struct device *dev, bool async) +{ + if (async || (pm_async_enabled && dev->power.async_suspend)) + wait_for_completion(&dev->power.completion); +} + static void dpm_wait(struct device *dev, bool async) { if (!dev) return; - if (async || (pm_async_enabled && dev->power.async_suspend)) - wait_for_completion(&dev->power.completion); + _dpm_wait(dev, async); +} + +static void dpm_wait_for_parent(struct device *dev, bool async) +{ + if (dev && dev->parent) { + struct device *dev_p = dev->parent; + + get_device(dev_p); + _dpm_wait(dev_p, async); + put_device(dev_p); + } } static int dpm_wait_fn(struct device *dev, void *async_ptr) @@ -277,7 +293,7 @@ static void dpm_wait_for_suppliers(struct device *dev, bool async) static void dpm_wait_for_superior(struct device *dev, bool async) { - dpm_wait(dev->parent, async); + dpm_wait_for_parent(dev, async); dpm_wait_for_suppliers(dev, async); } -- 2.7.4