hwrng: use reference counts on each struct hwrng.
current_rng holds one reference, and we bump it every time we want to do a read from it. This means we only hold the rng_mutex to grab or drop a reference, so accessing /sys/devices/virtual/misc/hw_random/rng_current doesn't block on read of /dev/hwrng. Using a kref is overkill (we're always under the rng_mutex), but a standard pattern. This also solves the problem that the hwrng_fillfn thread was accessing current_rng without a lock, which could change (eg. to NULL) underneath it. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Amos Kong <akong@redhat.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
parent
1dacb395d6
commit
3a2c0ba5ad
|
@ -42,6 +42,7 @@
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/random.h>
|
#include <linux/random.h>
|
||||||
|
#include <linux/err.h>
|
||||||
#include <asm/uaccess.h>
|
#include <asm/uaccess.h>
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,6 +92,60 @@ static void add_early_randomness(struct hwrng *rng)
|
||||||
add_device_randomness(bytes, bytes_read);
|
add_device_randomness(bytes, bytes_read);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void cleanup_rng(struct kref *kref)
|
||||||
|
{
|
||||||
|
struct hwrng *rng = container_of(kref, struct hwrng, ref);
|
||||||
|
|
||||||
|
if (rng->cleanup)
|
||||||
|
rng->cleanup(rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_current_rng(struct hwrng *rng)
|
||||||
|
{
|
||||||
|
BUG_ON(!mutex_is_locked(&rng_mutex));
|
||||||
|
kref_get(&rng->ref);
|
||||||
|
current_rng = rng;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drop_current_rng(void)
|
||||||
|
{
|
||||||
|
BUG_ON(!mutex_is_locked(&rng_mutex));
|
||||||
|
if (!current_rng)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* decrease last reference for triggering the cleanup */
|
||||||
|
kref_put(¤t_rng->ref, cleanup_rng);
|
||||||
|
current_rng = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns ERR_PTR(), NULL or refcounted hwrng */
|
||||||
|
static struct hwrng *get_current_rng(void)
|
||||||
|
{
|
||||||
|
struct hwrng *rng;
|
||||||
|
|
||||||
|
if (mutex_lock_interruptible(&rng_mutex))
|
||||||
|
return ERR_PTR(-ERESTARTSYS);
|
||||||
|
|
||||||
|
rng = current_rng;
|
||||||
|
if (rng)
|
||||||
|
kref_get(&rng->ref);
|
||||||
|
|
||||||
|
mutex_unlock(&rng_mutex);
|
||||||
|
return rng;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_rng(struct hwrng *rng)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Hold rng_mutex here so we serialize in case they set_current_rng
|
||||||
|
* on rng again immediately.
|
||||||
|
*/
|
||||||
|
mutex_lock(&rng_mutex);
|
||||||
|
if (rng)
|
||||||
|
kref_put(&rng->ref, cleanup_rng);
|
||||||
|
mutex_unlock(&rng_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int hwrng_init(struct hwrng *rng)
|
static inline int hwrng_init(struct hwrng *rng)
|
||||||
{
|
{
|
||||||
if (rng->init) {
|
if (rng->init) {
|
||||||
|
@ -113,12 +168,6 @@ static inline int hwrng_init(struct hwrng *rng)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void hwrng_cleanup(struct hwrng *rng)
|
|
||||||
{
|
|
||||||
if (rng && rng->cleanup)
|
|
||||||
rng->cleanup(rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int rng_dev_open(struct inode *inode, struct file *filp)
|
static int rng_dev_open(struct inode *inode, struct file *filp)
|
||||||
{
|
{
|
||||||
/* enforce read-only access to this chrdev */
|
/* enforce read-only access to this chrdev */
|
||||||
|
@ -154,21 +203,22 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
|
||||||
ssize_t ret = 0;
|
ssize_t ret = 0;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
int bytes_read, len;
|
int bytes_read, len;
|
||||||
|
struct hwrng *rng;
|
||||||
|
|
||||||
while (size) {
|
while (size) {
|
||||||
if (mutex_lock_interruptible(&rng_mutex)) {
|
rng = get_current_rng();
|
||||||
err = -ERESTARTSYS;
|
if (IS_ERR(rng)) {
|
||||||
|
err = PTR_ERR(rng);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
if (!rng) {
|
||||||
if (!current_rng) {
|
|
||||||
err = -ENODEV;
|
err = -ENODEV;
|
||||||
goto out_unlock;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&reading_mutex);
|
mutex_lock(&reading_mutex);
|
||||||
if (!data_avail) {
|
if (!data_avail) {
|
||||||
bytes_read = rng_get_data(current_rng, rng_buffer,
|
bytes_read = rng_get_data(rng, rng_buffer,
|
||||||
rng_buffer_size(),
|
rng_buffer_size(),
|
||||||
!(filp->f_flags & O_NONBLOCK));
|
!(filp->f_flags & O_NONBLOCK));
|
||||||
if (bytes_read < 0) {
|
if (bytes_read < 0) {
|
||||||
|
@ -200,8 +250,8 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
|
||||||
ret += len;
|
ret += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&rng_mutex);
|
|
||||||
mutex_unlock(&reading_mutex);
|
mutex_unlock(&reading_mutex);
|
||||||
|
put_rng(rng);
|
||||||
|
|
||||||
if (need_resched())
|
if (need_resched())
|
||||||
schedule_timeout_interruptible(1);
|
schedule_timeout_interruptible(1);
|
||||||
|
@ -213,12 +263,11 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
|
||||||
}
|
}
|
||||||
out:
|
out:
|
||||||
return ret ? : err;
|
return ret ? : err;
|
||||||
out_unlock:
|
|
||||||
mutex_unlock(&rng_mutex);
|
|
||||||
goto out;
|
|
||||||
out_unlock_reading:
|
out_unlock_reading:
|
||||||
mutex_unlock(&reading_mutex);
|
mutex_unlock(&reading_mutex);
|
||||||
goto out_unlock;
|
put_rng(rng);
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -257,8 +306,8 @@ static ssize_t hwrng_attr_current_store(struct device *dev,
|
||||||
err = hwrng_init(rng);
|
err = hwrng_init(rng);
|
||||||
if (err)
|
if (err)
|
||||||
break;
|
break;
|
||||||
hwrng_cleanup(current_rng);
|
drop_current_rng();
|
||||||
current_rng = rng;
|
set_current_rng(rng);
|
||||||
err = 0;
|
err = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -272,17 +321,15 @@ static ssize_t hwrng_attr_current_show(struct device *dev,
|
||||||
struct device_attribute *attr,
|
struct device_attribute *attr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
int err;
|
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
const char *name = "none";
|
struct hwrng *rng;
|
||||||
|
|
||||||
err = mutex_lock_interruptible(&rng_mutex);
|
rng = get_current_rng();
|
||||||
if (err)
|
if (IS_ERR(rng))
|
||||||
return -ERESTARTSYS;
|
return PTR_ERR(rng);
|
||||||
if (current_rng)
|
|
||||||
name = current_rng->name;
|
ret = snprintf(buf, PAGE_SIZE, "%s\n", rng ? rng->name : "none");
|
||||||
ret = snprintf(buf, PAGE_SIZE, "%s\n", name);
|
put_rng(rng);
|
||||||
mutex_unlock(&rng_mutex);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -353,12 +400,16 @@ static int hwrng_fillfn(void *unused)
|
||||||
long rc;
|
long rc;
|
||||||
|
|
||||||
while (!kthread_should_stop()) {
|
while (!kthread_should_stop()) {
|
||||||
if (!current_rng)
|
struct hwrng *rng;
|
||||||
|
|
||||||
|
rng = get_current_rng();
|
||||||
|
if (IS_ERR(rng) || !rng)
|
||||||
break;
|
break;
|
||||||
mutex_lock(&reading_mutex);
|
mutex_lock(&reading_mutex);
|
||||||
rc = rng_get_data(current_rng, rng_fillbuf,
|
rc = rng_get_data(rng, rng_fillbuf,
|
||||||
rng_buffer_size(), 1);
|
rng_buffer_size(), 1);
|
||||||
mutex_unlock(&reading_mutex);
|
mutex_unlock(&reading_mutex);
|
||||||
|
put_rng(rng);
|
||||||
if (rc <= 0) {
|
if (rc <= 0) {
|
||||||
pr_warn("hwrng: no data available\n");
|
pr_warn("hwrng: no data available\n");
|
||||||
msleep_interruptible(10000);
|
msleep_interruptible(10000);
|
||||||
|
@ -419,14 +470,13 @@ int hwrng_register(struct hwrng *rng)
|
||||||
err = hwrng_init(rng);
|
err = hwrng_init(rng);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
current_rng = rng;
|
set_current_rng(rng);
|
||||||
}
|
}
|
||||||
err = 0;
|
err = 0;
|
||||||
if (!old_rng) {
|
if (!old_rng) {
|
||||||
err = register_miscdev();
|
err = register_miscdev();
|
||||||
if (err) {
|
if (err) {
|
||||||
hwrng_cleanup(rng);
|
drop_current_rng();
|
||||||
current_rng = NULL;
|
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,22 +503,21 @@ EXPORT_SYMBOL_GPL(hwrng_register);
|
||||||
|
|
||||||
void hwrng_unregister(struct hwrng *rng)
|
void hwrng_unregister(struct hwrng *rng)
|
||||||
{
|
{
|
||||||
int err;
|
|
||||||
|
|
||||||
mutex_lock(&rng_mutex);
|
mutex_lock(&rng_mutex);
|
||||||
|
|
||||||
list_del(&rng->list);
|
list_del(&rng->list);
|
||||||
if (current_rng == rng) {
|
if (current_rng == rng) {
|
||||||
hwrng_cleanup(rng);
|
drop_current_rng();
|
||||||
if (list_empty(&rng_list)) {
|
if (!list_empty(&rng_list)) {
|
||||||
current_rng = NULL;
|
struct hwrng *tail;
|
||||||
} else {
|
|
||||||
current_rng = list_entry(rng_list.prev, struct hwrng, list);
|
tail = list_entry(rng_list.prev, struct hwrng, list);
|
||||||
err = hwrng_init(current_rng);
|
|
||||||
if (err)
|
if (hwrng_init(tail) == 0)
|
||||||
current_rng = NULL;
|
set_current_rng(tail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list_empty(&rng_list)) {
|
if (list_empty(&rng_list)) {
|
||||||
mutex_unlock(&rng_mutex);
|
mutex_unlock(&rng_mutex);
|
||||||
unregister_miscdev();
|
unregister_miscdev();
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
|
#include <linux/kref.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct hwrng - Hardware Random Number Generator driver
|
* struct hwrng - Hardware Random Number Generator driver
|
||||||
|
@ -44,6 +45,7 @@ struct hwrng {
|
||||||
|
|
||||||
/* internal. */
|
/* internal. */
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
|
struct kref ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Register a new Hardware Random Number Generator driver. */
|
/** Register a new Hardware Random Number Generator driver. */
|
||||||
|
|
Loading…
Reference in New Issue