From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EEA72295538; Tue, 29 Apr 2025 23:51:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745970713; cv=none; b=WNowypYDkbYuShkqaWlEoMHB8hhIVpBAlkoskUoYJQtVctG79zhvQ+xJvkj9yJJRQ3DA24cxyB6kGppD+dPZounq6BW5LjardZ8TpziqpFQSSWSOzZpkEoFp7BLqvLutdZjDXQn+CRE3plX8OPiVTJY2fVaCOwY6U6ZkEoR7JR8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745970713; c=relaxed/simple; bh=dHkRPW5Z5/bXUSdMfreg6ZfXdRtD44Fwx1t8rwNb9+c=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=sLFcDzV3RlzaVKFfKfElZIeRdfuMnLrtW8XQ2K5aFYv8PlS4+BtVvQ9Bh1TKK+/tMIy4he/8b7dlLdsbSAjz36VEaHJUthJbp2Cc92scuEc5WJKpAOU/fiV6LUnR0Xw1MgiAbdEzfa/dpn5BTEbM95LZFiEt1ifogswl155y/wM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=KSYrf2ht; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="KSYrf2ht" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7B71CC4CEE3; Tue, 29 Apr 2025 23:51:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1745970712; bh=dHkRPW5Z5/bXUSdMfreg6ZfXdRtD44Fwx1t8rwNb9+c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KSYrf2htnNQQcz14V0pV3lzFrrhjTKIgBSD/P/aLVa0tjJeAxfJ6PrSDLioM0xt+a w5d6wBXBcdw3IieMPSFqJ0S19wOzj2F14GGkp6xKAo/aQ8UDjxVhqE4Dqo3h9rvXCf eavMgpIIffcc6TrfF0lcM3el+5YVx6bMv4w1bHRtE+R2AExCIMOeQJ8B/eGEFg+45e f6vAnoz4JpZ468uxbHJzEDeXly6fwwZU4PqQgH3h9lGjBZWoZUdXCUW1DiWa2gifu7 psPUxmJqeyW+PpXROSOdcCEYEAGtb7mp9TgGuIJ1LWfSRy4L/OOztxNYxm00+fzXpr /EqPkWtZdKrkw== From: Sasha Levin To: linux-kernel@vger.kernel.org, stable@vger.kernel.org Cc: Dmitry Torokhov , Masami Hiramatsu , Greg Kroah-Hartman , Sasha Levin , rafael@kernel.org, dakr@kernel.org Subject: [PATCH AUTOSEL 6.12 15/37] driver core: fix potential NULL pointer dereference in dev_uevent() Date: Tue, 29 Apr 2025 19:51:00 -0400 Message-Id: <20250429235122.537321-15-sashal@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250429235122.537321-1-sashal@kernel.org> References: <20250429235122.537321-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-stable: review X-Patchwork-Hint: Ignore X-stable-base: Linux 6.12.25 Content-Transfer-Encoding: 8bit From: Dmitry Torokhov [ Upstream commit 18daa52418e7e4629ed1703b64777294209d2622 ] If userspace reads "uevent" device attribute at the same time as another threads unbinds the device from its driver, change to dev->driver from a valid pointer to NULL may result in crash. Fix this by using READ_ONCE() when fetching the pointer, and take bus' drivers klist lock to make sure driver instance will not disappear while we access it. Use WRITE_ONCE() when setting the driver pointer to ensure there is no tearing. Signed-off-by: Dmitry Torokhov Reviewed-by: Masami Hiramatsu (Google) Link: https://lore.kernel.org/r/20250311052417.1846985-3-dmitry.torokhov@gmail.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/base/base.h | 13 ++++++++++++- drivers/base/bus.c | 2 +- drivers/base/core.c | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index b1f20ee5dc298..c4ffd09950433 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -73,6 +73,7 @@ static inline void subsys_put(struct subsys_private *sp) kset_put(&sp->subsys); } +struct subsys_private *bus_to_subsys(const struct bus_type *bus); struct subsys_private *class_to_subsys(const struct class *class); struct driver_private { @@ -181,8 +182,18 @@ void device_driver_detach(struct device *dev); static inline void device_set_driver(struct device *dev, const struct device_driver *drv) { + /* + * Majority (all?) read accesses to dev->driver happens either + * while holding device lock or in bus/driver code that is only + * invoked when the device is bound to a driver and there is no + * concern of the pointer being changed while it is being read. + * However when reading device's uevent file we read driver pointer + * without taking device lock (so we do not block there for + * arbitrary amount of time). We use WRITE_ONCE() here to prevent + * tearing so that READ_ONCE() can safely be used in uevent code. + */ // FIXME - this cast should not be needed "soon" - dev->driver = (struct device_driver *)drv; + WRITE_ONCE(dev->driver, (struct device_driver *)drv); } int devres_release_all(struct device *dev); diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 657c93c38b0dc..eaf38a6f6091c 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -57,7 +57,7 @@ static int __must_check bus_rescan_devices_helper(struct device *dev, * NULL. A call to subsys_put() must be done when finished with the pointer in * order for it to be properly freed. */ -static struct subsys_private *bus_to_subsys(const struct bus_type *bus) +struct subsys_private *bus_to_subsys(const struct bus_type *bus) { struct subsys_private *sp = NULL; struct kobject *kobj; diff --git a/drivers/base/core.c b/drivers/base/core.c index 97f44f0f3b4b2..15a4ff617a63e 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -2624,6 +2624,35 @@ static const char *dev_uevent_name(const struct kobject *kobj) return NULL; } +/* + * Try filling "DRIVER=" uevent variable for a device. Because this + * function may race with binding and unbinding the device from a driver, + * we need to be careful. Binding is generally safe, at worst we miss the + * fact that the device is already bound to a driver (but the driver + * information that is delivered through uevents is best-effort, it may + * become obsolete as soon as it is generated anyways). Unbinding is more + * risky as driver pointer is transitioning to NULL, so READ_ONCE() should + * be used to make sure we are dealing with the same pointer, and to + * ensure that driver structure is not going to disappear from under us + * we take bus' drivers klist lock. The assumption that only registered + * driver can be bound to a device, and to unregister a driver bus code + * will take the same lock. + */ +static void dev_driver_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + struct subsys_private *sp = bus_to_subsys(dev->bus); + + if (sp) { + scoped_guard(spinlock, &sp->klist_drivers.k_lock) { + struct device_driver *drv = READ_ONCE(dev->driver); + if (drv) + add_uevent_var(env, "DRIVER=%s", drv->name); + } + + subsys_put(sp); + } +} + static int dev_uevent(const struct kobject *kobj, struct kobj_uevent_env *env) { const struct device *dev = kobj_to_dev(kobj); @@ -2655,8 +2684,8 @@ static int dev_uevent(const struct kobject *kobj, struct kobj_uevent_env *env) if (dev->type && dev->type->name) add_uevent_var(env, "DEVTYPE=%s", dev->type->name); - if (dev->driver) - add_uevent_var(env, "DRIVER=%s", dev->driver->name); + /* Add "DRIVER=%s" variable if the device is bound to a driver */ + dev_driver_uevent(dev, env); /* Add common DT information about the device */ of_device_uevent(dev, env); -- 2.39.5