]> git.itanic.dy.fi Git - linux-stable/commitdiff
hwrng: core - let sleep be interrupted when unregistering hwrng
authorJason A. Donenfeld <Jason@zx2c4.com>
Thu, 28 Jul 2022 10:22:20 +0000 (18:22 +0800)
committerHerbert Xu <herbert@gondor.apana.org.au>
Fri, 19 Aug 2022 10:39:34 +0000 (18:39 +0800)
There are two deadlock scenarios that need addressing, which cause
problems when the computer goes to sleep, the interface is set down, and
hwrng_unregister() is called. When the deadlock is hit, sleep is delayed
for tens of seconds, causing it to fail. These scenarios are:

1) The hwrng kthread can't be stopped while it's sleeping, because it
   uses msleep_interruptible() which does not react to kthread_stop.

2) A normal user thread can't be interrupted by hwrng_unregister() while
   it's sleeping, because hwrng_unregister() is called from elsewhere.

We solve both issues by add a completion object called dying that
fulfils waiters once we have started the process in hwrng_unregister.

At the same time, we should cleanup a common and useless dmesg splat
in the same area.

Cc: <stable@vger.kernel.org>
Reported-by: Gregory Erwin <gregerwin256@gmail.com>
Fixes: fcd09c90c3c5 ("ath9k: use hw_random API instead of directly dumping into random.c")
Link: https://lore.kernel.org/all/CAO+Okf6ZJC5-nTE_EJUGQtd8JiCkiEHytGgDsFGTEjs0c00giw@mail.gmail.com/
Link: https://lore.kernel.org/lkml/CAO+Okf5k+C+SE6pMVfPf-d8MfVPVq4PO7EY8Hys_DVXtent3HA@mail.gmail.com/
Link: https://bugs.archlinux.org/task/75138
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Acked-by: Toke Høiland-Jørgensen <toke@toke.dk>
Acked-by: Kalle Valo <kvalo@kernel.org>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
drivers/char/hw_random/core.c
drivers/net/wireless/ath/ath9k/rng.c
include/linux/hw_random.h

index 16f227b995e8a536897f24c44ac841a03fb91a8e..d7045dfaf16cf52c6738c2f4928518bcc9d8796d 100644 (file)
@@ -507,16 +507,17 @@ static int hwrng_fillfn(void *unused)
                        rng->quality = current_quality; /* obsolete */
                quality = rng->quality;
                mutex_unlock(&reading_mutex);
+
+               if (rc <= 0)
+                       hwrng_msleep(rng, 10000);
+
                put_rng(rng);
 
                if (!quality)
                        break;
 
-               if (rc <= 0) {
-                       pr_warn("hwrng: no data available\n");
-                       msleep_interruptible(10000);
+               if (rc <= 0)
                        continue;
-               }
 
                /* If we cannot credit at least one bit of entropy,
                 * keep track of the remainder for the next iteration
@@ -570,6 +571,7 @@ int hwrng_register(struct hwrng *rng)
 
        init_completion(&rng->cleanup_done);
        complete(&rng->cleanup_done);
+       init_completion(&rng->dying);
 
        if (!current_rng ||
            (!cur_rng_set_by_user && rng->quality > current_rng->quality)) {
@@ -617,6 +619,7 @@ void hwrng_unregister(struct hwrng *rng)
 
        old_rng = current_rng;
        list_del(&rng->list);
+       complete_all(&rng->dying);
        if (current_rng == rng) {
                err = enable_best_rng();
                if (err) {
@@ -685,6 +688,14 @@ void devm_hwrng_unregister(struct device *dev, struct hwrng *rng)
 }
 EXPORT_SYMBOL_GPL(devm_hwrng_unregister);
 
+long hwrng_msleep(struct hwrng *rng, unsigned int msecs)
+{
+       unsigned long timeout = msecs_to_jiffies(msecs) + 1;
+
+       return wait_for_completion_interruptible_timeout(&rng->dying, timeout);
+}
+EXPORT_SYMBOL_GPL(hwrng_msleep);
+
 static int __init hwrng_modinit(void)
 {
        int ret;
index cb5414265a9b53577f33c02edbb3a935f0321921..58c0ab01771b0483182fe6d77d47a944f83e4cd5 100644 (file)
@@ -83,7 +83,8 @@ static int ath9k_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
                if (!wait || !max || likely(bytes_read) || fail_stats > 110)
                        break;
 
-               msleep_interruptible(ath9k_rng_delay_get(++fail_stats));
+               if (hwrng_msleep(rng, ath9k_rng_delay_get(++fail_stats)))
+                       break;
        }
 
        if (wait && !bytes_read && max)
index aa1d4da03538bf4205ee0e6f184e01c75183d62a..77c2885c4c13027f5125ec5f2e3644b28a5adb8c 100644 (file)
@@ -50,6 +50,7 @@ struct hwrng {
        struct list_head list;
        struct kref ref;
        struct completion cleanup_done;
+       struct completion dying;
 };
 
 struct device;
@@ -61,4 +62,6 @@ extern int devm_hwrng_register(struct device *dev, struct hwrng *rng);
 extern void hwrng_unregister(struct hwrng *rng);
 extern void devm_hwrng_unregister(struct device *dve, struct hwrng *rng);
 
+extern long hwrng_msleep(struct hwrng *rng, unsigned int msecs);
+
 #endif /* LINUX_HWRANDOM_H_ */