public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
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


  reply	other threads:[~2023-08-23  9:16 UTC|newest]

Thread overview: 15+ 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:27     ` Babis Chalios
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: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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox