From: Guenter Roeck <linux@roeck-us.net>
To: linux-watchdog@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, Guenter Roeck <linux@roeck-us.net>
Subject: [PATCH 6/8] watchdog: core: Cancel timer if cdev_device_add() fails
Date: Tue, 29 Sep 2026 06:46:33 -0700 [thread overview]
Message-ID: <20260929134635.2567137-7-linux@roeck-us.net> (raw)
In-Reply-To: <20260929134635.2567137-1-linux@roeck-us.net>
If misc_register() exposed the device to userspace before cdev_device_add()
is called, a concurrent watchdog_open() could start the watchdog and arm
wd_data->timer as well as the pretimeout timer. Also, if the hardware
watchdog was already running, watchdog_open() expects the device and module
references to have been acquired prior to opening.
If cdev_device_add() then fails, the error path drops the device reference
but fails to stop the watchdog, cancel the timers, and stop the worker,
leaving the watchdog active and timers armed that can later fire and
dereference freed memory. Furthermore, if a concurrent watchdog_open() saw
hw_running == true before cdev_device_add() was called, it skipped taking
its own device reference, allowing put_device() on the error path to free
wd_data while the file descriptor is still open. Similarly, when
unregistering a running watchdog that is not currently open, the extra
hw_running module and device references were never released.
Fix the problem by initializing wd_data and taking the running-watchdog
references before exposing the device via misc_register(), stopping the
watchdog and canceling both the heartbeat and pretimeout timers and
stopping the worker on registration failure, and releasing unclaimed
running-watchdog references on registration failure and unregistration.
Fixes: ee142889e32f ("watchdog: Introduce WDOG_HW_RUNNING flag")
Assisted-by: LLM
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
---
drivers/watchdog/watchdog_dev.c | 91 ++++++++++++++++++++-------------
1 file changed, 55 insertions(+), 36 deletions(-)
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index edf2cccd1c0e..31567ffbfc23 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -1047,6 +1047,7 @@ static const struct class watchdog_class = {
static int watchdog_cdev_register(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data;
+ bool hw_running;
int err;
wd_data = kzalloc_obj(struct watchdog_core_data);
@@ -1082,46 +1083,10 @@ static int watchdog_cdev_register(struct watchdog_device *wdd)
HRTIMER_MODE_REL_HARD);
watchdog_hrtimer_pretimeout_init(wdd);
- if (wdd->id == 0) {
- old_wd_data = wd_data;
- watchdog_miscdev.parent = wdd->parent;
- err = misc_register(&watchdog_miscdev);
- if (err != 0) {
- pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
- wdd->info->identity, WATCHDOG_MINOR, err);
- if (err == -EBUSY)
- pr_err("%s: a legacy watchdog module is probably present.\n",
- wdd->info->identity);
- old_wd_data = NULL;
- wdd->wd_data = NULL;
- put_device(&wd_data->dev);
- return err;
- }
- }
-
/* Fill in the data structures */
cdev_init(&wd_data->cdev, &watchdog_fops);
wd_data->cdev.owner = wdd->ops->owner;
- /* Add the device */
- err = cdev_device_add(&wd_data->cdev, &wd_data->dev);
- if (err) {
- pr_err("watchdog%d unable to add device %d:%d\n",
- wdd->id, MAJOR(watchdog_devt), wdd->id);
- if (wdd->id == 0) {
- misc_deregister(&watchdog_miscdev);
- mutex_lock(&old_wd_data_lock);
- old_wd_data = NULL;
- mutex_unlock(&old_wd_data_lock);
- }
- mutex_lock(&wd_data->lock);
- wd_data->wdd = NULL;
- wdd->wd_data = NULL;
- mutex_unlock(&wd_data->lock);
- put_device(&wd_data->dev);
- return err;
- }
-
/* Record time of most recent heartbeat as 'just before now'. */
wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1);
watchdog_set_open_deadline(wd_data);
@@ -1141,7 +1106,55 @@ static int watchdog_cdev_register(struct watchdog_device *wdd)
wdd->id);
}
+ if (wdd->id == 0) {
+ old_wd_data = wd_data;
+ watchdog_miscdev.parent = wdd->parent;
+ err = misc_register(&watchdog_miscdev);
+ if (err != 0) {
+ pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
+ wdd->info->identity, WATCHDOG_MINOR, err);
+ if (err == -EBUSY)
+ pr_err("%s: a legacy watchdog module is probably present.\n",
+ wdd->info->identity);
+ old_wd_data = NULL;
+ goto err_clear;
+ }
+ }
+
+ /* Add the device */
+ err = cdev_device_add(&wd_data->cdev, &wd_data->dev);
+ if (err) {
+ pr_err("watchdog%d unable to add device %d:%d\n",
+ wdd->id, MAJOR(watchdog_devt), wdd->id);
+ if (wdd->id == 0) {
+ misc_deregister(&watchdog_miscdev);
+ mutex_lock(&old_wd_data_lock);
+ old_wd_data = NULL;
+ mutex_unlock(&old_wd_data_lock);
+ }
+ goto err_clear;
+ }
+
return 0;
+
+err_clear:
+ mutex_lock(&wd_data->lock);
+ hw_running = watchdog_hw_running(wdd);
+ if (watchdog_active(wdd))
+ watchdog_stop(wdd);
+ watchdog_hrtimer_pretimeout_stop(wdd);
+ if (hw_running && !test_bit(_WDOG_DEV_OPEN, &wd_data->status)) {
+ module_put(wdd->ops->owner);
+ put_device(&wd_data->dev);
+ }
+ wd_data->wdd = NULL;
+ wdd->wd_data = NULL;
+ mutex_unlock(&wd_data->lock);
+
+ hrtimer_cancel(&wd_data->timer);
+ kthread_cancel_work_sync(&wd_data->work);
+ put_device(&wd_data->dev);
+ return err;
}
/**
@@ -1154,6 +1167,7 @@ static int watchdog_cdev_register(struct watchdog_device *wdd)
static void watchdog_cdev_unregister(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
+ bool hw_running;
cdev_device_del(&wd_data->cdev, &wd_data->dev);
if (wdd->id == 0) {
@@ -1164,6 +1178,7 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
}
mutex_lock(&wd_data->lock);
+ hw_running = watchdog_hw_running(wdd);
if (watchdog_active(wdd) &&
test_bit(WDOG_STOP_ON_UNREGISTER, &wdd->status)) {
watchdog_stop(wdd);
@@ -1171,6 +1186,10 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
watchdog_hrtimer_pretimeout_stop(wdd);
+ if (hw_running && !test_bit(_WDOG_DEV_OPEN, &wd_data->status)) {
+ module_put(wdd->ops->owner);
+ put_device(&wd_data->dev);
+ }
wd_data->wdd = NULL;
wdd->wd_data = NULL;
mutex_unlock(&wd_data->lock);
--
2.45.2
next prev parent reply other threads:[~2026-09-29 13:46 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 13:46 [PATCH 0/8] watchdog: core: Fix locking, lifetime, suspend, and state management bugs Guenter Roeck
2026-09-29 13:46 ` [PATCH 1/8] watchdog: core: Clear wd_data pointer on errors Guenter Roeck
2026-09-29 13:46 ` [PATCH 2/8] watchdog: core: Add missing locks Guenter Roeck
2026-09-29 13:46 ` [PATCH 3/8] watchdog: core: Prevent ping worker from re-arming timer on suspend Guenter Roeck
2026-09-29 13:46 ` [PATCH 4/8] watchdog: core: Stop pretimeout hrtimer " Guenter Roeck
2026-09-29 13:46 ` [PATCH 5/8] watchdog: core: Restore WDOG_HW_RUNNING if stopping watchdog fails Guenter Roeck
2026-09-29 13:46 ` Guenter Roeck [this message]
2026-09-29 13:46 ` [PATCH 7/8] watchdog: core: Fix unbalanced module_put() in watchdog_open() Guenter Roeck
2026-09-29 13:46 ` [PATCH 8/8] watchdog: core: Update last_keepalive in watchdog_start() Guenter Roeck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260929134635.2567137-7-linux@roeck-us.net \
--to=linux@roeck-us.net \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-watchdog@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®