diff mbox series

[v34,04/12] LRNG - add switchable DRNG support

Message ID 2098366.irdbgypaU6@positron.chronox.de (mailing list archive)
State Not Applicable
Delegated to: Herbert Xu
Headers show
Series /dev/random - a new approach with full SP800-90B compliance | expand

Commit Message

Stephan Mueller Aug. 25, 2020, 7:24 a.m. UTC
The DRNG switch support allows replacing the DRNG mechanism of the
LRNG. The switching support rests on the interface definition of
include/linux/lrng.h. A new DRNG is implemented by filling in the
interface defined in this header file.

In addition to the DRNG, the extension also has to provide a hash
implementation that is used to hash the entropy pool for random number
extraction.

Note: It is permissible to implement a DRNG whose operations may sleep.
However, the hash function must not sleep.

The switchable DRNG support allows replacing the DRNG at runtime.
However, only one DRNG extension is allowed to be loaded at any given
time. Before replacing it with another DRNG implementation, the possibly
existing DRNG extension must be unloaded.

The switchable DRNG extension activates the new DRNG during load time.
It is expected, however, that such a DRNG switch would be done only once
by an administrator to load the intended DRNG implementation.

It is permissible to compile DRNG extensions either as kernel modules or
statically. The initialization of the DRNG extension should be performed
with a late_initcall to ensure the extension is available when user
space starts but after all other initialization completed.
The initialization is performed by registering the function call data
structure with the lrng_set_drng_cb function. In order to unload the
DRNG extension, lrng_set_drng_cb must be invoked with the NULL
parameter.

The DRNG extension should always provide a security strength that is at
least as strong as LRNG_DRNG_SECURITY_STRENGTH_BITS.

CC: "Eric W. Biederman" <ebiederm@xmission.com>
CC: "Alexander E. Patrakov" <patrakov@gmail.com>
CC: "Ahmed S. Darwish" <darwish.07@gmail.com>
CC: "Theodore Y. Ts'o" <tytso@mit.edu>
CC: Willy Tarreau <w@1wt.eu>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Vito Caputo <vcaputo@pengaru.com>
CC: Andreas Dilger <adilger.kernel@dilger.ca>
CC: Jan Kara <jack@suse.cz>
CC: Ray Strode <rstrode@redhat.com>
CC: William Jon McCann <mccann@jhu.edu>
CC: zhangjs <zachary@baishancloud.com>
CC: Andy Lutomirski <luto@kernel.org>
CC: Florian Weimer <fweimer@redhat.com>
CC: Lennart Poettering <mzxreary@0pointer.de>
CC: Nicolai Stange <nstange@suse.de>
Reviewed-by: Marcelo Henrique Cerri <marcelo.cerri@canonical.com>
Reviewed-by: Roman Drahtmueller <draht@schaltsekun.de>
Tested-by: Roman Drahtmüller <draht@schaltsekun.de>
Tested-by: Marcelo Henrique Cerri <marcelo.cerri@canonical.com>
Tested-by: Neil Horman <nhorman@redhat.com>
Signed-off-by: Stephan Mueller <smueller@chronox.de>
---
 drivers/char/lrng/Kconfig       |   7 ++
 drivers/char/lrng/Makefile      |   1 +
 drivers/char/lrng/lrng_switch.c | 193 ++++++++++++++++++++++++++++++++
 3 files changed, 201 insertions(+)
 create mode 100644 drivers/char/lrng/lrng_switch.c

Comments

kernel test robot Aug. 31, 2020, 10:03 a.m. UTC | #1
Hi "Stephan,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on char-misc/char-misc-testing]
[also build test ERROR on cryptodev/master crypto/master v5.9-rc3 next-20200828]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Stephan-M-ller/dev-random-a-new-approach-with-full-SP800-90B-compliance/20200825-153914
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git d162219c655c8cf8003128a13840d6c1e183fb80
config: riscv-allmodconfig (attached as .config)
compiler: riscv64-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=riscv 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   drivers/char/lrng/lrng_switch.c: In function 'lrng_drng_switch':
>> drivers/char/lrng/lrng_switch.c:20:2: error: variable 'seed' with 'latent_entropy' attribute must not be local
      20 |  u8 seed[LRNG_DRNG_SECURITY_STRENGTH_BYTES] __latent_entropy;
         |  ^~

# https://github.com/0day-ci/linux/commit/b4a65336bab63ba2d7b4be76a1acad8eb6b63daf
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Stephan-M-ller/dev-random-a-new-approach-with-full-SP800-90B-compliance/20200825-153914
git checkout b4a65336bab63ba2d7b4be76a1acad8eb6b63daf
vim +20 drivers/char/lrng/lrng_switch.c

    13	
    14	static int lrng_drng_switch(struct lrng_drng *drng_store,
    15				    const struct lrng_crypto_cb *cb, int node)
    16	{
    17		const struct lrng_crypto_cb *old_cb;
    18		unsigned long flags = 0;
    19		int ret;
  > 20		u8 seed[LRNG_DRNG_SECURITY_STRENGTH_BYTES] __latent_entropy;
    21		void *new_drng = cb->lrng_drng_alloc(LRNG_DRNG_SECURITY_STRENGTH_BYTES);
    22		void *old_drng, *new_hash, *old_hash;
    23		bool sl = false, reset_drng = !lrng_get_available();
    24	
    25		if (IS_ERR(new_drng)) {
    26			pr_warn("could not allocate new DRNG for NUMA node %d (%ld)\n",
    27				node, PTR_ERR(new_drng));
    28			return PTR_ERR(new_drng);
    29		}
    30	
    31		/*
    32		 * The seed potentially used as MAC key is undefined to add some
    33		 * variation. Yet, the security of the MAC does not rely on the key
    34		 * being secret. The key is only there to turn a MAC into a hash.
    35		 * The intention is to allow the specification of CMAC(AES) as "hash"
    36		 * to limit the dependency to AES when using the CTR DRBG.
    37		 */
    38		new_hash = cb->lrng_hash_alloc(seed, sizeof(seed));
    39		if (IS_ERR(new_hash)) {
    40			pr_warn("could not allocate new LRNG pool hash (%ld)\n",
    41				PTR_ERR(new_hash));
    42			cb->lrng_drng_dealloc(new_drng);
    43			return PTR_ERR(new_hash);
    44		}
    45	
    46		lrng_drng_lock(drng_store, &flags);
    47	
    48		/*
    49		 * Pull from existing DRNG to seed new DRNG regardless of seed status
    50		 * of old DRNG -- the entropy state for the DRNG is left unchanged which
    51		 * implies that als the new DRNG is reseeded when deemed necessary. This
    52		 * seeding of the new DRNG shall only ensure that the new DRNG has the
    53		 * same entropy as the old DRNG.
    54		 */
    55		ret = drng_store->crypto_cb->lrng_drng_generate_helper(
    56					drng_store->drng, seed, sizeof(seed));
    57		lrng_drng_unlock(drng_store, &flags);
    58	
    59		if (ret < 0) {
    60			reset_drng = true;
    61			pr_warn("getting random data from DRNG failed for NUMA node %d (%d)\n",
    62				node, ret);
    63		} else {
    64			/* seed new DRNG with data */
    65			ret = cb->lrng_drng_seed_helper(new_drng, seed, ret);
    66			if (ret < 0) {
    67				reset_drng = true;
    68				pr_warn("seeding of new DRNG failed for NUMA node %d (%d)\n",
    69					node, ret);
    70			} else {
    71				pr_debug("seeded new DRNG of NUMA node %d instance from old DRNG instance\n",
    72					 node);
    73			}
    74		}
    75	
    76		mutex_lock(&drng_store->lock);
    77		/*
    78		 * If we switch the DRNG from the initial ChaCha20 DRNG to something
    79		 * else, there is a lock transition from spin lock to mutex (see
    80		 * lrng_drng_is_atomic and how the lock is taken in lrng_drng_lock).
    81		 * Thus, we need to take both locks during the transition phase.
    82		 */
    83		if (lrng_drng_is_atomic(drng_store)) {
    84			spin_lock_irqsave(&drng_store->spin_lock, flags);
    85			sl = true;
    86		} else {
    87			__acquire(&drng_store->spin_lock);
    88		}
    89	
    90		if (reset_drng)
    91			lrng_drng_reset(drng_store);
    92	
    93		old_drng = drng_store->drng;
    94		old_cb = drng_store->crypto_cb;
    95		drng_store->drng = new_drng;
    96		drng_store->crypto_cb = cb;
    97	
    98		old_hash = drng_store->hash;
    99		drng_store->hash = new_hash;
   100		pr_info("Entropy pool read-hash allocated for DRNG for NUMA node %d\n",
   101			node);
   102	
   103		if (sl)
   104			spin_unlock_irqrestore(&drng_store->spin_lock, flags);
   105		else
   106			__release(&drng_store->spin_lock);
   107		mutex_unlock(&drng_store->lock);
   108	
   109		/* ChaCha20 serves as atomic instance left untouched. */
   110		if (old_drng != &chacha20) {
   111			old_cb->lrng_drng_dealloc(old_drng);
   112			old_cb->lrng_hash_dealloc(old_hash);
   113		}
   114	
   115		pr_info("DRNG of NUMA node %d switched\n", node);
   116	
   117		return 0;
   118	}
   119	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/drivers/char/lrng/Kconfig b/drivers/char/lrng/Kconfig
index 059b21d9728c..d3138458fd8b 100644
--- a/drivers/char/lrng/Kconfig
+++ b/drivers/char/lrng/Kconfig
@@ -65,4 +65,11 @@  config LRNG_POOL_SIZE
 	default 7 if LRNG_POOL_SIZE_65536
 	default 8 if LRNG_POOL_SIZE_131072
 
+menuconfig LRNG_DRNG_SWITCH
+	bool "Support DRNG runtime switching"
+	help
+	  The Linux RNG per default uses a ChaCha20 DRNG that is
+	  accessible via the external interfaces. With this configuration
+	  option other DRNGs can be selected and loaded at runtime.
+
 endif # LRNG
diff --git a/drivers/char/lrng/Makefile b/drivers/char/lrng/Makefile
index e69c176f0161..31cfe87c999e 100644
--- a/drivers/char/lrng/Makefile
+++ b/drivers/char/lrng/Makefile
@@ -10,3 +10,4 @@  obj-y				+= lrng_pool.o lrng_aux.o \
 
 obj-$(CONFIG_NUMA)		+= lrng_numa.o
 obj-$(CONFIG_SYSCTL)		+= lrng_proc.o
+obj-$(CONFIG_LRNG_DRNG_SWITCH)	+= lrng_switch.o
diff --git a/drivers/char/lrng/lrng_switch.c b/drivers/char/lrng/lrng_switch.c
new file mode 100644
index 000000000000..9b40671c6375
--- /dev/null
+++ b/drivers/char/lrng/lrng_switch.c
@@ -0,0 +1,193 @@ 
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * LRNG DRNG switching support
+ *
+ * Copyright (C) 2016 - 2020, Stephan Mueller <smueller@chronox.de>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/lrng.h>
+
+#include "lrng_internal.h"
+
+static int lrng_drng_switch(struct lrng_drng *drng_store,
+			    const struct lrng_crypto_cb *cb, int node)
+{
+	const struct lrng_crypto_cb *old_cb;
+	unsigned long flags = 0;
+	int ret;
+	u8 seed[LRNG_DRNG_SECURITY_STRENGTH_BYTES] __latent_entropy;
+	void *new_drng = cb->lrng_drng_alloc(LRNG_DRNG_SECURITY_STRENGTH_BYTES);
+	void *old_drng, *new_hash, *old_hash;
+	bool sl = false, reset_drng = !lrng_get_available();
+
+	if (IS_ERR(new_drng)) {
+		pr_warn("could not allocate new DRNG for NUMA node %d (%ld)\n",
+			node, PTR_ERR(new_drng));
+		return PTR_ERR(new_drng);
+	}
+
+	/*
+	 * The seed potentially used as MAC key is undefined to add some
+	 * variation. Yet, the security of the MAC does not rely on the key
+	 * being secret. The key is only there to turn a MAC into a hash.
+	 * The intention is to allow the specification of CMAC(AES) as "hash"
+	 * to limit the dependency to AES when using the CTR DRBG.
+	 */
+	new_hash = cb->lrng_hash_alloc(seed, sizeof(seed));
+	if (IS_ERR(new_hash)) {
+		pr_warn("could not allocate new LRNG pool hash (%ld)\n",
+			PTR_ERR(new_hash));
+		cb->lrng_drng_dealloc(new_drng);
+		return PTR_ERR(new_hash);
+	}
+
+	lrng_drng_lock(drng_store, &flags);
+
+	/*
+	 * Pull from existing DRNG to seed new DRNG regardless of seed status
+	 * of old DRNG -- the entropy state for the DRNG is left unchanged which
+	 * implies that als the new DRNG is reseeded when deemed necessary. This
+	 * seeding of the new DRNG shall only ensure that the new DRNG has the
+	 * same entropy as the old DRNG.
+	 */
+	ret = drng_store->crypto_cb->lrng_drng_generate_helper(
+				drng_store->drng, seed, sizeof(seed));
+	lrng_drng_unlock(drng_store, &flags);
+
+	if (ret < 0) {
+		reset_drng = true;
+		pr_warn("getting random data from DRNG failed for NUMA node %d (%d)\n",
+			node, ret);
+	} else {
+		/* seed new DRNG with data */
+		ret = cb->lrng_drng_seed_helper(new_drng, seed, ret);
+		if (ret < 0) {
+			reset_drng = true;
+			pr_warn("seeding of new DRNG failed for NUMA node %d (%d)\n",
+				node, ret);
+		} else {
+			pr_debug("seeded new DRNG of NUMA node %d instance from old DRNG instance\n",
+				 node);
+		}
+	}
+
+	mutex_lock(&drng_store->lock);
+	/*
+	 * If we switch the DRNG from the initial ChaCha20 DRNG to something
+	 * else, there is a lock transition from spin lock to mutex (see
+	 * lrng_drng_is_atomic and how the lock is taken in lrng_drng_lock).
+	 * Thus, we need to take both locks during the transition phase.
+	 */
+	if (lrng_drng_is_atomic(drng_store)) {
+		spin_lock_irqsave(&drng_store->spin_lock, flags);
+		sl = true;
+	} else {
+		__acquire(&drng_store->spin_lock);
+	}
+
+	if (reset_drng)
+		lrng_drng_reset(drng_store);
+
+	old_drng = drng_store->drng;
+	old_cb = drng_store->crypto_cb;
+	drng_store->drng = new_drng;
+	drng_store->crypto_cb = cb;
+
+	old_hash = drng_store->hash;
+	drng_store->hash = new_hash;
+	pr_info("Entropy pool read-hash allocated for DRNG for NUMA node %d\n",
+		node);
+
+	if (sl)
+		spin_unlock_irqrestore(&drng_store->spin_lock, flags);
+	else
+		__release(&drng_store->spin_lock);
+	mutex_unlock(&drng_store->lock);
+
+	/* ChaCha20 serves as atomic instance left untouched. */
+	if (old_drng != &chacha20) {
+		old_cb->lrng_drng_dealloc(old_drng);
+		old_cb->lrng_hash_dealloc(old_hash);
+	}
+
+	pr_info("DRNG of NUMA node %d switched\n", node);
+
+	return 0;
+}
+
+/*
+ * Switch the existing DRNG instances with new using the new crypto callbacks.
+ * The caller must hold the lrng_crypto_cb_update lock.
+ */
+static int lrng_drngs_switch(const struct lrng_crypto_cb *cb)
+{
+	struct lrng_drng **lrng_drng = lrng_drng_instances();
+	struct lrng_drng *lrng_drng_init = lrng_drng_init_instance();
+	int ret = 0;
+
+	/* Update DRNG */
+	if (lrng_drng) {
+		u32 node;
+
+		for_each_online_node(node) {
+			if (lrng_drng[node])
+				ret = lrng_drng_switch(lrng_drng[node], cb,
+						       node);
+		}
+	} else {
+		ret = lrng_drng_switch(lrng_drng_init, cb, 0);
+	}
+
+	if (!ret)
+		lrng_set_available();
+
+	return 0;
+}
+
+/**
+ * lrng_set_drng_cb - Register new cryptographic callback functions for DRNG
+ * The registering implies that all old DRNG states are replaced with new
+ * DRNG states.
+ *
+ * @cb: Callback functions to be registered -- if NULL, use the default
+ *	callbacks pointing to the ChaCha20 DRNG.
+ *
+ * Return:
+ * * 0 on success
+ * * < 0 on error
+ */
+int lrng_set_drng_cb(const struct lrng_crypto_cb *cb)
+{
+	struct lrng_drng *lrng_drng_init = lrng_drng_init_instance();
+	int ret;
+
+	if (!cb)
+		cb = &lrng_cc20_crypto_cb;
+
+	mutex_lock(&lrng_crypto_cb_update);
+
+	/*
+	 * If a callback other than the default is set, allow it only to be
+	 * set back to the default callback. This ensures that multiple
+	 * different callbacks can be registered at the same time. If a
+	 * callback different from the current callback and the default
+	 * callback shall be set, the current callback must be deregistered
+	 * (e.g. the kernel module providing it must be unloaded) and the new
+	 * implementation can be registered.
+	 */
+	if ((cb != &lrng_cc20_crypto_cb) &&
+	    (lrng_drng_init->crypto_cb != &lrng_cc20_crypto_cb)) {
+		pr_warn("disallow setting new cipher callbacks, unload the old callbacks first!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = lrng_drngs_switch(cb);
+
+out:
+	mutex_unlock(&lrng_crypto_cb_update);
+	return ret;
+}
+EXPORT_SYMBOL(lrng_set_drng_cb);