From 44fd5618cd312625394fc2cd0861b2ab44068148 Mon Sep 17 00:00:00 2001
From: Stephan Mueller <smueller@chronox.de>
Date: Mon, 20 Feb 2023 22:05:24 +0100
Subject: [PATCH 09/25] LRNG - add atomic DRNG implementation

The atomic DRNG implementation supports the in-kernel use cases which
request random numbers in atomic contexts. It uses the ChaCha20 DRNG
which is a code base that does not sleep and it provides an interface to
callers that does not sleep. This code will only be compiled when the
LRNG enables the in-kernel drop-in replacement APIs for the existing
random.c code base.

Signed-off-by: Stephan Mueller <smueller@chronox.de>
---
 drivers/char/lrng/Makefile           |   1 +
 drivers/char/lrng/lrng_drng_atomic.c | 130 +++++++++++++++++++++++++++
 2 files changed, 131 insertions(+)
 create mode 100644 drivers/char/lrng/lrng_drng_atomic.c

--- a/drivers/char/lrng/Makefile
+++ b/drivers/char/lrng/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_LRNG_HASH_KCAPI)		+= lrng_h
 obj-$(CONFIG_LRNG_DRNG_CHACHA20)	+= lrng_drng_chacha20.o
 obj-$(CONFIG_LRNG_DRBG)			+= lrng_drng_drbg.o
 obj-$(CONFIG_LRNG_DRNG_KCAPI)		+= lrng_drng_kcapi.o
+obj-$(CONFIG_LRNG_DRNG_ATOMIC)		+= lrng_drng_atomic.o
--- /dev/null
+++ b/drivers/char/lrng/lrng_drng_atomic.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * LRNG DRNG for atomic contexts
+ *
+ * Copyright (C) 2022, Stephan Mueller <smueller@chronox.de>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/lrng.h>
+
+#include "lrng_drng_atomic.h"
+#include "lrng_drng_chacha20.h"
+#include "lrng_es_aux.h"
+#include "lrng_es_mgr.h"
+#include "lrng_sha.h"
+
+static struct chacha20_state chacha20_atomic = {
+	LRNG_CC20_INIT_RFC7539(.block)
+};
+
+/*
+ * DRNG usable in atomic context. This DRNG will always use the ChaCha20
+ * DRNG. It will never benefit from a DRNG switch like the "regular" DRNG. If
+ * there was no DRNG switch, the atomic DRNG is identical to the "regular" DRNG.
+ *
+ * The reason for having this is due to the fact that DRNGs other than
+ * the ChaCha20 DRNG may sleep.
+ */
+static struct lrng_drng lrng_drng_atomic = {
+	LRNG_DRNG_STATE_INIT(lrng_drng_atomic,
+			     &chacha20_atomic, NULL,
+			     &lrng_cc20_drng_cb, &lrng_sha_hash_cb),
+	.spin_lock	= __SPIN_LOCK_UNLOCKED(lrng_drng_atomic.spin_lock)
+};
+
+struct lrng_drng *lrng_get_atomic(void)
+{
+	return &lrng_drng_atomic;
+}
+
+void lrng_drng_atomic_reset(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&lrng_drng_atomic.spin_lock, flags);
+	lrng_drng_reset(&lrng_drng_atomic);
+	spin_unlock_irqrestore(&lrng_drng_atomic.spin_lock, flags);
+}
+
+void lrng_drng_atomic_force_reseed(void)
+{
+	lrng_drng_atomic.force_reseed = lrng_drng_atomic.fully_seeded;
+}
+
+static bool lrng_drng_atomic_must_reseed(struct lrng_drng *drng)
+{
+	return (!drng->fully_seeded ||
+		atomic_read(&lrng_drng_atomic.requests) == 0 ||
+		drng->force_reseed ||
+		time_after(jiffies,
+			   drng->last_seeded + lrng_drng_reseed_max_time * HZ));
+}
+
+void lrng_drng_atomic_seed_drng(struct lrng_drng *regular_drng)
+{
+	u8 seedbuf[LRNG_DRNG_SECURITY_STRENGTH_BYTES]
+						__aligned(LRNG_KCAPI_ALIGN);
+	int ret;
+
+	if (!lrng_drng_atomic_must_reseed(&lrng_drng_atomic))
+		return;
+
+	/*
+	 * Reseed atomic DRNG another DRNG "regular" while this regular DRNG
+	 * is reseeded. Therefore, this code operates in non-atomic context and
+	 * thus can use the lrng_drng_get function to get random numbers from
+	 * the just freshly seeded DRNG.
+	 */
+	ret = lrng_drng_get(regular_drng, seedbuf, sizeof(seedbuf));
+
+	if (ret < 0) {
+		pr_warn("Error generating random numbers for atomic DRNG: %d\n",
+			ret);
+	} else {
+		unsigned long flags;
+
+		spin_lock_irqsave(&lrng_drng_atomic.spin_lock, flags);
+		lrng_drng_inject(&lrng_drng_atomic, seedbuf, ret,
+				 regular_drng->fully_seeded, "atomic");
+		spin_unlock_irqrestore(&lrng_drng_atomic.spin_lock, flags);
+	}
+	memzero_explicit(&seedbuf, sizeof(seedbuf));
+}
+
+static void lrng_drng_atomic_get(u8 *outbuf, u32 outbuflen)
+{
+	struct lrng_drng *drng = &lrng_drng_atomic;
+	unsigned long flags;
+
+	if (!outbuf || !outbuflen)
+		return;
+
+	outbuflen = min_t(size_t, outbuflen, INT_MAX);
+
+	while (outbuflen) {
+		u32 todo = min_t(u32, outbuflen, LRNG_DRNG_MAX_REQSIZE);
+		int ret;
+
+		atomic_dec(&drng->requests);
+
+		spin_lock_irqsave(&drng->spin_lock, flags);
+		ret = drng->drng_cb->drng_generate(drng->drng, outbuf, todo);
+		spin_unlock_irqrestore(&drng->spin_lock, flags);
+		if (ret <= 0) {
+			pr_warn("getting random data from DRNG failed (%d)\n",
+				ret);
+			return;
+		}
+		outbuflen -= ret;
+		outbuf += ret;
+	}
+}
+
+void lrng_get_random_bytes(void *buf, int nbytes)
+{
+	lrng_drng_atomic_get((u8 *)buf, (u32)nbytes);
+	lrng_debug_report_seedlevel("lrng_get_random_bytes");
+}
+EXPORT_SYMBOL(lrng_get_random_bytes);
