2022-08-31 17:21:46

by Sven van Ashbrook

[permalink] [raw]
Subject: [PATCH v1 1/2] random: make add_hwgenerator_randomness() more flexible

add_hwgenerator_randomness() currently blocks until more entropy
is needed. But, the required delay function will depend on the
the caller: e.g. freezable kthreads have their own freezable_XXX()
APIs; and delayed_work might prefer to use mod_delayed_work().

To accommodate these requirements, remove the blocking wait, and
let the function return the delay needed until more entropy is needed.

Signed-off-by: Sven van Ashbrook <[email protected]>
---

drivers/char/hw_random/core.c | 7 +++++--
drivers/char/random.c | 13 ++++++-------
include/linux/random.h | 2 +-
3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c
index 16f227b995e8..3675122c6cce 100644
--- a/drivers/char/hw_random/core.c
+++ b/drivers/char/hw_random/core.c
@@ -491,6 +491,7 @@ static int __init register_miscdev(void)
static int hwrng_fillfn(void *unused)
{
size_t entropy, entropy_credit = 0; /* in 1/1024 of a bit */
+ unsigned long delay;
long rc;

while (!kthread_should_stop()) {
@@ -526,8 +527,10 @@ static int hwrng_fillfn(void *unused)
entropy_credit = entropy;

/* Outside lock, sure, but y'know: randomness. */
- add_hwgenerator_randomness((void *)rng_fillbuf, rc,
- entropy >> 10);
+ delay = add_hwgenerator_randomness((void *)rng_fillbuf, rc,
+ entropy >> 10);
+ if (delay > 0)
+ schedule_timeout_interruptible(delay);
}
hwrng_fill = NULL;
return 0;
diff --git a/drivers/char/random.c b/drivers/char/random.c
index 79d7d4e4e582..7b6c27065cf9 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -686,7 +686,7 @@ static void __cold _credit_init_bits(size_t bits)
* the above entropy accumulation routines:
*
* void add_device_randomness(const void *buf, size_t len);
- * void add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy);
+ * unsigned long add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy);
* void add_bootloader_randomness(const void *buf, size_t len);
* void add_vmfork_randomness(const void *unique_vm_id, size_t len);
* void add_interrupt_randomness(int irq);
@@ -702,8 +702,8 @@ static void __cold _credit_init_bits(size_t bits)
* available to them (particularly common in the embedded world).
*
* add_hwgenerator_randomness() is for true hardware RNGs, and will credit
- * entropy as specified by the caller. If the entropy pool is full it will
- * block until more entropy is needed.
+ * entropy as specified by the caller. Returns number time delay in jiffies
+ * until more entropy is needed.
*
* add_bootloader_randomness() is called by bootloader drivers, such as EFI
* and device tree, and credits its input depending on whether or not the
@@ -857,10 +857,10 @@ EXPORT_SYMBOL(add_device_randomness);

/*
* Interface for in-kernel drivers of true hardware RNGs.
- * Those devices may produce endless random bits and will be throttled
+ * Those devices may produce endless random bits and should be throttled
* when our pool is full.
*/
-void add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy)
+unsigned long add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy)
{
mix_pool_bytes(buf, len);
credit_init_bits(entropy);
@@ -869,8 +869,7 @@ void add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy)
* Throttle writing to once every CRNG_RESEED_INTERVAL, unless
* we're not yet initialized.
*/
- if (!kthread_should_stop() && crng_ready())
- schedule_timeout_interruptible(CRNG_RESEED_INTERVAL);
+ return crng_ready() ? CRNG_RESEED_INTERVAL : 0;
}
EXPORT_SYMBOL_GPL(add_hwgenerator_randomness);

diff --git a/include/linux/random.h b/include/linux/random.h
index 3fec206487f6..6608b0fb4402 100644
--- a/include/linux/random.h
+++ b/include/linux/random.h
@@ -17,7 +17,7 @@ void __init add_bootloader_randomness(const void *buf, size_t len);
void add_input_randomness(unsigned int type, unsigned int code,
unsigned int value) __latent_entropy;
void add_interrupt_randomness(int irq) __latent_entropy;
-void add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy);
+unsigned long add_hwgenerator_randomness(const void *buf, size_t len, size_t entropy);

#if defined(LATENT_ENTROPY_PLUGIN) && !defined(__CHECKER__)
static inline void add_latent_entropy(void)
--
2.37.2.672.g94769d06f0-goog


2022-08-31 17:22:25

by Sven van Ashbrook

[permalink] [raw]
Subject: [PATCH v1 2/2] hwrng: core: fix potential suspend/resume race condition

The hwrng fill function runs as a normal kthread. This thread
doesn't get frozen by the PM, i.e. it will keep running during,
or in, system suspend. It may call the client driver's
data_present()/data_read() functions during, or in, suspend;
which may generate errors or warnings. For example, if the
client driver uses an i2c bus, the following warning may be
intermittently generated:

i2c: Transfer while suspended

Fix by converting the delay polled kthread into an ordered work
queue running a single, self-rearming delayed_work. Make the
workqueue WQ_FREEZABLE, so the PM will drain any work items
before going into suspend. This prevents client drivers from
being accessed during, or in, suspend.

Tested on a Chromebook containing an cr50 tpm over i2c. The test
consists of 31000 suspend/resume cycles. Occasional
"i2c: Transfer while suspended" warnings are seen. After applying
this patch, these warnings disappear.

This patch also does not appear to cause any regressions on the
ChromeOS test queues.

Signed-off-by: Sven van Ashbrook <[email protected]>
---

drivers/char/hw_random/core.c | 95 +++++++++++++++++++----------------
1 file changed, 51 insertions(+), 44 deletions(-)

diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c
index 3675122c6cce..ee85ca97d215 100644
--- a/drivers/char/hw_random/core.c
+++ b/drivers/char/hw_random/core.c
@@ -17,7 +17,7 @@
#include <linux/hw_random.h>
#include <linux/random.h>
#include <linux/kernel.h>
-#include <linux/kthread.h>
+#include <linux/workqueue.h>
#include <linux/sched/signal.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
@@ -28,14 +28,17 @@

#define RNG_MODULE_NAME "hw_random"

-static struct hwrng *current_rng;
/* the current rng has been explicitly chosen by user via sysfs */
static int cur_rng_set_by_user;
-static struct task_struct *hwrng_fill;
+static struct workqueue_struct *hwrng_wq;
+static struct delayed_work hwrng_fill_dwork;
+static size_t entropy_credit;
+/* Protects rng_list, current_rng, is_hwrng_wq_running */
+static DEFINE_MUTEX(rng_mutex);
/* list of registered rngs */
static LIST_HEAD(rng_list);
-/* Protects rng_list and current_rng */
-static DEFINE_MUTEX(rng_mutex);
+static struct hwrng *current_rng;
+static bool is_hwrng_wq_running;
/* Protects rng read functions, data_avail, rng_buffer and rng_fillbuf */
static DEFINE_MUTEX(reading_mutex);
static int data_avail;
@@ -488,37 +491,29 @@ static int __init register_miscdev(void)
return misc_register(&rng_miscdev);
}

-static int hwrng_fillfn(void *unused)
+static void hwrng_fillfn(struct work_struct *unused)
{
- size_t entropy, entropy_credit = 0; /* in 1/1024 of a bit */
+ unsigned short quality;
unsigned long delay;
+ struct hwrng *rng;
+ size_t entropy; /* in 1/1024 of a bit */
long rc;

- while (!kthread_should_stop()) {
- unsigned short quality;
- struct hwrng *rng;
-
- rng = get_current_rng();
- if (IS_ERR(rng) || !rng)
- break;
- mutex_lock(&reading_mutex);
- rc = rng_get_data(rng, rng_fillbuf,
- rng_buffer_size(), 1);
- if (current_quality != rng->quality)
- rng->quality = current_quality; /* obsolete */
- quality = rng->quality;
- mutex_unlock(&reading_mutex);
- put_rng(rng);
-
- if (!quality)
- break;
+ rng = get_current_rng();
+ if (IS_ERR(rng) || !rng)
+ return;
+ mutex_lock(&reading_mutex);
+ rc = rng_get_data(rng, rng_fillbuf, rng_buffer_size(), 1);
+ if (current_quality != rng->quality)
+ rng->quality = current_quality; /* obsolete */
+ quality = rng->quality;
+ mutex_unlock(&reading_mutex);
+ put_rng(rng);

- if (rc <= 0) {
- pr_warn("hwrng: no data available\n");
- msleep_interruptible(10000);
- continue;
- }
+ if (!quality)
+ return;

+ if (rc > 0) {
/* If we cannot credit at least one bit of entropy,
* keep track of the remainder for the next iteration
*/
@@ -529,11 +524,11 @@ static int hwrng_fillfn(void *unused)
/* Outside lock, sure, but y'know: randomness. */
delay = add_hwgenerator_randomness((void *)rng_fillbuf, rc,
entropy >> 10);
- if (delay > 0)
- schedule_timeout_interruptible(delay);
+ } else {
+ pr_warn("hwrng: no data available\n");
+ delay = 10 * HZ;
}
- hwrng_fill = NULL;
- return 0;
+ mod_delayed_work(hwrng_wq, &hwrng_fill_dwork, delay);
}

static void hwrng_manage_rngd(struct hwrng *rng)
@@ -541,14 +536,12 @@ static void hwrng_manage_rngd(struct hwrng *rng)
if (WARN_ON(!mutex_is_locked(&rng_mutex)))
return;

- if (rng->quality == 0 && hwrng_fill)
- kthread_stop(hwrng_fill);
- if (rng->quality > 0 && !hwrng_fill) {
- hwrng_fill = kthread_run(hwrng_fillfn, NULL, "hwrng");
- if (IS_ERR(hwrng_fill)) {
- pr_err("hwrng_fill thread creation failed\n");
- hwrng_fill = NULL;
- }
+ if (rng->quality == 0 && is_hwrng_wq_running) {
+ cancel_delayed_work(&hwrng_fill_dwork);
+ is_hwrng_wq_running = false;
+ } else if (rng->quality > 0 && !is_hwrng_wq_running) {
+ mod_delayed_work(hwrng_wq, &hwrng_fill_dwork, 0);
+ is_hwrng_wq_running = true;
}
}

@@ -631,8 +624,7 @@ void hwrng_unregister(struct hwrng *rng)
new_rng = get_current_rng_nolock();
if (list_empty(&rng_list)) {
mutex_unlock(&rng_mutex);
- if (hwrng_fill)
- kthread_stop(hwrng_fill);
+ cancel_delayed_work_sync(&hwrng_fill_dwork);
} else
mutex_unlock(&rng_mutex);

@@ -703,17 +695,32 @@ static int __init hwrng_modinit(void)
return -ENOMEM;
}

+ /* ordered wq to mimic delay-polled kthread behaviour */
+ hwrng_wq = alloc_ordered_workqueue("hwrng",
+ WQ_FREEZABLE | /* prevent work from running during suspend/resume */
+ WQ_MEM_RECLAIM /* client drivers may need memory reclaim */
+ );
+ if (!hwrng_wq) {
+ kfree(rng_fillbuf);
+ kfree(rng_buffer);
+ return -ENOMEM;
+ }
+
ret = register_miscdev();
if (ret) {
+ destroy_workqueue(hwrng_wq);
kfree(rng_fillbuf);
kfree(rng_buffer);
}

+ INIT_DELAYED_WORK(&hwrng_fill_dwork, hwrng_fillfn);
+
return ret;
}

static void __exit hwrng_modexit(void)
{
+ destroy_workqueue(hwrng_wq);
mutex_lock(&rng_mutex);
BUG_ON(current_rng);
kfree(rng_buffer);
--
2.37.2.672.g94769d06f0-goog

2022-09-01 05:12:57

by Dominik Brodowski

[permalink] [raw]
Subject: Re: [PATCH v1 1/2] random: make add_hwgenerator_randomness() more flexible


Am Wed, Aug 31, 2022 at 05:20:23PM +0000 schrieb Sven van Ashbrook:
> add_hwgenerator_randomness() currently blocks until more entropy
> is needed. But, the required delay function will depend on the
> the caller: e.g. freezable kthreads have their own freezable_XXX()
> APIs; and delayed_work might prefer to use mod_delayed_work().
>
> To accommodate these requirements, remove the blocking wait, and
> let the function return the delay needed until more entropy is needed.

AFAICS, there's only one caller in the kernel, and its specific requirements
are currently met by the callee. So the rationale for this patch is wanting,
yet you may wish to justify this patch more explicitly as a preparation for
the second patch.

Thanks,
Dominik

2022-09-07 06:35:45

by Herbert Xu

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] hwrng: core: fix potential suspend/resume race condition

On Wed, Aug 31, 2022 at 05:20:24PM +0000, Sven van Ashbrook wrote:
> The hwrng fill function runs as a normal kthread. This thread
> doesn't get frozen by the PM, i.e. it will keep running during,
> or in, system suspend. It may call the client driver's
> data_present()/data_read() functions during, or in, suspend;
> which may generate errors or warnings. For example, if the
> client driver uses an i2c bus, the following warning may be
> intermittently generated:
>
> i2c: Transfer while suspended
>
> Fix by converting the delay polled kthread into an ordered work
> queue running a single, self-rearming delayed_work. Make the
> workqueue WQ_FREEZABLE, so the PM will drain any work items
> before going into suspend. This prevents client drivers from
> being accessed during, or in, suspend.
>
> Tested on a Chromebook containing an cr50 tpm over i2c. The test
> consists of 31000 suspend/resume cycles. Occasional
> "i2c: Transfer while suspended" warnings are seen. After applying
> this patch, these warnings disappear.
>
> This patch also does not appear to cause any regressions on the
> ChromeOS test queues.
>
> Signed-off-by: Sven van Ashbrook <[email protected]>

The general concept seems to be fine.

> - if (rc <= 0) {
> - pr_warn("hwrng: no data available\n");
> - msleep_interruptible(10000);
> - continue;
> - }
> + if (!quality)
> + return;
>
> + if (rc > 0) {
> /* If we cannot credit at least one bit of entropy,
> * keep track of the remainder for the next iteration
> */

You need to refresh your patch-set against the latest tree. This
function has changed quite a bit.


> @@ -541,14 +536,12 @@ static void hwrng_manage_rngd(struct hwrng *rng)
> if (WARN_ON(!mutex_is_locked(&rng_mutex)))
> return;
>
> - if (rng->quality == 0 && hwrng_fill)
> - kthread_stop(hwrng_fill);
> - if (rng->quality > 0 && !hwrng_fill) {
> - hwrng_fill = kthread_run(hwrng_fillfn, NULL, "hwrng");
> - if (IS_ERR(hwrng_fill)) {
> - pr_err("hwrng_fill thread creation failed\n");
> - hwrng_fill = NULL;
> - }
> + if (rng->quality == 0 && is_hwrng_wq_running) {
> + cancel_delayed_work(&hwrng_fill_dwork);
> + is_hwrng_wq_running = false;
> + } else if (rng->quality > 0 && !is_hwrng_wq_running) {
> + mod_delayed_work(hwrng_wq, &hwrng_fill_dwork, 0);
> + is_hwrng_wq_running = true;
> }
> }
>
> @@ -631,8 +624,7 @@ void hwrng_unregister(struct hwrng *rng)
> new_rng = get_current_rng_nolock();
> if (list_empty(&rng_list)) {
> mutex_unlock(&rng_mutex);
> - if (hwrng_fill)
> - kthread_stop(hwrng_fill);
> + cancel_delayed_work_sync(&hwrng_fill_dwork);

What if hwrng_manage_rngd races against hwrng_unregister?

Thanks,
--
Email: Herbert Xu <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

2022-09-07 14:56:28

by Sven van Ashbrook

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] hwrng: core: fix potential suspend/resume race condition

Hi Herbert,

On Wed, Sep 7, 2022 at 2:29 AM Herbert Xu <[email protected]> wrote:
>
> The general concept seems to be fine.

Thank you kindly for the fast review. I plan to get a v2 out in the
next few days.