From: Babis Chalios <bchalios@amazon.es>
To: Olivia Mackall <olivia@selenic.com>,
Herbert Xu <herbert@gondor.apana.org.au>,
Theodore Ts'o <tytso@mit.edu>,
"Jason A. Donenfeld" <Jason@zx2c4.com>,
"Michael S. Tsirkin" <mst@redhat.com>,
"Jason Wang" <jasowang@redhat.com>,
Xuan Zhuo <xuanzhuo@linux.alibaba.com>,
<linux-crypto@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<virtualization@lists.linux-foundation.org>
Cc: <bchalios@amazon.es>, <graf@amazon.de>, <xmarcalx@amazon.co.uk>,
<aams@amazon.de>, <dwmw@amazon.co.uk>,
<gregkh@linuxfoundation.org>
Subject: [RFC PATCH 1/2] random: emit reseed notifications for PRNGs
Date: Wed, 23 Aug 2023 11:01:05 +0200 [thread overview]
Message-ID: <20230823090107.65749-2-bchalios@amazon.es> (raw)
In-Reply-To: <20230823090107.65749-1-bchalios@amazon.es>
Sometimes, PRNGs need to reseed. For example, on a regular timer
interval, to ensure nothing consumes a random value for longer than e.g.
5 minutes, or when VMs get cloned, to ensure seeds don't leak in to
clones.
The notification happens through a 32bit epoch value that changes every
time cached entropy is no longer valid, hence PRNGs need to reseed. User
space applications can get hold of a pointer to this value through
/dev/(u)random. We introduce a new ioctl() that returns an anonymous
file descriptor. From this file descriptor we can mmap() a single page
which includes the epoch at offset 0.
random.c maintains the epoch value in a global shared page. It exposes
a registration API for kernel subsystems that are able to notify when
reseeding is needed. Notifiers register with random.c and receive a
unique 8bit ID and a pointer to the epoch. When they need to report a
reseeding event they write a new epoch value which includes the
notifier ID in the first 8 bits and an increasing counter value in the
remaining 24 bits:
RNG epoch
*-------------*---------------------*
| notifier id | epoch counter value |
*-------------*---------------------*
8 bits 24 bits
Like this, different notifiers always write different values in the
epoch.
Signed-off-by: Babis Chalios <bchalios@amazon.es>
---
drivers/char/random.c | 147 ++++++++++++++++++++++++++++++++++++
include/linux/random.h | 28 +++++++
include/uapi/linux/random.h | 11 +++
3 files changed, 186 insertions(+)
diff --git a/drivers/char/random.c b/drivers/char/random.c
index 3cb37760dfec..72b524099b60 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -54,6 +54,8 @@
#include <linux/suspend.h>
#include <linux/siphash.h>
#include <linux/sched/isolation.h>
+#include "linux/anon_inodes.h"
+#include "linux/bitmap.h"
#include <crypto/chacha.h>
#include <crypto/blake2s.h>
#include <asm/archrandom.h>
@@ -206,6 +208,7 @@ enum {
static struct {
u8 key[CHACHA_KEY_SIZE] __aligned(__alignof__(long));
unsigned long generation;
+ u32 cached_epoch;
spinlock_t lock;
} base_crng = {
.lock = __SPIN_LOCK_UNLOCKED(base_crng.lock)
@@ -242,6 +245,138 @@ static unsigned int crng_reseed_interval(void)
return CRNG_RESEED_INTERVAL;
}
+/*
+ * Tracking moments in time that PRNGs (ours and user-space) need to reseed
+ * due to an "entropy leak".
+ *
+ * We call the time period between two "entropy leak" events an "epoch".
+ * Epoch is a 32-bit unsigned value that lives in a dedicated global page.
+ * Systems that want to report entropy leaks will get an 1-byte notifier id
+ * (up to 256 notifiers) and the address of the epoch.
+ *
+ * Each notifier will write epochs in the form:
+ *
+ * 1 byte 3 bytes
+ * +---------------+-------------------------------+
+ * | notifier id | next epoch counter value |
+ * +---------------+-------------------------------+
+ *
+ * This way, epochs are namespaced per notifier, so no two different
+ * notifiers will ever write the same epoch value.
+ */
+
+static struct {
+ struct rand_epoch_data *epoch;
+ DECLARE_BITMAP(notifiers, RNG_EPOCH_NOTIFIER_NR_BITS);
+ spinlock_t lock;
+} epoch_data = {
+ .lock = __SPIN_LOCK_UNLOCKED(epoch_data.lock),
+};
+
+static int epoch_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+ if (vma->vm_pgoff || vma_pages(vma) > 1)
+ return -EINVAL;
+
+ if (vma->vm_flags & VM_WRITE)
+ return -EPERM;
+
+ /* Don't allow growing the region with mremap(). */
+ vm_flags_set(vma, VM_DONTEXPAND);
+ /* Don't allow mprotect() to make this writeable in the future */
+ vm_flags_clear(vma, VM_MAYWRITE);
+
+ return vm_insert_page(vma, vma->vm_start, virt_to_page(epoch_data.epoch));
+}
+
+static const struct file_operations rng_epoch_fops = {
+ .mmap = epoch_mmap,
+ .llseek = noop_llseek,
+};
+
+static int create_epoch_fd(void)
+{
+ unsigned long flags;
+ int ret = -ENOTTY;
+
+ spin_lock_irqsave(&epoch_data.lock, flags);
+ if (bitmap_empty(epoch_data.notifiers, RNG_EPOCH_NOTIFIER_NR_BITS))
+ goto out;
+ spin_unlock_irqrestore(&epoch_data.lock, flags);
+
+ return anon_inode_getfd("rand:epoch", &rng_epoch_fops, &epoch_data, O_RDONLY | O_CLOEXEC);
+out:
+ spin_unlock_irqrestore(&epoch_data.lock, flags);
+ return ret;
+}
+
+/*
+ * Get the current epoch. If nobody has subscribed, this will always return 0.
+ */
+static unsigned long get_epoch(void)
+{
+ u32 epoch = 0;
+
+ if (likely(epoch_data.epoch))
+ epoch = epoch_data.epoch->data;
+
+ return epoch;
+}
+
+/*
+ * Register an epoch notifier
+ *
+ * Allocate a notifier ID and provide the address to the epoch. If the address
+ * has not being allocated yet (this is the first call to register a notifier)
+ * this will allocate the page holding the epoch. If we have reached the limit
+ * of notifiers it will fail.
+ */
+int rng_register_epoch_notifier(struct rng_epoch_notifier *notifier)
+{
+ unsigned long flags;
+ u8 new_id;
+
+ if (!notifier)
+ return -EINVAL;
+
+ spin_lock_irqsave(&epoch_data.lock, flags);
+ new_id = bitmap_find_free_region(epoch_data.notifiers, RNG_EPOCH_NOTIFIER_NR_BITS, 0);
+ if (new_id < 0)
+ goto err_no_id;
+ spin_unlock_irqrestore(&epoch_data.lock, flags);
+
+ notifier->id = new_id;
+ notifier->epoch = epoch_data.epoch;
+ return 0;
+
+err_no_id:
+ spin_unlock_irqrestore(&epoch_data.lock, flags);
+ return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(rng_register_epoch_notifier);
+
+/*
+ * Unregister an epoch notifier
+ *
+ * This will release the notifier ID previously allocated through
+ * `rng_register_epoch_notifier`.
+ */
+int rng_unregister_epoch_notifier(struct rng_epoch_notifier *notifier)
+{
+ unsigned long flags;
+
+ if (!notifier)
+ return -EINVAL;
+
+ spin_lock_irqsave(&epoch_data.lock, flags);
+ bitmap_clear(epoch_data.notifiers, notifier->id, 1);
+ spin_unlock_irqrestore(&epoch_data.lock, flags);
+
+ notifier->epoch = NULL;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rng_unregister_epoch_notifier);
+
/* Used by crng_reseed() and crng_make_state() to extract a new seed from the input pool. */
static void extract_entropy(void *buf, size_t len);
@@ -344,6 +479,14 @@ static void crng_make_state(u32 chacha_state[CHACHA_STATE_WORDS],
return;
}
+ /*
+ * If the epoch has changed we reseed.
+ */
+ if (unlikely(READ_ONCE(base_crng.cached_epoch) != get_epoch())) {
+ WRITE_ONCE(base_crng.cached_epoch, get_epoch());
+ crng_reseed(NULL);
+ }
+
local_lock_irqsave(&crngs.lock, flags);
crng = raw_cpu_ptr(&crngs);
@@ -888,6 +1031,8 @@ void __init random_init(void)
_mix_pool_bytes(&entropy, sizeof(entropy));
add_latent_entropy();
+ epoch_data.epoch = (struct rand_epoch_data *)get_zeroed_page(GFP_KERNEL);
+
/*
* If we were initialized by the cpu or bootloader before jump labels
* are initialized, then we should enable the static branch here, where
@@ -1528,6 +1673,8 @@ static long random_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
return -ENODATA;
crng_reseed(NULL);
return 0;
+ case RNDEPOCH:
+ return create_epoch_fd();
default:
return -EINVAL;
}
diff --git a/include/linux/random.h b/include/linux/random.h
index b0a940af4fff..0fdacf4ee8aa 100644
--- a/include/linux/random.h
+++ b/include/linux/random.h
@@ -161,4 +161,32 @@ int random_online_cpu(unsigned int cpu);
extern const struct file_operations random_fops, urandom_fops;
#endif
+
+/*
+ * Constants that define the format of the epoch value.
+ *
+ * Currently we use a 8/24 split for epoch values. The lower 24 bits are used
+ * for the epoch counter and the 8 remaining are used for the notifier ID.
+ */
+#define RNG_EPOCH_NOTIFIER_NR_BITS 8
+#define RNG_EPOCH_COUNTER_SHIFT 0
+#define RNG_EPOCH_COUNTER_MASK GENMASK(23, 0)
+#define RNG_EPOCH_ID_SHIFT 24
+#define RNG_EPOCH_ID_MASK GENMASK(31, 24)
+
+/*
+ * An epoch notifier is a system that can report entropy leak events.
+ * Notifiers receive a unique identifier and the address where they will write
+ * a new epoch when an entropy leak happens.
+ */
+struct rng_epoch_notifier {
+ /* unique ID of the notifier */
+ u8 id;
+ /* pointer to epoch data */
+ struct rand_epoch_data *epoch;
+};
+
+int rng_register_epoch_notifier(struct rng_epoch_notifier *notifier);
+int rng_unregister_epoch_notifier(struct rng_epoch_notifier *notifier);
+
#endif /* _LINUX_RANDOM_H */
diff --git a/include/uapi/linux/random.h b/include/uapi/linux/random.h
index e744c23582eb..f79d93820bdd 100644
--- a/include/uapi/linux/random.h
+++ b/include/uapi/linux/random.h
@@ -38,6 +38,9 @@
/* Reseed CRNG. (Superuser only.) */
#define RNDRESEEDCRNG _IO( 'R', 0x07 )
+/* Get a file descriptor for the RNG generation page. */
+#define RNDEPOCH _IO('R', 0x08)
+
struct rand_pool_info {
int entropy_count;
int buf_size;
@@ -55,4 +58,12 @@ struct rand_pool_info {
#define GRND_RANDOM 0x0002
#define GRND_INSECURE 0x0004
+/*
+ * The epoch type exposed through /dev/(u)random to notify user-space
+ * PRNGs that need to re-seed
+ */
+struct rand_epoch_data {
+ __u32 data;
+};
+
#endif /* _UAPI_LINUX_RANDOM_H */
--
2.40.1
next prev parent reply other threads:[~2023-08-23 9:15 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-08-23 9:01 [RFC PATCH 0/2] Propagating reseed notifications to user space Babis Chalios
2023-08-23 9:01 ` Babis Chalios [this message]
2023-08-23 9:08 ` [RFC PATCH 1/2] random: emit reseed notifications for PRNGs Greg KH
2023-08-23 9:08 ` Greg KH
2023-08-23 9:27 ` Babis Chalios
2023-08-23 10:06 ` Greg KH
2023-08-23 10:06 ` Greg KH
[not found] ` <89ce1064-e4a3-461f-8a78-88e72e5b6419@amazon.es>
2023-08-23 10:20 ` Alexander Graf
2023-08-23 10:25 ` Greg KH
2023-08-23 10:25 ` Greg KH
2023-08-23 10:37 ` Alexander Graf
2023-08-23 9:01 ` [RFC PATCH 2/2] virtio-rng: implement entropy leak feature Babis Chalios
2023-09-04 13:44 ` [RFC PATCH 0/2] Propagating reseed notifications to user space Babis Chalios
2023-09-04 14:42 ` Jason A. Donenfeld
2023-09-04 14:54 ` Babis Chalios
2023-09-06 14:25 ` Alexander Graf
2023-09-17 13:34 ` Yann Droneaud
2023-09-18 8:32 ` Alexander Graf
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=20230823090107.65749-2-bchalios@amazon.es \
--to=bchalios@amazon.es \
--cc=Jason@zx2c4.com \
--cc=aams@amazon.de \
--cc=dwmw@amazon.co.uk \
--cc=graf@amazon.de \
--cc=gregkh@linuxfoundation.org \
--cc=herbert@gondor.apana.org.au \
--cc=jasowang@redhat.com \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mst@redhat.com \
--cc=olivia@selenic.com \
--cc=tytso@mit.edu \
--cc=virtualization@lists.linux-foundation.org \
--cc=xmarcalx@amazon.co.uk \
--cc=xuanzhuo@linux.alibaba.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.