public inbox for linux-modules@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 1/2] lib/crypto: Add ML-DSA verification support
From: Eric Biggers @ 2025-11-26 20:35 UTC (permalink / raw)
  To: linux-crypto, David Howells
  Cc: Herbert Xu, Eric Biggers, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <20251126203517.167040-1-ebiggers@kernel.org>

Add support for verifying ML-DSA signatures.

ML-DSA (Module-Lattice-Based Digital Signature Algorithm) is specified
in FIPS 204 and is the standard version of Dilithium.  Unlike RSA and
elliptic-curve cryptography, ML-DSA is believed to be secure even
against adversaries in possession of a large-scale quantum computer.

Compared to the earlier patch
(https://lore.kernel.org/r/20251117145606.2155773-3-dhowells@redhat.com/)
that was based on "leancrypto", this implementation:

  - Is about 700 lines of source code instead of 4800.

  - Generates about 4 KB of object code instead of 28 KB.

  - Uses 9-13 KB of memory to verify a signature instead of 31-84 KB.

  - Is at least about the same speed, with a microbenchmark showing 3-5%
    improvements on one x86_64 CPU and -1% to 1% changes on another.
    When memory is a bottleneck, it's likely much faster.

  - Correctly implements the RejNTTPoly step of the algorithm.

The API just consists of a single function mldsa_verify(), supporting
pure ML-DSA with any standard parameter set (ML-DSA-44, ML-DSA-65, or
ML-DSA-87) as selected by an enum.  That's all that's actually needed.

Note that mldsa_verify() allocates memory, so it can sleep and can fail
with ENOMEM.  Unfortunately we don't have much choice about that, since
ML-DSA needs a lot of memory.  At least callers have to check for errors
anyway, since the signature could be invalid.

The following four potential features are unneeded and aren't included.
However, any that ever become needed could fairly easily be added later,
as they only affect how the message representative mu is calculated:

  - Nonempty context strings
  - Incremental message hashing
  - HashML-DSA
  - External mu

Signing support would, of course, be a larger and more complex addition.
However, the kernel doesn't, and shouldn't, need ML-DSA signing support.

Note that verification doesn't require constant-time code, and in fact
some steps are inherently variable-time.  I've used constant-time
patterns in some places anyway, but technically they're not needed.

Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
 include/crypto/mldsa.h |  53 ++++
 lib/crypto/Kconfig     |   7 +
 lib/crypto/Makefile    |   5 +
 lib/crypto/mldsa.c     | 651 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 716 insertions(+)
 create mode 100644 include/crypto/mldsa.h
 create mode 100644 lib/crypto/mldsa.c

diff --git a/include/crypto/mldsa.h b/include/crypto/mldsa.h
new file mode 100644
index 000000000000..950f3b771dff
--- /dev/null
+++ b/include/crypto/mldsa.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Support for verifying ML-DSA signatures
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef _CRYPTO_MLDSA_H
+#define _CRYPTO_MLDSA_H
+
+#include <linux/types.h>
+
+/* Identifier for an ML-DSA parameter set */
+enum mldsa_alg {
+	MLDSA44, /* ML-DSA-44 */
+	MLDSA65, /* ML-DSA-65 */
+	MLDSA87, /* ML-DSA-87 */
+};
+
+/* Lengths of ML-DSA public keys and signatures in bytes */
+#define MLDSA44_PUBLIC_KEY_SIZE 1312
+#define MLDSA65_PUBLIC_KEY_SIZE 1952
+#define MLDSA87_PUBLIC_KEY_SIZE 2592
+#define MLDSA44_SIGNATURE_SIZE 2420
+#define MLDSA65_SIGNATURE_SIZE 3309
+#define MLDSA87_SIGNATURE_SIZE 4627
+
+/**
+ * mldsa_verify() - Verify an ML-DSA signature
+ * @alg: The ML-DSA parameter set to use
+ * @sig: The signature
+ * @sig_len: Length of the signature in bytes.  Should match the
+ *	     MLDSA*_SIGNATURE_SIZE constant associated with @alg,
+ *	     otherwise -EBADMSG will be returned right away.
+ * @msg: The message
+ * @msg_len: Length of the message in bytes
+ * @pk: The public key
+ * @pk_len: Length of the public key in bytes.  Should match the
+ *	    MLDSA*_PUBLIC_KEY_SIZE constant associated with @alg,
+ *	    otherwise -EBADMSG will be returned right away.
+ *
+ * This verifies a signature using pure ML-DSA with the specified parameter set.
+ * The context string is assumed to be empty.
+ *
+ * Context: Might sleep
+ * Return: 0 if the signature is valid, -EBADMSG if the signature and/or public
+ *	   key is malformed, -EKEYREJECTED if the signature and public key are
+ *	   well-formed but the signature is invalid, or -ENOMEM if out of memory
+ *	   so the validity of the signature is unknown.
+ */
+int mldsa_verify(enum mldsa_alg alg, const u8 *sig, size_t sig_len,
+		 const u8 *msg, size_t msg_len, const u8 *pk, size_t pk_len);
+
+#endif /* _CRYPTO_MLDSA_H */
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
index 9d04b3771ce2..51ac3186ebc2 100644
--- a/lib/crypto/Kconfig
+++ b/lib/crypto/Kconfig
@@ -98,10 +98,17 @@ config CRYPTO_LIB_MD5_ARCH
 	depends on CRYPTO_LIB_MD5 && !UML
 	default y if MIPS && CPU_CAVIUM_OCTEON
 	default y if PPC
 	default y if SPARC64
 
+config CRYPTO_LIB_MLDSA
+	tristate
+	select CRYPTO_LIB_SHA3
+	help
+	  The ML-DSA library functions.  Select this if your module uses any of
+	  the functions from <crypto/mldsa.h>.
+
 config CRYPTO_LIB_POLY1305
 	tristate
 	help
 	  The Poly1305 library functions.  Select this if your module uses any
 	  of the functions from <crypto/poly1305.h>.
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index 6580991f8e12..fb83ec480ec0 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -125,10 +125,15 @@ libmd5-$(CONFIG_PPC) += powerpc/md5-asm.o
 libmd5-$(CONFIG_SPARC) += sparc/md5_asm.o
 endif # CONFIG_CRYPTO_LIB_MD5_ARCH
 
 ################################################################################
 
+obj-$(CONFIG_CRYPTO_LIB_MLDSA) += libmldsa.o
+libmldsa-y := mldsa.o
+
+################################################################################
+
 obj-$(CONFIG_CRYPTO_LIB_POLY1305) += libpoly1305.o
 libpoly1305-y := poly1305.o
 ifeq ($(CONFIG_ARCH_SUPPORTS_INT128),y)
 libpoly1305-$(CONFIG_CRYPTO_LIB_POLY1305_GENERIC) += poly1305-donna64.o
 else
diff --git a/lib/crypto/mldsa.c b/lib/crypto/mldsa.c
new file mode 100644
index 000000000000..911be1668a61
--- /dev/null
+++ b/lib/crypto/mldsa.c
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for verifying ML-DSA signatures
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include <crypto/mldsa.h>
+#include <crypto/sha3.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#define Q 8380417 /* The prime q = 2^23 - 2^13 + 1 */
+#define QINV_MOD_R 58728449 /* Multiplicative inverse of q mod 2^32 */
+#define R_MOD_Q 4193792 /* 2^32 mod q */
+#define N 256 /* Number of components per ring element */
+#define D 13 /* Number of bits dropped from the public key vector t */
+#define RHO_LEN 32 /* Length of the public random seed in bytes */
+#define MAX_W1_ENCODED_LEN 192 /* Max encoded length of one element of w'_1 */
+
+/*
+ * The zetas array in Montgomery form, i.e. with extra factor of 2^32.
+ * Reference: FIPS 204 Section 7.5 "NTT and NTT^-1"
+ * Generated by the following Python code:
+ * q=8380417; [a%q - q*(a%q > q//2) for a in [1753**(int(f'{i:08b}'[::-1], 2)) << 32 for i in range(256)]]
+ */
+static const s32 zetas_times_2_32[N] = {
+	-4186625, 25847,    -2608894, -518909,	237124,	  -777960,  -876248,
+	466468,	  1826347,  2353451,  -359251,	-2091905, 3119733,  -2884855,
+	3111497,  2680103,  2725464,  1024112,	-1079900, 3585928,  -549488,
+	-1119584, 2619752,  -2108549, -2118186, -3859737, -1399561, -3277672,
+	1757237,  -19422,   4010497,  280005,	2706023,  95776,    3077325,
+	3530437,  -1661693, -3592148, -2537516, 3915439,  -3861115, -3043716,
+	3574422,  -2867647, 3539968,  -300467,	2348700,  -539299,  -1699267,
+	-1643818, 3505694,  -3821735, 3507263,	-2140649, -1600420, 3699596,
+	811944,	  531354,   954230,   3881043,	3900724,  -2556880, 2071892,
+	-2797779, -3930395, -1528703, -3677745, -3041255, -1452451, 3475950,
+	2176455,  -1585221, -1257611, 1939314,	-4083598, -1000202, -3190144,
+	-3157330, -3632928, 126922,   3412210,	-983419,  2147896,  2715295,
+	-2967645, -3693493, -411027,  -2477047, -671102,  -1228525, -22981,
+	-1308169, -381987,  1349076,  1852771,	-1430430, -3343383, 264944,
+	508951,	  3097992,  44288,    -1100098, 904516,	  3958618,  -3724342,
+	-8578,	  1653064,  -3249728, 2389356,	-210977,  759969,   -1316856,
+	189548,	  -3553272, 3159746,  -1851402, -2409325, -177440,  1315589,
+	1341330,  1285669,  -1584928, -812732,	-1439742, -3019102, -3881060,
+	-3628969, 3839961,  2091667,  3407706,	2316500,  3817976,  -3342478,
+	2244091,  -2446433, -3562462, 266997,	2434439,  -1235728, 3513181,
+	-3520352, -3759364, -1197226, -3193378, 900702,	  1859098,  909542,
+	819034,	  495491,   -1613174, -43260,	-522500,  -655327,  -3122442,
+	2031748,  3207046,  -3556995, -525098,	-768622,  -3595838, 342297,
+	286988,	  -2437823, 4108315,  3437287,	-3342277, 1735879,  203044,
+	2842341,  2691481,  -2590150, 1265009,	4055324,  1247620,  2486353,
+	1595974,  -3767016, 1250494,  2635921,	-3548272, -2994039, 1869119,
+	1903435,  -1050970, -1333058, 1237275,	-3318210, -1430225, -451100,
+	1312455,  3306115,  -1962642, -1279661, 1917081,  -2546312, -1374803,
+	1500165,  777191,   2235880,  3406031,	-542412,  -2831860, -1671176,
+	-1846953, -2584293, -3724270, 594136,	-3776993, -2013608, 2432395,
+	2454455,  -164721,  1957272,  3369112,	185531,	  -1207385, -3183426,
+	162844,	  1616392,  3014001,  810149,	1652634,  -3694233, -1799107,
+	-3038916, 3523897,  3866901,  269760,	2213111,  -975884,  1717735,
+	472078,	  -426683,  1723600,  -1803090, 1910376,  -1667432, -1104333,
+	-260646,  -3833893, -2939036, -2235985, -420899,  -2286327, 183443,
+	-976891,  1612842,  -3545687, -554416,	3919660,  -48306,   -1362209,
+	3937738,  1400424,  -846154,  1976782
+};
+
+/* Reference: FIPS 204 Section 4 "Parameter Sets" */
+static const struct mldsa_parameter_set {
+	u8 k; /* num rows in the matrix A */
+	u8 l; /* num columns in the matrix A */
+	u8 ctilde_len; /* length of commitment hash ctilde in bytes; lambda/4 */
+	u8 omega; /* max num of 1's in the hint vector h */
+	u8 tau; /* num of +-1's in challenge c */
+	u8 beta; /* tau times eta */
+	u16 pk_len; /* length of public keys in bytes */
+	u16 sig_len; /* length of signatures in bytes */
+	s32 gamma1; /* coefficient range of y */
+} mldsa_parameter_sets[] = {
+	[MLDSA44] = {
+		.k = 4,
+		.l = 4,
+		.ctilde_len = 32,
+		.omega = 80,
+		.tau = 39,
+		.beta = 78,
+		.pk_len = MLDSA44_PUBLIC_KEY_SIZE,
+		.sig_len = MLDSA44_SIGNATURE_SIZE,
+		.gamma1 = 1 << 17,
+	},
+	[MLDSA65] = {
+		.k = 6,
+		.l = 5,
+		.ctilde_len = 48,
+		.omega = 55,
+		.tau = 49,
+		.beta = 196,
+		.pk_len = MLDSA65_PUBLIC_KEY_SIZE,
+		.sig_len = MLDSA65_SIGNATURE_SIZE,
+		.gamma1 = 1 << 19,
+	},
+	[MLDSA87] = {
+		.k = 8,
+		.l = 7,
+		.ctilde_len = 64,
+		.omega = 75,
+		.tau = 60,
+		.beta = 120,
+		.pk_len = MLDSA87_PUBLIC_KEY_SIZE,
+		.sig_len = MLDSA87_SIGNATURE_SIZE,
+		.gamma1 = 1 << 19,
+	},
+};
+
+/*
+ * An element of the ring R_q (normal form) or the ring T_q (NTT form).  It
+ * consists of N integers mod q: either the polynomial coefficients of the R_q
+ * element or the components of the T_q element.  In either case, whether they
+ * are fully reduced to [0, q - 1] varies in the different parts of the code.
+ */
+struct mldsa_ring_elem {
+	s32 x[N];
+};
+
+struct mldsa_verification_workspace {
+	/* SHAKE context for computing c, mu, and ctildeprime */
+	struct shake_ctx shake;
+	/* The fields in this union are used in their order of declaration. */
+	union {
+		/* The hash of the public key */
+		u8 tr[64];
+		/* The message representative mu */
+		u8 mu[64];
+		/* Encoded element of w'_1 */
+		u8 w1_encoded[MAX_W1_ENCODED_LEN];
+		/* The commitment hash.  Real length is params->ctilde_len */
+		u8 ctildeprime[64];
+	};
+	/* SHAKE context for generating elements of the matrix A */
+	struct shake_ctx a_shake;
+	/*
+	 * An element of the matrix A generated from the public seed, or an
+	 * element of the vector t_1 decoded from the public key and pre-scaled
+	 * by 2^d.  Both are in NTT form.  To reduce memory usage, we generate
+	 * or decode these elements only as needed.
+	 */
+	union {
+		struct mldsa_ring_elem a;
+		struct mldsa_ring_elem t1_scaled;
+	};
+	/* The challenge c, generated from ctilde */
+	struct mldsa_ring_elem c;
+	/* A temporary element used during calculations */
+	struct mldsa_ring_elem tmp;
+
+	/* The following fields are variable-length: */
+
+	/* The signer's response vector */
+	struct mldsa_ring_elem z[/* l */];
+
+	/* The signer's hint vector */
+	/* u8 h[k * N]; */
+};
+
+/*
+ * Compute a * b * 2^-32 mod q.  a * b must be in the range [-2^31 * q, 2^31 * q
+ * - 1] before reduction.  The return value is in the range [-q + 1, q - 1].
+ *
+ * To reduce mod q efficiently, this uses Montgomery reduction with R=2^32.
+ * That's where the factor of 2^-32 comes from.  The caller must include a
+ * factor of 2^32 at some point to compensate for that.
+ *
+ * To keep the input and output ranges very close to symmetric, this
+ * specifically does a "signed" Montgomery reduction.  That is, when doing the
+ * first step d = c * q^-1 mod 2^32, this uses representatives in [S32_MIN,
+ * S32_MAX] rather than [0, U32_MAX], i.e. s32 rather than u32.  This matters in
+ * the wider multiplication d * Q when d keeps its value via sign extension.
+ *
+ * Reference: FIPS 204 Appendix A "Montgomery Multiplication".  But, it doesn't
+ * explain it properly: it has an off-by-one error in the upper end of the input
+ * range, it doesn't clarify that the signed version should be used, and it
+ * gives an unnecessarily large output range.  A better citation is perhaps the
+ * Dilithium reference code, which functionally matches the below code and
+ * merely has the (benign) off-by-one error in its documentation.
+ */
+static inline s32 Zq_mult(s32 a, s32 b)
+{
+	/* Compute the unreduced product c. */
+	s64 c = (s64)a * b;
+
+	/* Compute d = c * q^-1 mod 2^32.  Signed, as explained above. */
+	s32 d = (s32)c * QINV_MOD_R;
+
+	/*
+	 * Compute e = c - d * q.  This makes the low 32 bits zero, since
+	 *   c - (c * q^-1) * q mod 2^32
+	 * = c - c * (q^-1 * q) mod 2^32
+	 * = c - c * 1 mod 2^32
+	 * = c - c mod 2^32
+	 * = 0 mod 2^32
+	 */
+	s64 e = c - (s64)d * Q;
+
+	/* Finally, return e * 2^-32. */
+	return e >> 32;
+}
+
+/*
+ * Convert @w to its number-theoretically-transformed representation in-place.
+ * Reference: FIPS 204 Algorithm 41, NTT
+ *
+ * To prevent intermediate overflows, all input coefficients must have absolute
+ * value < q.  All output components have absolute value < 9*q.
+ */
+static void ntt(struct mldsa_ring_elem *w)
+{
+	int m = 0; /* index in zetas_times_2_32 */
+
+	for (int len = 128; len >= 1; len /= 2) {
+		for (int start = 0; start < 256; start += 2 * len) {
+			const s32 z = zetas_times_2_32[++m];
+
+			for (int j = start; j < start + len; j++) {
+				s32 t = Zq_mult(z, w->x[j + len]);
+
+				w->x[j + len] = w->x[j] - t;
+				w->x[j] += t;
+			}
+		}
+	}
+}
+
+/*
+ * Convert @w from its number-theoretically-transformed representation in-place.
+ * Reference: FIPS 204 Algorithm 42, NTT^-1
+ *
+ * This also multiplies the coefficients by 2^32, undoing an extra factor of
+ * 2^-32 introduced earlier.
+ *
+ * To prevent intermediate overflows, all input components must have absolute
+ * value < q.  The output coefficients have absolute value < q as well.
+ */
+static void invntt_and_mul_2_32(struct mldsa_ring_elem *w)
+{
+	int m = 256; /* index in zetas_times_2_32 */
+
+	for (int len = 1; len < 256; len *= 2) {
+		for (int start = 0; start < 256; start += 2 * len) {
+			const s32 z = -zetas_times_2_32[--m];
+
+			for (int j = start; j < start + len; j++) {
+				s32 t = w->x[j];
+
+				w->x[j] = t + w->x[j + len];
+				w->x[j + len] = Zq_mult(z, t - w->x[j + len]);
+			}
+		}
+	}
+	/*
+	 * Multiply by 2^32 * 256^-1.  2^32 cancels the factor of 2^-32 from
+	 * earlier Montgomery multiplications.  256^-1 is for NTT^-1.  This
+	 * itself uses Montgomery multiplication, so *another* 2^32 is needed.
+	 * Thus the actual multiplicand is 2^32 * 2^32 * 256^-1 mod q = 41978.
+	 */
+	for (int j = 0; j < 256; j++)
+		w->x[j] = Zq_mult(w->x[j], 41978);
+}
+
+/*
+ * Decode an element of t_1, i.e. the high d bits of t = A*s_1 + s_2.
+ * Reference: FIPS 204 Algorithm 23, pkDecode.
+ * Also multiply it by 2^d and convert it to NTT form.
+ */
+static const u8 *decode_t1_elem(struct mldsa_ring_elem *out,
+				const u8 *t1_encoded)
+{
+	for (int j = 0; j < N; j += 4, t1_encoded += 5) {
+		u32 v = get_unaligned_le32(t1_encoded);
+
+		out->x[j + 0] = ((v >> 0) & 0x3ff) << D;
+		out->x[j + 1] = ((v >> 10) & 0x3ff) << D;
+		out->x[j + 2] = ((v >> 20) & 0x3ff) << D;
+		out->x[j + 3] = ((v >> 30) | (t1_encoded[4] << 2)) << D;
+		static_assert(0x3ff << D < Q); /* All coefficients < q. */
+	}
+	ntt(out);
+	return t1_encoded; /* Return updated pointer. */
+}
+
+/*
+ * Decode the signer's response vector 'z' from the signature.
+ * Reference: FIPS 204 Algorithm 27, sigDecode.
+ *
+ * This also validates that the coefficients of z are in range, corresponding
+ * the infinity norm check at the end of Algorithm 8, ML-DSA.Verify_internal.
+ *
+ * Finally, this also converts z to NTT form.
+ */
+static bool decode_z(struct mldsa_ring_elem z[/* l */], int l, s32 gamma1,
+		     int beta, const u8 **sig_ptr)
+{
+	const u8 *sig = *sig_ptr;
+
+	for (int i = 0; i < l; i++) {
+		if (l == 4) { /* ML-DSA-44? */
+			/* 18-bit coefficients: decode 4 from 9 bytes. */
+			for (int j = 0; j < N; j += 4, sig += 9) {
+				u64 v = get_unaligned_le64(sig);
+
+				z[i].x[j + 0] = (v >> 0) & 0x3ffff;
+				z[i].x[j + 1] = (v >> 18) & 0x3ffff;
+				z[i].x[j + 2] = (v >> 36) & 0x3ffff;
+				z[i].x[j + 3] = (v >> 54) | (sig[8] << 10);
+			}
+		} else {
+			/* 20-bit coefficients: decode 4 from 10 bytes. */
+			for (int j = 0; j < N; j += 4, sig += 10) {
+				u64 v = get_unaligned_le64(sig);
+
+				z[i].x[j + 0] = (v >> 0) & 0xfffff;
+				z[i].x[j + 1] = (v >> 20) & 0xfffff;
+				z[i].x[j + 2] = (v >> 40) & 0xfffff;
+				z[i].x[j + 3] =
+					(v >> 60) |
+					(get_unaligned_le16(&sig[8]) << 4);
+			}
+		}
+		for (int j = 0; j < N; j++) {
+			z[i].x[j] = gamma1 - z[i].x[j];
+			if (z[i].x[j] <= -(gamma1 - beta) ||
+			    z[i].x[j] >= gamma1 - beta)
+				return false;
+		}
+		ntt(&z[i]);
+	}
+	*sig_ptr = sig; /* Return updated pointer. */
+	return true;
+}
+
+/*
+ * Decode the signer's hint vector 'h' from the signature.
+ * Reference: FIPS 204 Algorithm 21, HintBitUnpack
+ *
+ * Note that there are several ways in which the hint vector can be malformed.
+ */
+static bool decode_hint_vector(u8 h[/* k * N */], int k, int omega, const u8 *y)
+{
+	int index = 0;
+
+	memset(h, 0, k * N);
+	for (int i = 0; i < k; i++) {
+		int count = y[omega + i]; /* num 1's in elems 0 through i */
+		int prev = -1;
+
+		/* Cumulative count mustn't decrease or exceed omega. */
+		if (count < index || count > omega)
+			return false;
+		for (; index < count; index++) {
+			if (prev >= y[index]) /* Coefficients out of order? */
+				return false;
+			prev = y[index];
+			h[i * N + y[index]] = 1;
+		}
+	}
+	return mem_is_zero(&y[index], omega - index);
+}
+
+/*
+ * Expand @seed into an element of R_q @c with coefficients in {-1, 0, 1},
+ * exactly @tau of them nonzero.  Reference: FIPS 204 Algorithm 29, SampleInBall
+ */
+static void sample_in_ball(struct mldsa_ring_elem *c, const u8 *seed,
+			   size_t seed_len, int tau, struct shake_ctx *shake)
+{
+	u64 signs;
+	u8 j;
+
+	shake256_init(shake);
+	shake_update(shake, seed, seed_len);
+	shake_squeeze(shake, (u8 *)&signs, sizeof(signs));
+	le64_to_cpus(&signs);
+	*c = (struct mldsa_ring_elem){};
+	for (int i = N - tau; i < N; i++, signs >>= 1) {
+		do {
+			shake_squeeze(shake, &j, 1);
+		} while (j > i);
+		c->x[i] = c->x[j];
+		c->x[j] = 1 - 2 * (s32)(signs & 1);
+	}
+}
+
+/*
+ * Expand the public seed @rho and @row_and_column into an element of T_q @out.
+ * Reference: FIPS 204 Algorithm 30, RejNTTPoly
+ */
+static void rej_ntt_poly(struct mldsa_ring_elem *out, const u8 rho[RHO_LEN],
+			 __le16 row_and_column, struct shake_ctx *shake)
+{
+	u8 block[SHAKE128_BLOCK_SIZE + 1]; /* 1 extra to allow 4-byte loads */
+
+	shake128_init(shake);
+	shake_update(shake, rho, RHO_LEN);
+	shake_update(shake, (u8 *)&row_and_column, sizeof(row_and_column));
+	for (int i = 0; i < N;) {
+		shake_squeeze(shake, block, SHAKE128_BLOCK_SIZE);
+		static_assert(SHAKE128_BLOCK_SIZE % 3 == 0);
+		for (int j = 0; j < SHAKE128_BLOCK_SIZE && i < N; j += 3) {
+			u32 x = get_unaligned_le32(&block[j]) & 0x7fffff;
+
+			if (x < Q) /* Ignore values >= q. */
+				out->x[i++] = x;
+		}
+	}
+}
+
+/*
+ * Return the HighBits of r adjusted according to hint h.  r is in [0, q - 1].
+ * Reference: FIPS 204 Algorithm 40, UseHint
+ *
+ * This is needed because of the public key compression in ML-DSA.
+ * gamma2 is a compile-time constant, either (q - 1) / 88 or (q - 1) / 32.
+ */
+static __always_inline s32 use_hint(u8 h, s32 r, const s32 gamma2)
+{
+	const s32 m = (Q - 1) / (2 * gamma2); /* 44 or 16, compile-time const */
+	s32 r0, r1;
+
+	/*
+	 * Compute the (non-hint-adjusted) HighBits r1 as:
+	 *
+	 *  r1 = (r - (r mod+- (2 * gamma2))) / (2 * gamma2)
+	 *     = floor((r + gamma2 - 1) / (2 * gamma2))
+	 *
+	 * ... except when this would give the highest possible value, m, in
+	 * which case set r1 to 0 instead.
+	 */
+	if (m != 16) {
+		if (r + gamma2 - 1 >= m * 2 * gamma2)
+			r1 = 0;
+		else /* Compilers optimize this to reciprocal multiplication. */
+			r1 = ((u32)r + gamma2 - 1) / (2 * gamma2);
+	} else {
+		/*
+		 * In this case, m is 16 which is a power of 2.  Handle the
+		 * special case of mapping 16 to 0 by just truncating the high
+		 * bit.  It can be done for free by integrating it into division
+		 * by '2 * gamma2' which has to be done anyway, if that division
+		 * is implemented as a reciprocal multiplication.  Compilers
+		 * don't know to use this truncation trick, so implement it
+		 * explicitly.  The multiplier is ceil(2^60 / (2 * gamma2)).
+		 */
+		r1 = (u64)((r + gamma2 - 1) * 2201172838403ULL) >> 60;
+	}
+	if (h == 0)
+		return r1; /* Hint bit is 0.  Just return the HighBits r1. */
+
+	/*
+	 * Determine whether the LowBits r0 are positive, and based on that, add
+	 * or subtract the hint bit 1 to the HighBits r1, modulo m.
+	 *
+	 * 'r - r1 * 2 * gamma2' gives the LowBits r0 in [-gamma2 + 1, gamma2],
+	 * except in the special case where r1 was mapped from m to 0.  In that
+	 * case it instead gives a value in [q - gamma2, q - 1] and the correct
+	 * r0 is negative, so just treat only the range [1, gamma2] as positive.
+	 */
+	r0 = r - r1 * 2 * gamma2;
+	if (r0 >= 1 && r0 <= gamma2) {
+		/* Return (r1 + 1) % m, where r1 is in [0, m - 1]. */
+		if (is_power_of_2(m))
+			return (r1 + 1) & (m - 1);
+		return (r1 == m - 1) ? 0 : r1 + 1;
+	} else {
+		/* Return (r1 - 1) % m, where r1 is in [0, m - 1]. */
+		if (is_power_of_2(m))
+			return (r1 - 1) & (m - 1);
+		return (r1 == 0) ? m - 1 : r1 - 1;
+	}
+}
+
+static __always_inline void reduce_and_use_hint(struct mldsa_ring_elem *w,
+						const u8 h[N], s32 gamma2)
+{
+	for (int j = 0; j < N; j++) {
+		w->x[j] += (w->x[j] >> 31) & Q; /* [-q+1, q-1] to [0, q-1] */
+		w->x[j] = use_hint(h[j], w->x[j], gamma2);
+	}
+}
+
+/*
+ * Encode one element of the commitment vector w'_1 into a byte string.
+ * Reference: FIPS 204 Algorithm 28, w1Encode.
+ * Return the number of bytes used: 192 for ML-DSA-44 and 128 for the others.
+ */
+static size_t encode_w1(u8 out[MAX_W1_ENCODED_LEN],
+			const struct mldsa_ring_elem *w1, int k)
+{
+	size_t pos = 0;
+
+	static_assert(MAX_W1_ENCODED_LEN == N * 6 / 8);
+	if (k == 4) { /* ML-DSA-44? */
+		/* 6 bits per coefficient.  Pack 4 at a time. */
+		for (int j = 0; j < N; j += 4) {
+			u32 v = (w1->x[j + 0] << 0) | (w1->x[j + 1] << 6) |
+				(w1->x[j + 2] << 12) | (w1->x[j + 3] << 18);
+			out[pos++] = v >> 0;
+			out[pos++] = v >> 8;
+			out[pos++] = v >> 16;
+		}
+	} else {
+		/* 4 bits per coefficient.  Pack 2 at a time. */
+		for (int j = 0; j < N; j += 2)
+			out[pos++] = w1->x[j] | (w1->x[j + 1] << 4);
+	}
+	return pos;
+}
+
+/* Reference: FIPS 204 Section 6.3 "ML-DSA Verifying (Internal)" */
+int mldsa_verify(enum mldsa_alg alg, const u8 *sig, size_t sig_len,
+		 const u8 *msg, size_t msg_len, const u8 *pk, size_t pk_len)
+{
+	const struct mldsa_parameter_set *params = &mldsa_parameter_sets[alg];
+	const int k = params->k, l = params->l;
+	/* For now this just does pure ML-DSA with an empty context string. */
+	static const u8 msg_prefix[2] = { /* dom_sep= */ 0, /* ctx_len= */ 0 };
+	const u8 *ctilde; /* The signer's commitment hash */
+	const u8 *t1_encoded = &pk[RHO_LEN]; /* Next encoded element of t_1 */
+	u8 *h; /* The signer's hint vector, length k * N */
+	size_t w1_enc_len;
+
+	/* Validate the public key and signature lengths. */
+	if (pk_len != params->pk_len || sig_len != params->sig_len)
+		return -EBADMSG;
+
+	/*
+	 * Allocate the workspace, including variable-length fields.  Its size
+	 * depends only on the ML-DSA parameter set, not the other inputs.
+	 *
+	 * For freeing it, use kfree_sensitive() rather than kfree().  This is
+	 * mainly to comply with FIPS 204 Section 3.6.3 "Intermediate Values".
+	 * In reality it's a bit gratuitous, as this is a public key operation.
+	 */
+	struct mldsa_verification_workspace *ws __free(kfree_sensitive) =
+		kmalloc(sizeof(*ws) + (l * sizeof(ws->z[0])) + (k * N),
+			GFP_KERNEL);
+	if (!ws)
+		return -ENOMEM;
+	h = (u8 *)&ws->z[l];
+
+	/* Decode the signature.  Reference: FIPS 204 Algorithm 27, sigDecode */
+	ctilde = sig;
+	sig += params->ctilde_len;
+	if (!decode_z(ws->z, l, params->gamma1, params->beta, &sig))
+		return -EBADMSG;
+	if (!decode_hint_vector(h, k, params->omega, sig))
+		return -EBADMSG;
+
+	/* Recreate the challenge c from the signer's commitment hash. */
+	sample_in_ball(&ws->c, ctilde, params->ctilde_len, params->tau,
+		       &ws->shake);
+	ntt(&ws->c);
+
+	/* Compute the message representative mu. */
+	shake256(pk, pk_len, ws->tr, sizeof(ws->tr));
+	shake256_init(&ws->shake);
+	shake_update(&ws->shake, ws->tr, sizeof(ws->tr));
+	shake_update(&ws->shake, msg_prefix, sizeof(msg_prefix));
+	shake_update(&ws->shake, msg, msg_len);
+	shake_squeeze(&ws->shake, ws->mu, sizeof(ws->mu));
+
+	/* Start computing ctildeprime = H(mu || w1Encode(w'_1)). */
+	shake256_init(&ws->shake);
+	shake_update(&ws->shake, ws->mu, sizeof(ws->mu));
+
+	/*
+	 * Compute the commitment w'_1 from A, z, c, t_1, and h.
+	 *
+	 * The computation is the same for each of the k rows.  Just do each row
+	 * before moving on to the next, resulting in only one loop over k.
+	 */
+	for (int i = 0; i < k; i++) {
+		/*
+		 * tmp = NTT(A) * NTT(z) * 2^-32
+		 * To reduce memory use, generate each element of NTT(A)
+		 * on-demand.  Note that each element is used only once.
+		 */
+		ws->tmp = (struct mldsa_ring_elem){};
+		for (int j = 0; j < l; j++) {
+			rej_ntt_poly(&ws->a, pk /* rho is first field of pk */,
+				     cpu_to_le16((i << 8) | j), &ws->a_shake);
+			for (int n = 0; n < N; n++)
+				ws->tmp.x[n] +=
+					Zq_mult(ws->a.x[n], ws->z[j].x[n]);
+		}
+		/* All components of tmp now have abs value < l*q. */
+
+		/* Decode the next element of t_1. */
+		t1_encoded = decode_t1_elem(&ws->t1_scaled, t1_encoded);
+
+		/*
+		 * tmp -= NTT(c) * NTT(t_1 * 2^d) * 2^-32
+		 *
+		 * Taking a conservative bound for the output of ntt(), the
+		 * multiplicands can have absolute value up to 9*q.  That
+		 * corresponds to a product with absolute value 81*q^2.  That is
+		 * within the limits of Zq_mult() which needs < ~256*q^2.
+		 */
+		for (int j = 0; j < N; j++)
+			ws->tmp.x[j] -= Zq_mult(ws->c.x[j], ws->t1_scaled.x[j]);
+
+		/*
+		 * All components of tmp now have abs value < (l+1)*q.
+		 * To safely do the inverse NTT, reduce them to abs value < q.
+		 */
+		for (int j = 0; j < N; j++)
+			ws->tmp.x[j] = Zq_mult(ws->tmp.x[j], R_MOD_Q);
+
+		/* tmp = w'_Approx = NTT^-1(tmp) * 2^32 */
+		invntt_and_mul_2_32(&ws->tmp);
+
+		/*
+		 * Reduce the coefficients to their standard representatives in
+		 * the range [0, q - 1].  They're already in the range [-q + 1,
+		 * q - 1], so all that's needed is a conditional addition of q.
+		 *
+		 * After that, do: tmp = w'_1 = UseHint(h, w'_Approx)
+		 * For efficiency, gamma2 is set to a compile-time constant.
+		 */
+		if (k == 4)
+			reduce_and_use_hint(&ws->tmp, &h[i * N], (Q - 1) / 88);
+		else
+			reduce_and_use_hint(&ws->tmp, &h[i * N], (Q - 1) / 32);
+
+		/* Encode and hash the next element of w'_1. */
+		w1_enc_len = encode_w1(ws->w1_encoded, &ws->tmp, k);
+		shake_update(&ws->shake, ws->w1_encoded, w1_enc_len);
+	}
+
+	/* Finish computing ctildeprime. */
+	shake_squeeze(&ws->shake, ws->ctildeprime, params->ctilde_len);
+
+	/* Verify that ctilde == ctildeprime. */
+	if (memcmp(ws->ctildeprime, ctilde, params->ctilde_len) != 0)
+		return -EKEYREJECTED;
+	/* ||z||_infinity < gamma1 - beta was already checked in decode_z(). */
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mldsa_verify);
+
+MODULE_DESCRIPTION("ML-DSA signature verification");
+MODULE_LICENSE("GPL");
-- 
2.52.0


^ permalink raw reply related

* [PATCH v2 0/2] lib/crypto: ML-DSA verification support
From: Eric Biggers @ 2025-11-26 20:35 UTC (permalink / raw)
  To: linux-crypto, David Howells
  Cc: Herbert Xu, Eric Biggers, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, keyrings,
	linux-modules, linux-kernel

This series is targeting libcrypto-next.  It can also be retrieved from:

    git fetch https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git mldsa-v2

This series adds support for verifying ML-DSA signatures to lib/crypto/.
Patch 1 is the ML-DSA implementation itself.  See that for full details.
Patch 2 adds the KUnit test suite.

The initial use case for this will be kernel module signature
verification.  For more details, see David Howells' patchset
https://lore.kernel.org/linux-crypto/20251120104439.2620205-1-dhowells@redhat.com/

Changed in v2:
- Reworked the KUnit test suite
- Improved commit messages and comments
- Added missing MODULE_DESCRIPTION() and MODULE_LICENSE()
- Made the return values of mldsa_verify() differentiate between an
  input being malformed and the "real" signature check failing
- Refactored w1 encoding into a helper function
- Used kfree() instead of kfree_sensitive()
- Avoided unusal C syntax by accessing the hint vector via 'u8 *'
- Reworked use_hint() to be better optimized and documented

Eric Biggers (2):
  lib/crypto: Add ML-DSA verification support
  lib/crypto: tests: Add KUnit tests for ML-DSA verification

 include/crypto/mldsa.h            |   53 +
 lib/crypto/Kconfig                |    7 +
 lib/crypto/Makefile               |    5 +
 lib/crypto/mldsa.c                |  651 ++++++++++
 lib/crypto/tests/Kconfig          |    9 +
 lib/crypto/tests/Makefile         |    1 +
 lib/crypto/tests/mldsa-testvecs.h | 1877 +++++++++++++++++++++++++++++
 lib/crypto/tests/mldsa_kunit.c    |  381 ++++++
 8 files changed, 2984 insertions(+)
 create mode 100644 include/crypto/mldsa.h
 create mode 100644 lib/crypto/mldsa.c
 create mode 100644 lib/crypto/tests/mldsa-testvecs.h
 create mode 100644 lib/crypto/tests/mldsa_kunit.c


base-commit: c0127f3ad65e8b21752f7b4d7dbe7e4ab5b5c62d
-- 
2.52.0


^ permalink raw reply

* Re: [PATCH] gendwarfksyms: Fix build on 32-bit hosts
From: Daniel Gomez @ 2025-11-26 19:59 UTC (permalink / raw)
  To: Sami Tolvanen, Michal Suchánek
  Cc: linux-modules, Luis Chamberlain, Petr Pavlu, linux-kbuild,
	linux-kernel
In-Reply-To: <CABCJKucc0bxLJ=b9rkiwWts6uA=ReLFr32K1OP9WH51D-hO4+A@mail.gmail.com>



On 25/11/2025 21.09, Sami Tolvanen wrote:
> On Tue, Nov 18, 2025 at 8:18 AM Michal Suchánek <msuchanek@suse.de> wrote:
>>
>> Hello,
>>
>> On Mon, Nov 17, 2025 at 08:38:07PM +0000, Sami Tolvanen wrote:
>>> We have interchangeably used unsigned long for some of the types
>>> defined in elfutils, assuming they're always 64-bit. This obviously
>>> fails when building gendwarfksyms on 32-bit hosts. Fix the types.
>>>
>>> Reported-by: Michal Suchánek <msuchanek@suse.de>
>>> Closes: https://lore.kernel.org/linux-modules/aRcxzPxtJblVSh1y@kitsune.suse.cz/
>>> Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
>>> ---
>>>  scripts/gendwarfksyms/dwarf.c   | 4 +++-
>>>  scripts/gendwarfksyms/symbols.c | 5 +++--
>>>  2 files changed, 6 insertions(+), 3 deletions(-)
>>
>> with this patch gendwarfksyms builds on 32bit x86 and Arm.
>>
>> Tested-by: Michal Suchánek <msuchanek@suse.de>
> 
> Great, thanks for testing!  Daniel, do you want to take this fix
> through the modules tree?
> 
> Sami

Absolutely! Since we are at the end of the rc cycle, I'll merge this after
sending the current queued patches.

^ permalink raw reply

* Re: [linux-next:master 4806/10599] error[E0560]: struct `bindings::kernel_param_ops` has no field named `get`
From: Miguel Ojeda @ 2025-11-26 13:50 UTC (permalink / raw)
  To: Daniel Gomez
  Cc: kernel test robot, Andreas Hindborg, llvm, oe-kbuild-all,
	Benno Lossin, linux-modules, Richard Weinberger, Anton Ivanov,
	Johannes Berg, linux-um, David Gow
In-Reply-To: <84b74435-5aad-4c15-aea5-db87b4a6bf11@kernel.org>

On Wed, Nov 26, 2025 at 2:41 PM Daniel Gomez <da.gomez@kernel.org> wrote:
>
> On 21/11/2025 01.24, kernel test robot wrote:
> > tree:   https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git master
> > head:   88cbd8ac379cf5ce68b7efcfd4d1484a6871ee0b
> > commit: 0b08fc292842a13aa496413b48c1efb83573b8c6 [4806/10599] rust: introduce module_param module
> > config: um-randconfig-001-20251121 (https://download.01.org/0day-ci/archive/20251121/202511210858.uwVivgvn-lkp@intel.com/config)
> > compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 9e9fe08b16ea2c4d9867fb4974edf2a3776d6ece)
> > rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
> > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251121/202511210858.uwVivgvn-lkp@intel.com/reproduce)
>
> We can't reproduce this.
>
> If anyone cares, please let us know how to reproduce it.
>
> Tested on Debian testing x86_64 host.
>
> rustc --version
> rustc 1.91.1 (ed61e7d7e 2025-11-07
>
> /home/dagomez/0day/llvm-22.0.0-e19fa930ca838715028c00c234874d1db4f93154-20250918-184558-x86_64/bin/clang-22 --version
> ClangBuiltLinux clang version 22.0.0git (https://github.com/llvm/llvm-project.git e19fa930ca838715028c00c234874d1db4f93154)
> Target: x86_64-unknown-linux-gnu
> Thread model: posix
> InstalledDir: /home/dagomez/0day/llvm-22.0.0-e19fa930ca838715028c00c234874d1db4f93154-20250918-184558-x86_64/bin
>
>   561  wget https://download.01.org/0day-ci/archive/20251121/202511210858.uwVivgvn-lkp@intel.com/config
>   563  git clone https://github.com/intel/lkp-tests.git ~/lkp-tests
>   565  mkdir -p build_dir && cp config build_dir/.config
>
>   571  COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang-22 ~/lkp-tests/kbuild/make.cross W=1 O=build_dir ARCH=um olddefconfig
>   572  COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang-22 ~/lkp-tests/kbuild/make.cross W=1 O=build_dir ARCH=um prepare
>   573  COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang-22 ~/lkp-tests/kbuild/make.cross W=1 O=build_dir ARCH=um -j$(nproc)
>
> I'm just getting these warnings:
>
> ...

Cc'ing UML so that they are in the loop.

Cheers,
Miguel

^ permalink raw reply

* Re: [linux-next:master 4806/10599] error[E0560]: struct `bindings::kernel_param_ops` has no field named `get`
From: Daniel Gomez @ 2025-11-26 13:41 UTC (permalink / raw)
  To: kernel test robot, Andreas Hindborg
  Cc: llvm, oe-kbuild-all, Benno Lossin, linux-modules
In-Reply-To: <202511210858.uwVivgvn-lkp@intel.com>



On 21/11/2025 01.24, kernel test robot wrote:
> tree:   https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git master
> head:   88cbd8ac379cf5ce68b7efcfd4d1484a6871ee0b
> commit: 0b08fc292842a13aa496413b48c1efb83573b8c6 [4806/10599] rust: introduce module_param module
> config: um-randconfig-001-20251121 (https://download.01.org/0day-ci/archive/20251121/202511210858.uwVivgvn-lkp@intel.com/config)
> compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 9e9fe08b16ea2c4d9867fb4974edf2a3776d6ece)
> rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251121/202511210858.uwVivgvn-lkp@intel.com/reproduce)

We can't reproduce this.

If anyone cares, please let us know how to reproduce it. 

Tested on Debian testing x86_64 host.

rustc --version
rustc 1.91.1 (ed61e7d7e 2025-11-07

/home/dagomez/0day/llvm-22.0.0-e19fa930ca838715028c00c234874d1db4f93154-20250918-184558-x86_64/bin/clang-22 --version
ClangBuiltLinux clang version 22.0.0git (https://github.com/llvm/llvm-project.git e19fa930ca838715028c00c234874d1db4f93154)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/dagomez/0day/llvm-22.0.0-e19fa930ca838715028c00c234874d1db4f93154-20250918-184558-x86_64/bin

  561  wget https://download.01.org/0day-ci/archive/20251121/202511210858.uwVivgvn-lkp@intel.com/config
  563  git clone https://github.com/intel/lkp-tests.git ~/lkp-tests
  565  mkdir -p build_dir && cp config build_dir/.config

  571  COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang-22 ~/lkp-tests/kbuild/make.cross W=1 O=build_dir ARCH=um olddefconfig
  572  COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang-22 ~/lkp-tests/kbuild/make.cross W=1 O=build_dir ARCH=um prepare
  573  COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang-22 ~/lkp-tests/kbuild/make.cross W=1 O=build_dir ARCH=um -j$(nproc)

I'm just getting these warnings:

...
In file included from ../arch/um/include/asm/io.h:24:
../include/asm-generic/io.h:1209:55: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
 1209 |         return (port > MMIO_UPPER_LIMIT) ? NULL : PCI_IOBASE + port;
      |                                                   ~~~~~~~~~~ ^
In file included from ../drivers/gpu/drm/nouveau/nvc0_fence.c:24:
In file included from ../drivers/gpu/drm/nouveau/nouveau_drv.h:42:
In file included from ../drivers/gpu/drm/nouveau/include/nvif/client.h:5:
In file included from ../drivers/gpu/drm/nouveau/include/nvif/object.h:4:
In file included from ../drivers/gpu/drm/nouveau/include/nvif/os.h:8:
In file included from ../include/linux/pci.h:38:
In file included from ../include/linux/interrupt.h:11:
In file included from ../include/linux/hardirq.h:11:
In file included from ../arch/um/include/asm/hardirq.h:5:
In file included from ../include/asm-generic/hardirq.h:17:
In file included from ../include/linux/irq.h:20:
In file included from ../include/linux/io.h:12:
In file included from ../arch/um/include/asm/io.h:24:
../include/asm-generic/io.h:1209:55: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
 1209 |         return (port > MMIO_UPPER_LIMIT) ? NULL : PCI_IOBASE + port;
      |                                                   ~~~~~~~~~~ ^
1 warning generated.
1 warning generated.
1 warning generated.
1 warning generated.
In file included from ../drivers/gpu/drm/nouveau/gv100_fence.c:5:
In file included from ../drivers/gpu/drm/nouveau/nouveau_drv.h:42:
In file included from ../drivers/gpu/drm/nouveau/include/nvif/client.h:5:
In file included from ../drivers/gpu/drm/nouveau/include/nvif/object.h:4:
In file included from ../drivers/gpu/drm/nouveau/include/nvif/os.h:8:
In file included from ../include/linux/pci.h:38:
In file included from ../include/linux/interrupt.h:11:
In file included from ../include/linux/hardirq.h:11:
In file included from ../arch/um/include/asm/hardirq.h:5:
In file included from ../include/asm-generic/hardirq.h:17:
In file included from ../include/linux/irq.h:20:
In file included from ../include/linux/io.h:12:
In file included from ../arch/um/include/asm/io.h:24:
../include/asm-generic/io.h:1209:55: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
 1209 |         return (port > MMIO_UPPER_LIMIT) ? NULL : PCI_IOBASE + port;
      |                                                   ~~~~~~~~~~ ^
1 warning generated.
1 warning generated.
1 warning generated.
1 warning generated.
1 warning generated.
1 warning generated.
1 warning generated.
  AR      drivers/gpu/drm/nouveau/built-in.a
  AR      drivers/gpu/drm/built-in.a
  AR      drivers/gpu/built-in.a
  AR      drivers/built-in.a
  AR      built-in.a
  AR      vmlinux.a
  LD      vmlinux.o
  MODPOST vmlinux.symvers
  CC      .vmlinux.export.o
  UPD     include/generated/utsversion.h
  CC      init/version-timestamp.o
  KSYMS   .tmp_vmlinux0.kallsyms.S
  AS      .tmp_vmlinux0.kallsyms.o
  LD      .tmp_vmlinux1
/usr/bin/ld: warning: .tmp_vmlinux1 has a LOAD segment with RWX permissions
  NM      .tmp_vmlinux1.syms
  KSYMS   .tmp_vmlinux1.kallsyms.S
  AS      .tmp_vmlinux1.kallsyms.o
  LD      .tmp_vmlinux2
/usr/bin/ld: warning: .tmp_vmlinux2 has a LOAD segment with RWX permissions
  NM      .tmp_vmlinux2.syms
  KSYMS   .tmp_vmlinux2.kallsyms.S
  AS      .tmp_vmlinux2.kallsyms.o
  LD      vmlinux.unstripped
/usr/bin/ld: warning: vmlinux.unstripped has a LOAD segment with RWX permissions
  NM      System.map
  OBJCOPY vmlinux
  OBJCOPY modules.builtin.modinfo
  GEN     modules.builtin
  LINK linux
make[1]: Leaving directory '/home/dagomez/ws/c131/kernel/vcs/modules/build_dir'

> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202511210858.uwVivgvn-lkp@intel.com/
> 
> All errors (new ones prefixed by >>):
> 
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `get`
>    --> rust/kernel/module_param.rs:166:13
>    |
>    166 |             get: None,
>    |             ^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    177 | make_param_ops!(PARAM_OPS_U32, u32);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `free`
>    --> rust/kernel/module_param.rs:167:13
>    |
>    167 |             free: None,
>    |             ^^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    177 | make_param_ops!(PARAM_OPS_U32, u32);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `flags`
>    --> rust/kernel/module_param.rs:164:13
>    |
>    164 |             flags: 0,
>    |             ^^^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    178 | make_param_ops!(PARAM_OPS_I64, i64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `set`
>    --> rust/kernel/module_param.rs:165:13
>    |
>    165 |             set: Some(set_param::<$ty>),
>    |             ^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    178 | make_param_ops!(PARAM_OPS_I64, i64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `get`
>    --> rust/kernel/module_param.rs:166:13
>    |
>    166 |             get: None,
>    |             ^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    178 | make_param_ops!(PARAM_OPS_I64, i64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `free`
>    --> rust/kernel/module_param.rs:167:13
>    |
>    167 |             free: None,
>    |             ^^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    178 | make_param_ops!(PARAM_OPS_I64, i64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `flags`
>    --> rust/kernel/module_param.rs:164:13
>    |
>    164 |             flags: 0,
>    |             ^^^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    179 | make_param_ops!(PARAM_OPS_U64, u64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `set`
>    --> rust/kernel/module_param.rs:165:13
>    |
>    165 |             set: Some(set_param::<$ty>),
>    |             ^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    179 | make_param_ops!(PARAM_OPS_U64, u64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `get`
>    --> rust/kernel/module_param.rs:166:13
>    |
>    166 |             get: None,
>    |             ^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    179 | make_param_ops!(PARAM_OPS_U64, u64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `free`
>    --> rust/kernel/module_param.rs:167:13
>    |
>    167 |             free: None,
>    |             ^^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    179 | make_param_ops!(PARAM_OPS_U64, u64);
>    | ----------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> --
>>> error[E0560]: struct `bindings::kernel_param_ops` has no field named `flags`
>    --> rust/kernel/module_param.rs:164:13
>    |
>    164 |             flags: 0,
>    |             ^^^^^ `bindings::kernel_param_ops` does not have this field
>    ...
>    180 | make_param_ops!(PARAM_OPS_ISIZE, isize);
>    | --------------------------------------- in this macro invocation
>    |
>    = note: all struct fields are already assigned
>    = note: this error originates in the macro `make_param_ops` (in Nightly builds, run with -Z macro-backtrace for more info)
> ..
> 

^ permalink raw reply

* Re: [PATCH v9 2/9] crypto: Add ML-DSA/Dilithium verify support
From: Ignat Korchagin @ 2025-11-25 20:51 UTC (permalink / raw)
  To: Eric Biggers
  Cc: David Howells, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <20251125202419.GB3061247@google.com>

On Tue, Nov 25, 2025 at 8:24 PM Eric Biggers <ebiggers@kernel.org> wrote:
>
> On Tue, Nov 25, 2025 at 10:10:18AM +0000, Ignat Korchagin wrote:
> > Hi all,
> >
> > On Mon, Nov 17, 2025 at 5:11 PM Eric Biggers <ebiggers@kernel.org> wrote:
> > >
> > > On Mon, Nov 17, 2025 at 02:55:51PM +0000, David Howells wrote:
> > > >  lib/crypto/Kconfig                            |   1 +
> > > >  lib/crypto/Makefile                           |   2 +
> > > >  lib/crypto/mldsa/Kconfig                      |  29 ++
> > > >  lib/crypto/mldsa/Makefile                     |  20 +
> > > >  lib/crypto/mldsa/crypto_mldsa_44.c            | 166 ++++++++
> > > >  lib/crypto/mldsa/crypto_mldsa_65.c            | 166 ++++++++
> > > >  lib/crypto/mldsa/crypto_mldsa_87.c            | 166 ++++++++
> > > >  lib/crypto/mldsa/dilithium.h                  | 304 ++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_44.c               |  33 ++
> > > >  lib/crypto/mldsa/dilithium_44.h               | 291 ++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_65.c               |  33 ++
> > > >  lib/crypto/mldsa/dilithium_65.h               | 291 ++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_87.c               |  33 ++
> > > >  lib/crypto/mldsa/dilithium_87.h               | 291 ++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_common.c           | 117 ++++++
> > > >  lib/crypto/mldsa/dilithium_debug.h            |  49 +++
> > > >  lib/crypto/mldsa/dilithium_ntt.c              |  89 +++++
> > > >  lib/crypto/mldsa/dilithium_ntt.h              |  35 ++
> > > >  lib/crypto/mldsa/dilithium_pack.h             | 119 ++++++
> > > >  lib/crypto/mldsa/dilithium_poly.c             | 377 ++++++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_poly.h             | 181 +++++++++
> > > >  lib/crypto/mldsa/dilithium_poly_c.h           | 141 +++++++
> > > >  lib/crypto/mldsa/dilithium_poly_common.h      |  35 ++
> > > >  lib/crypto/mldsa/dilithium_polyvec.h          | 343 ++++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_polyvec_c.h        |  81 ++++
> > > >  lib/crypto/mldsa/dilithium_reduce.h           |  85 ++++
> > > >  lib/crypto/mldsa/dilithium_rounding.c         | 128 ++++++
> > > >  lib/crypto/mldsa/dilithium_service_helpers.h  |  99 +++++
> > > >  lib/crypto/mldsa/dilithium_signature_c.c      | 279 +++++++++++++
> > > >  lib/crypto/mldsa/dilithium_signature_c.h      |  37 ++
> > > >  lib/crypto/mldsa/dilithium_signature_impl.h   | 370 +++++++++++++++++
> > > >  lib/crypto/mldsa/dilithium_type.h             | 108 +++++
> > > >  lib/crypto/mldsa/dilithium_zetas.c            |  68 ++++
> > > >  .../mldsa/signature_domain_separation.c       | 204 ++++++++++
> > > >  .../mldsa/signature_domain_separation.h       |  30 ++
> > > >  35 files changed, 4801 insertions(+)
> > >
> > > Over the past week I've been starting to review this massive addition.
> > >
> > > I don't think this is on the right track.  This implementation is really
> > > messy, with lots of unused functionality and unnecessary abstractions,
> > > and code that doesn't follow kernel conventions.
> > >
> > > In comparison, BoringSSL has an entire implementation of ML-DSA,
> > > *including key generation and signing*, in a bit over 3000 lines in one
> > > file.  But about half of that code is specific to key generation or
> > > signing, which the kernel doesn't need, so in principle
> > > verification-only shouldn't be much more than a thousand.  I find it to
> > > be much easier to understand than leancrypto as well.
> > >
> > > Historically we've had a lot of problems with people integrating code
> > > from external sources into the kernel, like mpi, with properly "owning"
> > > it because they feel like it's not their code and someone else is
> > > responsible.  I feel like that's going to be a big problem here.
> > >
> > > I think we can do better here and put together a smaller implementation
> > > for the kernel that we'll actually be able to maintain.
> >
> > I was thinking about this lately for some time - even put forward a
> > small discussion proposal for upcoming Plumbers (which wasn't accepted
> > unfortunately). Should we consider somehow safely "outsourcing" at
> > least some (asymmetric - potentially slow anyway) crypto
> > implementations to userspace? Something similar to
> > request_module/request_firmware/request_key usermode helpers or an
> > io_uring interface or a crypto socket? This way we can bring any
> > well-maintained crypto library (and even multiple ones) to the kernel:
> >   * it can have any software license
> >   * can be written in any programming language
> >   * can use architecture vector instructions more easily
> >   * can have any certifications out of the box (looking at you - FIPS)
> >   * distros would have the ability to plug their own
> >   * maybe it can even do private key crypto (which I personally would
> > really like more support of and it would be acceptable to Herbert)
> >
> > Given the past experience of RSA and mpi - it is not that difficult to
> > port something to the kernel, but quite hard to maintain it over time
> > in a secure manner. With a userspace approach the kernel can
> > piggy-back on proven vendors like any other piece of open source
> > software out there.
> >
> > I understand that there probably still be a need for some in-kernel
> > crypto, so the proposal here is not to fully replace things, but
> > rather complement the current offerings with an interface, which could
> > enable faster adoption of newer (and more secure versions of existing)
> > crypto algorithms.
>
> The performance cost of that would be very high, so it would only have
> any chance at possibly being reasonable for some of the asymmetric
> algorithms.  It would also introduce reliability issues.

Yes, definitely. For the userspace approach I would think only
asymmetric algorithms. For "reliability issues" I don't know if it is
a problem of the kernel itself, or the user/admin of the system. The
kernel supports network-based and userspace-based filesystems and
userspace-based block devices even and users successfully use them as
root filesystems. Surely a small crypto agent running locally would
not be worse than this.

> I'll also note that the main reason that people seem to want private key
> operations in the kernel is for the keyctl() UAPIs for userspace, which
> is already a bad idea.  So I guess we end up with userspace calling into

Can you elaborate on this? (I want to understand for myself). I think
keyctl UAPIs for private keys are a great idea and allows building
good (and relatively simple) secure architectures. So I want to enable
more of this, not less.

> the kernel, which calls back into userspace to use some userspace crypto
> library which the original userspace program refused to use in the first
> place for some reason?  It makes no sense to me, sorry.

It does make sense for userspace programs (apart from potential keyctl
users which don't want to have private keys in their address space),
but what about in-kernel services? How is this fundamentally different
from request_module, for example? If the kernel needs a module, it
tells userspace and there is a userspace helper, which provides it. It
is an "external service" to the kernel. So crypto can be another
service. After all, microkernels have been doing this forever.

> There is the opportunity to share more code with userspace projects at
> the source code level.  Just it doesn't always work out due to different
> languages, licences, requirements, conventions, and implementation
> qualities.  For ML-DSA verification I didn't see a good alternative to
> just writing it myself.  But in other cases a different conclusion could
> be reached.  The kernel uses a lot of the assembly files from

What about sharing code at the binary level? At Cloudflare we have an
internal kernel crypto driver, which just imports its implementation
directly from compiled BoringSSL into the kernel space. So in the end
it is fully in-kernel and fast, but the advantage is that the code
itself comes from BoringSSL (and we regularly update it). There are
rough edges of course (like using vector instructions, different stack
alignment etc), but it kinda works and I think with some investment we
can make such an approach upstream. We even considered open sourcing
it, but not sure about licensing as we effectively have a bespoke
"module loader" for BoringSSL, which is Apache (I hope James is right
that Apache may be considered GPL-2 compatible in the future).

While I think such an approach is much more technically challenging,
it provides "best of both worlds" by having fast in-kernel crypto
directly from established libraries.

> CRYPTOGAMS, for example, and some of the Curve25519 code (including
> formally verified code) is imported from other sources.
>
> - Eric

Ignat

^ permalink raw reply

* Re: [PATCH v9 2/9] crypto: Add ML-DSA/Dilithium verify support
From: Eric Biggers @ 2025-11-25 20:24 UTC (permalink / raw)
  To: Ignat Korchagin
  Cc: David Howells, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <CALrw=nG6X5Opjb1H4VVzCNpJ4QtmHUK3nQ1XQ5GKMvnE9NnZsQ@mail.gmail.com>

On Tue, Nov 25, 2025 at 10:10:18AM +0000, Ignat Korchagin wrote:
> Hi all,
> 
> On Mon, Nov 17, 2025 at 5:11 PM Eric Biggers <ebiggers@kernel.org> wrote:
> >
> > On Mon, Nov 17, 2025 at 02:55:51PM +0000, David Howells wrote:
> > >  lib/crypto/Kconfig                            |   1 +
> > >  lib/crypto/Makefile                           |   2 +
> > >  lib/crypto/mldsa/Kconfig                      |  29 ++
> > >  lib/crypto/mldsa/Makefile                     |  20 +
> > >  lib/crypto/mldsa/crypto_mldsa_44.c            | 166 ++++++++
> > >  lib/crypto/mldsa/crypto_mldsa_65.c            | 166 ++++++++
> > >  lib/crypto/mldsa/crypto_mldsa_87.c            | 166 ++++++++
> > >  lib/crypto/mldsa/dilithium.h                  | 304 ++++++++++++++
> > >  lib/crypto/mldsa/dilithium_44.c               |  33 ++
> > >  lib/crypto/mldsa/dilithium_44.h               | 291 ++++++++++++++
> > >  lib/crypto/mldsa/dilithium_65.c               |  33 ++
> > >  lib/crypto/mldsa/dilithium_65.h               | 291 ++++++++++++++
> > >  lib/crypto/mldsa/dilithium_87.c               |  33 ++
> > >  lib/crypto/mldsa/dilithium_87.h               | 291 ++++++++++++++
> > >  lib/crypto/mldsa/dilithium_common.c           | 117 ++++++
> > >  lib/crypto/mldsa/dilithium_debug.h            |  49 +++
> > >  lib/crypto/mldsa/dilithium_ntt.c              |  89 +++++
> > >  lib/crypto/mldsa/dilithium_ntt.h              |  35 ++
> > >  lib/crypto/mldsa/dilithium_pack.h             | 119 ++++++
> > >  lib/crypto/mldsa/dilithium_poly.c             | 377 ++++++++++++++++++
> > >  lib/crypto/mldsa/dilithium_poly.h             | 181 +++++++++
> > >  lib/crypto/mldsa/dilithium_poly_c.h           | 141 +++++++
> > >  lib/crypto/mldsa/dilithium_poly_common.h      |  35 ++
> > >  lib/crypto/mldsa/dilithium_polyvec.h          | 343 ++++++++++++++++
> > >  lib/crypto/mldsa/dilithium_polyvec_c.h        |  81 ++++
> > >  lib/crypto/mldsa/dilithium_reduce.h           |  85 ++++
> > >  lib/crypto/mldsa/dilithium_rounding.c         | 128 ++++++
> > >  lib/crypto/mldsa/dilithium_service_helpers.h  |  99 +++++
> > >  lib/crypto/mldsa/dilithium_signature_c.c      | 279 +++++++++++++
> > >  lib/crypto/mldsa/dilithium_signature_c.h      |  37 ++
> > >  lib/crypto/mldsa/dilithium_signature_impl.h   | 370 +++++++++++++++++
> > >  lib/crypto/mldsa/dilithium_type.h             | 108 +++++
> > >  lib/crypto/mldsa/dilithium_zetas.c            |  68 ++++
> > >  .../mldsa/signature_domain_separation.c       | 204 ++++++++++
> > >  .../mldsa/signature_domain_separation.h       |  30 ++
> > >  35 files changed, 4801 insertions(+)
> >
> > Over the past week I've been starting to review this massive addition.
> >
> > I don't think this is on the right track.  This implementation is really
> > messy, with lots of unused functionality and unnecessary abstractions,
> > and code that doesn't follow kernel conventions.
> >
> > In comparison, BoringSSL has an entire implementation of ML-DSA,
> > *including key generation and signing*, in a bit over 3000 lines in one
> > file.  But about half of that code is specific to key generation or
> > signing, which the kernel doesn't need, so in principle
> > verification-only shouldn't be much more than a thousand.  I find it to
> > be much easier to understand than leancrypto as well.
> >
> > Historically we've had a lot of problems with people integrating code
> > from external sources into the kernel, like mpi, with properly "owning"
> > it because they feel like it's not their code and someone else is
> > responsible.  I feel like that's going to be a big problem here.
> >
> > I think we can do better here and put together a smaller implementation
> > for the kernel that we'll actually be able to maintain.
> 
> I was thinking about this lately for some time - even put forward a
> small discussion proposal for upcoming Plumbers (which wasn't accepted
> unfortunately). Should we consider somehow safely "outsourcing" at
> least some (asymmetric - potentially slow anyway) crypto
> implementations to userspace? Something similar to
> request_module/request_firmware/request_key usermode helpers or an
> io_uring interface or a crypto socket? This way we can bring any
> well-maintained crypto library (and even multiple ones) to the kernel:
>   * it can have any software license
>   * can be written in any programming language
>   * can use architecture vector instructions more easily
>   * can have any certifications out of the box (looking at you - FIPS)
>   * distros would have the ability to plug their own
>   * maybe it can even do private key crypto (which I personally would
> really like more support of and it would be acceptable to Herbert)
> 
> Given the past experience of RSA and mpi - it is not that difficult to
> port something to the kernel, but quite hard to maintain it over time
> in a secure manner. With a userspace approach the kernel can
> piggy-back on proven vendors like any other piece of open source
> software out there.
> 
> I understand that there probably still be a need for some in-kernel
> crypto, so the proposal here is not to fully replace things, but
> rather complement the current offerings with an interface, which could
> enable faster adoption of newer (and more secure versions of existing)
> crypto algorithms.

The performance cost of that would be very high, so it would only have
any chance at possibly being reasonable for some of the asymmetric
algorithms.  It would also introduce reliability issues.

I'll also note that the main reason that people seem to want private key
operations in the kernel is for the keyctl() UAPIs for userspace, which
is already a bad idea.  So I guess we end up with userspace calling into
the kernel, which calls back into userspace to use some userspace crypto
library which the original userspace program refused to use in the first
place for some reason?  It makes no sense to me, sorry.

There is the opportunity to share more code with userspace projects at
the source code level.  Just it doesn't always work out due to different
languages, licences, requirements, conventions, and implementation
qualities.  For ML-DSA verification I didn't see a good alternative to
just writing it myself.  But in other cases a different conclusion could
be reached.  The kernel uses a lot of the assembly files from
CRYPTOGAMS, for example, and some of the Curve25519 code (including
formally verified code) is imported from other sources.

- Eric

^ permalink raw reply

* Re: [PATCH] gendwarfksyms: Fix build on 32-bit hosts
From: Sami Tolvanen @ 2025-11-25 20:09 UTC (permalink / raw)
  To: Michal Suchánek, Daniel Gomez
  Cc: linux-modules, Luis Chamberlain, Petr Pavlu, linux-kbuild,
	linux-kernel
In-Reply-To: <aRycVOe5ZXSJJFpn@kitsune.suse.cz>

On Tue, Nov 18, 2025 at 8:18 AM Michal Suchánek <msuchanek@suse.de> wrote:
>
> Hello,
>
> On Mon, Nov 17, 2025 at 08:38:07PM +0000, Sami Tolvanen wrote:
> > We have interchangeably used unsigned long for some of the types
> > defined in elfutils, assuming they're always 64-bit. This obviously
> > fails when building gendwarfksyms on 32-bit hosts. Fix the types.
> >
> > Reported-by: Michal Suchánek <msuchanek@suse.de>
> > Closes: https://lore.kernel.org/linux-modules/aRcxzPxtJblVSh1y@kitsune.suse.cz/
> > Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
> > ---
> >  scripts/gendwarfksyms/dwarf.c   | 4 +++-
> >  scripts/gendwarfksyms/symbols.c | 5 +++--
> >  2 files changed, 6 insertions(+), 3 deletions(-)
>
> with this patch gendwarfksyms builds on 32bit x86 and Arm.
>
> Tested-by: Michal Suchánek <msuchanek@suse.de>

Great, thanks for testing!  Daniel, do you want to take this fix
through the modules tree?

Sami

^ permalink raw reply

* Re: [PATCH v9 2/9] crypto: Add ML-DSA/Dilithium verify support
From: Ignat Korchagin @ 2025-11-25 10:10 UTC (permalink / raw)
  To: Eric Biggers
  Cc: David Howells, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <20251117171003.GC1584@sol>

Hi all,

On Mon, Nov 17, 2025 at 5:11 PM Eric Biggers <ebiggers@kernel.org> wrote:
>
> On Mon, Nov 17, 2025 at 02:55:51PM +0000, David Howells wrote:
> >  lib/crypto/Kconfig                            |   1 +
> >  lib/crypto/Makefile                           |   2 +
> >  lib/crypto/mldsa/Kconfig                      |  29 ++
> >  lib/crypto/mldsa/Makefile                     |  20 +
> >  lib/crypto/mldsa/crypto_mldsa_44.c            | 166 ++++++++
> >  lib/crypto/mldsa/crypto_mldsa_65.c            | 166 ++++++++
> >  lib/crypto/mldsa/crypto_mldsa_87.c            | 166 ++++++++
> >  lib/crypto/mldsa/dilithium.h                  | 304 ++++++++++++++
> >  lib/crypto/mldsa/dilithium_44.c               |  33 ++
> >  lib/crypto/mldsa/dilithium_44.h               | 291 ++++++++++++++
> >  lib/crypto/mldsa/dilithium_65.c               |  33 ++
> >  lib/crypto/mldsa/dilithium_65.h               | 291 ++++++++++++++
> >  lib/crypto/mldsa/dilithium_87.c               |  33 ++
> >  lib/crypto/mldsa/dilithium_87.h               | 291 ++++++++++++++
> >  lib/crypto/mldsa/dilithium_common.c           | 117 ++++++
> >  lib/crypto/mldsa/dilithium_debug.h            |  49 +++
> >  lib/crypto/mldsa/dilithium_ntt.c              |  89 +++++
> >  lib/crypto/mldsa/dilithium_ntt.h              |  35 ++
> >  lib/crypto/mldsa/dilithium_pack.h             | 119 ++++++
> >  lib/crypto/mldsa/dilithium_poly.c             | 377 ++++++++++++++++++
> >  lib/crypto/mldsa/dilithium_poly.h             | 181 +++++++++
> >  lib/crypto/mldsa/dilithium_poly_c.h           | 141 +++++++
> >  lib/crypto/mldsa/dilithium_poly_common.h      |  35 ++
> >  lib/crypto/mldsa/dilithium_polyvec.h          | 343 ++++++++++++++++
> >  lib/crypto/mldsa/dilithium_polyvec_c.h        |  81 ++++
> >  lib/crypto/mldsa/dilithium_reduce.h           |  85 ++++
> >  lib/crypto/mldsa/dilithium_rounding.c         | 128 ++++++
> >  lib/crypto/mldsa/dilithium_service_helpers.h  |  99 +++++
> >  lib/crypto/mldsa/dilithium_signature_c.c      | 279 +++++++++++++
> >  lib/crypto/mldsa/dilithium_signature_c.h      |  37 ++
> >  lib/crypto/mldsa/dilithium_signature_impl.h   | 370 +++++++++++++++++
> >  lib/crypto/mldsa/dilithium_type.h             | 108 +++++
> >  lib/crypto/mldsa/dilithium_zetas.c            |  68 ++++
> >  .../mldsa/signature_domain_separation.c       | 204 ++++++++++
> >  .../mldsa/signature_domain_separation.h       |  30 ++
> >  35 files changed, 4801 insertions(+)
>
> Over the past week I've been starting to review this massive addition.
>
> I don't think this is on the right track.  This implementation is really
> messy, with lots of unused functionality and unnecessary abstractions,
> and code that doesn't follow kernel conventions.
>
> In comparison, BoringSSL has an entire implementation of ML-DSA,
> *including key generation and signing*, in a bit over 3000 lines in one
> file.  But about half of that code is specific to key generation or
> signing, which the kernel doesn't need, so in principle
> verification-only shouldn't be much more than a thousand.  I find it to
> be much easier to understand than leancrypto as well.
>
> Historically we've had a lot of problems with people integrating code
> from external sources into the kernel, like mpi, with properly "owning"
> it because they feel like it's not their code and someone else is
> responsible.  I feel like that's going to be a big problem here.
>
> I think we can do better here and put together a smaller implementation
> for the kernel that we'll actually be able to maintain.

I was thinking about this lately for some time - even put forward a
small discussion proposal for upcoming Plumbers (which wasn't accepted
unfortunately). Should we consider somehow safely "outsourcing" at
least some (asymmetric - potentially slow anyway) crypto
implementations to userspace? Something similar to
request_module/request_firmware/request_key usermode helpers or an
io_uring interface or a crypto socket? This way we can bring any
well-maintained crypto library (and even multiple ones) to the kernel:
  * it can have any software license
  * can be written in any programming language
  * can use architecture vector instructions more easily
  * can have any certifications out of the box (looking at you - FIPS)
  * distros would have the ability to plug their own
  * maybe it can even do private key crypto (which I personally would
really like more support of and it would be acceptable to Herbert)

Given the past experience of RSA and mpi - it is not that difficult to
port something to the kernel, but quite hard to maintain it over time
in a secure manner. With a userspace approach the kernel can
piggy-back on proven vendors like any other piece of open source
software out there.

I understand that there probably still be a need for some in-kernel
crypto, so the proposal here is not to fully replace things, but
rather complement the current offerings with an interface, which could
enable faster adoption of newer (and more secure versions of existing)
crypto algorithms.

> - Eric

Ignat

^ permalink raw reply

* Re: [PATCH v9 2/9] crypto: Add ML-DSA/Dilithium verify support
From: Stephan Müller @ 2025-11-25  8:32 UTC (permalink / raw)
  To: David Howells, Eric Biggers
  Cc: Herbert Xu, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel, Lukas Wunner,
	Ignat Korchagin, linux-crypto, keyrings, linux-modules,
	linux-kernel
In-Reply-To: <20251125041050.GA1608@sol>

Am Dienstag, 25. November 2025, 05:10:50 Mitteleuropäische Normalzeit schrieb 
Eric Biggers:

Hi Eric,

> No reply from Stephan yet, so to make sure this doesn't get missed I
> also opened an issue at
> https://github.com/smuellerDD/leancrypto/issues/42

Thanks for the report, fixed.

Ciao
Stephan



^ permalink raw reply

* Re: [PATCH 1/4] lib/crypto: Add ML-DSA verification support
From: Eric Biggers @ 2025-11-25  4:29 UTC (permalink / raw)
  To: David Howells
  Cc: linux-crypto, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <20251121171421.GA1737@sol>

On Fri, Nov 21, 2025 at 09:14:21AM -0800, Eric Biggers wrote:
> However, unfortunately neither source explains it properly, and they
> actually provide incorrect information.  The comment in the reference
> code says the the input can be in "-2^{31}Q <= a <= Q*2^31", which isn't
> quite correct; the upper bound is actually exclusive.  In my code, I
> correctly document the upper bound as being exclusive.

I opened https://github.com/pq-crystals/dilithium/issues/108 against the
reference implementation.  So hopefully that comment will get fixed.

> FIPS 204 documents the same incorrect interval, but then sort of gets
> around it by only claiming that the output is less than 2q in absolute
> value (rather than q) and also by not clarifying whether sign extension
> is done.  They may have thought that sign extension shouldn't be done,
> as you seem to have thought.  Either way, their explanation is
> misleading.  The very-nearly-symmetric version that produces an output
> less than q in absolute value is the logical version when working with
> signed values, and it seems to be what the Dilithium authors intended.

I'm collecting the mistakes that I've found in FIPS 204 into a list,
which I'll send in to NIST as an errata request at some point...

- Eric

^ permalink raw reply

* Re: [PATCH v9 2/9] crypto: Add ML-DSA/Dilithium verify support
From: Eric Biggers @ 2025-11-25  4:10 UTC (permalink / raw)
  To: David Howells
  Cc: Herbert Xu, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, linux-crypto,
	keyrings, linux-modules, linux-kernel
In-Reply-To: <20251121013716.GE3532564@google.com>

On Fri, Nov 21, 2025 at 01:37:16AM +0000, Eric Biggers wrote:
> On Mon, Nov 17, 2025 at 02:55:51PM +0000, David Howells wrote:
> > +/*
> > + * @brief poly_uniform - Sample polynomial with uniformly random coefficients
> > + *			 in [0,Q-1] by performing rejection sampling on the
> > + *			 output stream of SHAKE128(seed|nonce).
> > + *
> > + * @param [out] a pointer to output polynomial
> > + * @param [in] seed byte array with seed of length DILITHIUM_SEEDBYTES
> > + * @param [in] nonce 2-byte nonce
> > + */
> > +void poly_uniform(poly *a, const uint8_t seed[DILITHIUM_SEEDBYTES],
> > +		  __le16 nonce, void *ws_buf)
> > +{
> > +	struct shake_ctx hash_ctx;
> > +	unsigned int i, ctr, off;
> > +	unsigned int buflen = POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCK_SIZE;
> > +	uint8_t *buf = ws_buf;
> > +
> > +	shake128_init(&hash_ctx);
> > +	shake_update(&hash_ctx, seed, DILITHIUM_SEEDBYTES);
> > +	shake_update(&hash_ctx, (uint8_t *)&nonce, sizeof(nonce));
> > +	shake_squeeze(&hash_ctx, buf, buflen);
> > +
> > +	ctr = rej_uniform(a->coeffs, DILITHIUM_N, buf, buflen);
> > +
> > +	while (ctr < DILITHIUM_N) {
> > +		off = buflen % 3;
> > +		for (i = 0; i < off; ++i)
> > +			buf[i] = buf[buflen - off + i];
> > +
> > +		shake_squeeze(&hash_ctx, buf + off, SHAKE128_BLOCK_SIZE);
> > +		buflen = DILITHIUM_SEEDBYTES + off;
> > +		ctr += rej_uniform(a->coeffs + ctr, DILITHIUM_N - ctr, buf,
> > +				   buflen);
> > +	}
> > +
> > +	shake_zeroize_ctx(&hash_ctx);
> > +}
> 
> By the way, the above has a bug.  In the second and later squeezes, it
> squeezes SHAKE128_BLOCK_SIZE (168) bytes, but then it uses only the
> first DILITHIUM_SEEDBYTES (32) bytes.
> 
> Now, that 32 is on top of the 840-byte first squeeze, so there are 872
> correct bytes which is enough for 290 samples.  So an incorrect matrix
> would be generated only if more than 290 samples happen to be required
> to get the 256 coefficients.  q / 2^23 = ~99.9% of coefficients are
> accepted, so that number of rejections would be pretty unlikely.
> 
> Still, it's a bug.  Anyway, we're not going to use this code (we'll use
> my code that does this correctly and in a simpler way), but I thought
> I'd point it out so that Stephan can fix it.  This seems to be a
> "leancrypto" specific bug.
> 
> Note: this feedback should not be taken as implying that I've reviewed
> the entire 4800 lines of code.  I just happened to notice this.

No reply from Stephan yet, so to make sure this doesn't get missed I
also opened an issue at
https://github.com/smuellerDD/leancrypto/issues/42

- Eric

^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: David Howells @ 2025-11-24 20:43 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, Jason A. Donenfeld, Herbert Xu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, linux-crypto,
	keyrings, linux-modules, linux-kernel
In-Reply-To: <20251124202702.GA43598@google.com>

Eric Biggers <ebiggers@kernel.org> wrote:

> It should also become the recommended algorithm anyway, right?

It's used for more than just module signing.  IMA, for example.

David


^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: Eric Biggers @ 2025-11-24 20:27 UTC (permalink / raw)
  To: Jason A. Donenfeld
  Cc: David Howells, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Ard Biesheuvel, Stephan Mueller,
	Lukas Wunner, Ignat Korchagin, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <CAHmME9oK-e2BOXspf89McOFOiq8wp2-QgHsumK6r9kL5FyeMig@mail.gmail.com>

On Mon, Nov 24, 2025 at 09:10:33PM +0100, Jason A. Donenfeld wrote:
> On Mon, Nov 24, 2025 at 8:54 PM David Howells <dhowells@redhat.com> wrote:
> > > > > Still not really sure what the point is.  There's only one user of
> > > > > crypto_sig, and it could just call the ML-DSA functions directly.
> > > >
> > > > Is it your aim to kill off the crypto/ dir and all the (old) crypto API?
> > >
> > > Probably entirely killing off the old API is going to be fraught
> > > because its abstraction has leaked out to userspace. But to the extent
> > > we can minimize its use over time, I think that's a good thing. Even
> > > for crypto usages that generalize to a few different ciphers of one
> > > variety or another, I think being explicit about which ciphers and
> > > having purpose-built dispatchers is usually a better route.
> >
> > How are you proposing handling the autoloading feature of the old API?
> 
> I don't know. Not all features will have direct replacements. Not all
> usages will be replaced. Not all use cases benefit from being
> replaced. You asked if it was an "aim." I replied by telling you that
> I think killing it is going to be difficult, but that over time, usage
> will decline. I think that'll be a natural thing. For now, when
> something uses the library API, there's a pretty easy and obvious case
> to be made for it, as there are still such obvious low hanging use
> cases. I suppose in a while, we might run out of those perhaps. But
> that hasn't happened yet, I guess.
> 
> Jason

+1 to what Jason said.  The traditional API generally isn't going away.
However, there's no need to add new functionality to it when the library
is a better fit.  The crypto_sig abstraction doesn't seem very helpful,
since it was added only recently and has only one user (public_key).

The traditional crypto API's dynamic loading by name also tends to be
more harmful than helpful.  Linux users and distros keep running into
problems where algorithms aren't available in the initramfs when they
should be, or where the slow generic code is used instead of the
optimized code.  The direct linking finally just fixes that.

You may also still be thinking of the ML-DSA code as something
heavyweight.  I reduced it to under 4 KB of object code.

It should also become the recommended algorithm anyway, right?

Either way, 4 KB seems awfully small to be wanting to be dynamically
loaded.  Last year I shrunk the x86_64 AES-GCM code (which most Linux
distros build in to avoid the dynamic loading issues) by over 200 KB.
No one even seemed to care that much...  We can add a lot of stuff
before we're even back to where we were before that.

- Eric

^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: Jason A. Donenfeld @ 2025-11-24 20:10 UTC (permalink / raw)
  To: David Howells
  Cc: Eric Biggers, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Ard Biesheuvel, Stephan Mueller,
	Lukas Wunner, Ignat Korchagin, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <3649785.1764014053@warthog.procyon.org.uk>

On Mon, Nov 24, 2025 at 8:54 PM David Howells <dhowells@redhat.com> wrote:
> > > > Still not really sure what the point is.  There's only one user of
> > > > crypto_sig, and it could just call the ML-DSA functions directly.
> > >
> > > Is it your aim to kill off the crypto/ dir and all the (old) crypto API?
> >
> > Probably entirely killing off the old API is going to be fraught
> > because its abstraction has leaked out to userspace. But to the extent
> > we can minimize its use over time, I think that's a good thing. Even
> > for crypto usages that generalize to a few different ciphers of one
> > variety or another, I think being explicit about which ciphers and
> > having purpose-built dispatchers is usually a better route.
>
> How are you proposing handling the autoloading feature of the old API?

I don't know. Not all features will have direct replacements. Not all
usages will be replaced. Not all use cases benefit from being
replaced. You asked if it was an "aim." I replied by telling you that
I think killing it is going to be difficult, but that over time, usage
will decline. I think that'll be a natural thing. For now, when
something uses the library API, there's a pretty easy and obvious case
to be made for it, as there are still such obvious low hanging use
cases. I suppose in a while, we might run out of those perhaps. But
that hasn't happened yet, I guess.

Jason

^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: David Howells @ 2025-11-24 19:54 UTC (permalink / raw)
  To: Jason A. Donenfeld
  Cc: dhowells, Eric Biggers, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Ard Biesheuvel, Stephan Mueller,
	Lukas Wunner, Ignat Korchagin, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <CAHmME9pPWGKAdm83wKhc3iHCjgZ8gOtZnt=+6x5V6D1prMb2Gw@mail.gmail.com>

Jason A. Donenfeld <Jason@zx2c4.com> wrote:

> > > Still not really sure what the point is.  There's only one user of
> > > crypto_sig, and it could just call the ML-DSA functions directly.
> >
> > Is it your aim to kill off the crypto/ dir and all the (old) crypto API?
> 
> Probably entirely killing off the old API is going to be fraught
> because its abstraction has leaked out to userspace. But to the extent
> we can minimize its use over time, I think that's a good thing. Even
> for crypto usages that generalize to a few different ciphers of one
> variety or another, I think being explicit about which ciphers and
> having purpose-built dispatchers is usually a better route.

How are you proposing handling the autoloading feature of the old API?

David


^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: Jason A. Donenfeld @ 2025-11-24 18:06 UTC (permalink / raw)
  To: David Howells
  Cc: Eric Biggers, Herbert Xu, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Ard Biesheuvel, Stephan Mueller,
	Lukas Wunner, Ignat Korchagin, linux-crypto, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <3647621.1764005088@warthog.procyon.org.uk>

On Mon, Nov 24, 2025 at 6:25 PM David Howells <dhowells@redhat.com> wrote:
>
> Eric Biggers <ebiggers@kernel.org> wrote:
>
> > Still not really sure what the point is.  There's only one user of
> > crypto_sig, and it could just call the ML-DSA functions directly.
>
> Is it your aim to kill off the crypto/ dir and all the (old) crypto API?

Probably entirely killing off the old API is going to be fraught
because its abstraction has leaked out to userspace. But to the extent
we can minimize its use over time, I think that's a good thing. Even
for crypto usages that generalize to a few different ciphers of one
variety or another, I think being explicit about which ciphers and
having purpose-built dispatchers is usually a better route.

Jason

^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: David Howells @ 2025-11-24 17:24 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, Herbert Xu, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, linux-crypto,
	keyrings, linux-modules, linux-kernel
In-Reply-To: <20251124164914.GA6186@sol>

Eric Biggers <ebiggers@kernel.org> wrote:

> Still not really sure what the point is.  There's only one user of
> crypto_sig, and it could just call the ML-DSA functions directly.

Is it your aim to kill off the crypto/ dir and all the (old) crypto API?

Someone (not me) thought it worthwhile removing the akcipher algorithms out of
crypto/asymmetric_keys/ and interfacing to them inside crypto_akcipher and
crypto_sig.

Anyway, I'll continue using crypto_sig as that provides module autoload
capabilities - meaning we don't have to build all the algorithms into the base
kernel.

David


^ permalink raw reply

* RE: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: Elliott, Robert (Servers) @ 2025-11-24 16:58 UTC (permalink / raw)
  To: David Howells
  Cc: Herbert Xu, Eric Biggers, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin,
	linux-crypto@vger.kernel.org, keyrings@vger.kernel.org,
	linux-modules@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <3374841.1763975577@warthog.procyon.org.uk>

> -----Original Message-----
> From: David Howells <dhowells@redhat.com>
> Sent: Monday, November 24, 2025 3:13 AM
> Subject: Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
...
> +++ b/crypto/mldsa.c
...
> +static unsigned int crypto_mldsa_key_size(struct crypto_sig *tfm)
> +{
> +	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
> +
> +	switch (ctx->strength) {
> +	case MLDSA44:
> +		return MLDSA44_PUBLIC_KEY_SIZE;
> +	case MLDSA65:
> +		return MLDSA65_PUBLIC_KEY_SIZE;
> +	case MLDSA87:
> +		return MLDSA87_PUBLIC_KEY_SIZE;
> +	default:
> +		WARN_ON_ONCE(1);
> +		return 0;
> +	}
> +}
...
> +static unsigned int crypto_mldsa_max_size(struct crypto_sig *tfm)
> +{
> +	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
> +
> +	switch (ctx->strength) {
> +	case MLDSA44:
> +		return MLDSA44_SIGNATURE_SIZE;
> +	case MLDSA65:
> +		return MLDSA65_SIGNATURE_SIZE;
> +	case MLDSA87:
> +		return MLDSA87_SIGNATURE_SIZE;
> +	default:
> +		WARN_ON_ONCE(1);
> +		return 0;
> +	}

In case there's any way userspace can trigger those, I'd rather
not have any WARN stack dumps.


^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: Eric Biggers @ 2025-11-24 16:49 UTC (permalink / raw)
  To: David Howells
  Cc: Herbert Xu, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, linux-crypto,
	keyrings, linux-modules, linux-kernel
In-Reply-To: <3374841.1763975577@warthog.procyon.org.uk>

On Mon, Nov 24, 2025 at 09:12:57AM +0000, David Howells wrote:
> diff --git a/crypto/mldsa.c b/crypto/mldsa.c
> new file mode 100644
> index 000000000000..2146c774b5ca
> --- /dev/null
> +++ b/crypto/mldsa.c
> @@ -0,0 +1,201 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * crypto_sig wrapper around ML-DSA library.
> + */

Still not really sure what the point is.  There's only one user of
crypto_sig, and it could just call the ML-DSA functions directly.

- Eric

^ permalink raw reply

* Re: [PATCH v3 0/9] module: Introduce hash-based integrity checking
From: Thomas Weißschuh @ 2025-11-24  9:41 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: James Bottomley, Masahiro Yamada, Nathan Chancellor,
	Arnd Bergmann, Luis Chamberlain, Petr Pavlu, Sami Tolvanen,
	Daniel Gomez, Paul Moore, James Morris, Serge E. Hallyn,
	Jonathan Corbet, Madhavan Srinivasan, Michael Ellerman,
	Nicholas Piggin, Christophe Leroy, Naveen N Rao, Mimi Zohar,
	Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
	Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
	Christian Heusel, Câju Mihai-Drosi, linux-kbuild,
	linux-kernel, linux-arch, linux-modules, linux-security-module,
	linux-doc, linuxppc-dev, linux-integrity
In-Reply-To: <20251123170502.Ai5Ig66Z@breakpoint.cc>

Hi Sebastian,

On 2025-11-23 18:05:02+0100, Sebastian Andrzej Siewior wrote:
> On 2025-11-19 16:48:34 [+0100], Sebastian Andrzej Siewior wrote:
> > I fully agree with this approach. I don't like the big hash array but I
> > have an idea how to optimize that part. So I don't see a problem in the
> > long term.
> 
> The following PoC creates a merkle tree from a set files ending with .ko
> within the specified directory. It will write a .hash files containing
> the required hash for each file for its validation. The root hash is
> saved as "hash_root" and "hash_root.h" in the directory.

Thanks a lot!

> The Debian kernel shipps 4256 modules:
> 
> | $ time ./compute_hashes mods_deb
> | Files 4256 levels: 13 root hash: 97f8f439d63938ed74f48ec46dbd75c2b5e5b49f012a414e89b6f0e0f06efe84
> | 
> | real    0m0,732s
> | user    0m0,304s
> | sys     0m0,427s
> 
> This computes the hashes for all the modules it found in the mods_deb
> folder.
> The kernel needs the root hash (for sha256 32 bytes) and the depth of
> the tree (4 bytes). That are 36 bytes regardless of the number of
> modules that are built.
> In this case, the attached hash for each module is 420 bytes. This is 4
> bytes (position in the tree) + 13 (depth) * 32.
> The verification process requires 13 hash operation to hash through the
> tree and verify against the root hash.

We'll need to store the proof together with the modules somewhere.
Regular module signatures are stored as PKCS#7 and appended to the module
file. If we can also encode the merkle proof as PKCS#7, the integration
into the existing infrastructure should be much easier.
It will require some changes to this series, but honestly the Merkle
tree aproach looks like the clear winner here.

> For convience, the following PoC can also be found at
> 	https://git.kernel.org/pub/scm/linux/kernel/git/bigeasy/mtree-hashed-mods.git/
> 
> which also includes a small testsuite.

(...)


Thomas

^ permalink raw reply

* Re: [PATCH v10 5/8] crypto: Add ML-DSA crypto_sig support
From: David Howells @ 2025-11-24  9:12 UTC (permalink / raw)
  Cc: dhowells, Herbert Xu, Eric Biggers, Luis Chamberlain, Petr Pavlu,
	Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
	Stephan Mueller, Lukas Wunner, Ignat Korchagin, linux-crypto,
	keyrings, linux-modules, linux-kernel
In-Reply-To: <20251120104439.2620205-6-dhowells@redhat.com>

Meh.  I forgot to git add crypto/mldsa.c.

David
---
diff --git a/crypto/Kconfig b/crypto/Kconfig
index bf8b8a60a0c0..45e376af02dc 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -344,6 +344,16 @@ config CRYPTO_ECRDSA
 	  One of the Russian cryptographic standard algorithms (called GOST
 	  algorithms). Only signature verification is implemented.
 
+config CRYPTO_MLDSA
+	tristate "ML-DSA (Module-Lattice-Based Digital Signature Algorithm)"
+	select CRYPTO_SIG
+	select CRYPTO_LIB_MLDSA
+	select CRYPTO_LIB_SHA3
+	help
+	  ML-DSA (Module-Lattice-Based Digital Signature Algorithm) (FIPS-204).
+
+	  Only signature verification is implemented.
+
 endmenu
 
 menu "Block ciphers"
diff --git a/crypto/Makefile b/crypto/Makefile
index 093c56a45d3f..b181f8a54099 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -60,6 +60,8 @@ ecdsa_generic-y += ecdsa-p1363.o
 ecdsa_generic-y += ecdsasignature.asn1.o
 obj-$(CONFIG_CRYPTO_ECDSA) += ecdsa_generic.o
 
+obj-$(CONFIG_CRYPTO_MLDSA) += mldsa.o
+
 crypto_acompress-y := acompress.o
 crypto_acompress-y += scompress.o
 obj-$(CONFIG_CRYPTO_ACOMP2) += crypto_acompress.o
diff --git a/crypto/mldsa.c b/crypto/mldsa.c
new file mode 100644
index 000000000000..2146c774b5ca
--- /dev/null
+++ b/crypto/mldsa.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * crypto_sig wrapper around ML-DSA library.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <crypto/internal/sig.h>
+#include <crypto/mldsa.h>
+
+struct crypto_mldsa_ctx {
+	u8 pk[MAX(MAX(MLDSA44_PUBLIC_KEY_SIZE,
+		      MLDSA65_PUBLIC_KEY_SIZE),
+		  MLDSA87_PUBLIC_KEY_SIZE)];
+	unsigned int pk_len;
+	enum mldsa_alg strength;
+	u8 key_set;
+};
+
+static int crypto_mldsa_sign(struct crypto_sig *tfm,
+			     const void *msg, unsigned int msg_len,
+			     void *sig, unsigned int sig_len)
+{
+	return -EOPNOTSUPP;
+}
+
+static int crypto_mldsa_verify(struct crypto_sig *tfm,
+			       const void *sig, unsigned int sig_len,
+			       const void *msg, unsigned int msg_len)
+{
+	const struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+	if (unlikely(!ctx->key_set))
+		return -EINVAL;
+
+	return mldsa_verify(ctx->strength, sig, sig_len, msg, msg_len,
+			    ctx->pk, ctx->pk_len);
+}
+
+static unsigned int crypto_mldsa_key_size(struct crypto_sig *tfm)
+{
+	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+	switch (ctx->strength) {
+	case MLDSA44:
+		return MLDSA44_PUBLIC_KEY_SIZE;
+	case MLDSA65:
+		return MLDSA65_PUBLIC_KEY_SIZE;
+	case MLDSA87:
+		return MLDSA87_PUBLIC_KEY_SIZE;
+	default:
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+}
+
+static int crypto_mldsa_set_pub_key(struct crypto_sig *tfm,
+				    const void *key, unsigned int keylen)
+{
+	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+	unsigned int expected_len = crypto_mldsa_key_size(tfm);
+
+	if (keylen != expected_len)
+		return -EINVAL;
+
+	ctx->pk_len = keylen;
+	memcpy(ctx->pk, key, keylen);
+	ctx->key_set = true;
+	return 0;
+}
+
+static int crypto_mldsa_set_priv_key(struct crypto_sig *tfm,
+				     const void *key, unsigned int keylen)
+{
+	return -EOPNOTSUPP;
+}
+
+static unsigned int crypto_mldsa_max_size(struct crypto_sig *tfm)
+{
+	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+	switch (ctx->strength) {
+	case MLDSA44:
+		return MLDSA44_SIGNATURE_SIZE;
+	case MLDSA65:
+		return MLDSA65_SIGNATURE_SIZE;
+	case MLDSA87:
+		return MLDSA87_SIGNATURE_SIZE;
+	default:
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+}
+
+static int crypto_mldsa44_alg_init(struct crypto_sig *tfm)
+{
+	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+	ctx->strength = MLDSA44;
+	ctx->key_set = false;
+	return 0;
+}
+
+static int crypto_mldsa65_alg_init(struct crypto_sig *tfm)
+{
+	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+	ctx->strength = MLDSA65;
+	ctx->key_set = false;
+	return 0;
+}
+
+static int crypto_mldsa87_alg_init(struct crypto_sig *tfm)
+{
+	struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+	ctx->strength = MLDSA87;
+	ctx->key_set = false;
+	return 0;
+}
+
+static void crypto_mldsa_alg_exit(struct crypto_sig *tfm)
+{
+}
+
+static struct sig_alg crypto_mldsa_algs[] = {
+	{
+		.sign			= crypto_mldsa_sign,
+		.verify			= crypto_mldsa_verify,
+		.set_pub_key		= crypto_mldsa_set_pub_key,
+		.set_priv_key		= crypto_mldsa_set_priv_key,
+		.key_size		= crypto_mldsa_key_size,
+		.max_size		= crypto_mldsa_max_size,
+		.init			= crypto_mldsa44_alg_init,
+		.exit			= crypto_mldsa_alg_exit,
+		.base.cra_name		= "mldsa44",
+		.base.cra_driver_name	= "mldsa44-lib",
+		.base.cra_ctxsize	= sizeof(struct crypto_mldsa_ctx),
+		.base.cra_module	= THIS_MODULE,
+		.base.cra_priority	= 5000,
+	}, {
+		.sign			= crypto_mldsa_sign,
+		.verify			= crypto_mldsa_verify,
+		.set_pub_key		= crypto_mldsa_set_pub_key,
+		.set_priv_key		= crypto_mldsa_set_priv_key,
+		.key_size		= crypto_mldsa_key_size,
+		.max_size		= crypto_mldsa_max_size,
+		.init			= crypto_mldsa65_alg_init,
+		.exit			= crypto_mldsa_alg_exit,
+		.base.cra_name		= "mldsa65",
+		.base.cra_driver_name	= "mldsa65-lib",
+		.base.cra_ctxsize	= sizeof(struct crypto_mldsa_ctx),
+		.base.cra_module	= THIS_MODULE,
+		.base.cra_priority	= 5000,
+	}, {
+		.sign			= crypto_mldsa_sign,
+		.verify			= crypto_mldsa_verify,
+		.set_pub_key		= crypto_mldsa_set_pub_key,
+		.set_priv_key		= crypto_mldsa_set_priv_key,
+		.key_size		= crypto_mldsa_key_size,
+		.max_size		= crypto_mldsa_max_size,
+		.init			= crypto_mldsa87_alg_init,
+		.exit			= crypto_mldsa_alg_exit,
+		.base.cra_name		= "mldsa87",
+		.base.cra_driver_name	= "mldsa87-lib",
+		.base.cra_ctxsize	= sizeof(struct crypto_mldsa_ctx),
+		.base.cra_module	= THIS_MODULE,
+		.base.cra_priority	= 5000,
+	},
+};
+
+static int __init mldsa_init(void)
+{
+	int ret, i;
+
+	for (i = 0; i < ARRAY_SIZE(crypto_mldsa_algs); i++) {
+		ret = crypto_register_sig(&crypto_mldsa_algs[i]);
+		if (ret < 0)
+			goto error;
+	}
+	return 0;
+
+error:
+	pr_err("Failed to register (%d)\n", ret);
+	for (i--; i >= 0; i--)
+		crypto_unregister_sig(&crypto_mldsa_algs[i]);
+	return ret;
+}
+module_init(mldsa_init);
+
+static void mldsa_exit(void)
+{
+	for (int i = 0; i < ARRAY_SIZE(crypto_mldsa_algs); i++)
+		crypto_unregister_sig(&crypto_mldsa_algs[i]);
+}
+module_exit(mldsa_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Crypto API support for ML-DSA signature verification");
+MODULE_ALIAS_CRYPTO("mldsa44");
+MODULE_ALIAS_CRYPTO("mldsa65");
+MODULE_ALIAS_CRYPTO("mldsa87");


^ permalink raw reply related

* Re: [PATCH v3 7/9] module: Move lockdown check into generic module loader
From: Sebastian Andrzej Siewior @ 2025-11-23 17:10 UTC (permalink / raw)
  To: Paul Moore
  Cc: Thomas Weißschuh, Masahiro Yamada, Nathan Chancellor,
	Arnd Bergmann, Luis Chamberlain, Petr Pavlu, Sami Tolvanen,
	Daniel Gomez, James Morris, Serge E. Hallyn, Jonathan Corbet,
	Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
	Christophe Leroy, Naveen N Rao, Mimi Zohar, Roberto Sassu,
	Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
	Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
	Christian Heusel, Câju Mihai-Drosi, linux-kbuild,
	linux-kernel, linux-arch, linux-modules, linux-security-module,
	linux-doc, linuxppc-dev, linux-integrity
In-Reply-To: <CAHC9VhTuf1u4B3uybZxdojcmz5sFG+_JHUCC=C0N=9gFDmurHg@mail.gmail.com>

On 2025-11-19 14:55:47 [-0500], Paul Moore wrote:
> On Wed, Nov 19, 2025 at 6:20 AM Sebastian Andrzej Siewior
> <bigeasy@linutronix.de> wrote:
> > On 2025-04-29 15:04:34 [+0200], Thomas Weißschuh wrote:
> > > The lockdown check buried in module_sig_check() will not compose well
> > > with the introduction of hash-based module validation.
> >
> > An explanation of why would be nice.
> 
> /me shrugs
> 
> I thought the explanation was sufficient.

Okay. So if it is just me and everyone is well aware then okay.

Sebastian

^ permalink raw reply

* Re: [PATCH v3 0/9] module: Introduce hash-based integrity checking
From: Sebastian Andrzej Siewior @ 2025-11-23 17:05 UTC (permalink / raw)
  To: Thomas Weißschuh
  Cc: James Bottomley, Masahiro Yamada, Nathan Chancellor,
	Arnd Bergmann, Luis Chamberlain, Petr Pavlu, Sami Tolvanen,
	Daniel Gomez, Paul Moore, James Morris, Serge E. Hallyn,
	Jonathan Corbet, Madhavan Srinivasan, Michael Ellerman,
	Nicholas Piggin, Christophe Leroy, Naveen N Rao, Mimi Zohar,
	Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
	Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
	Christian Heusel, Câju Mihai-Drosi, linux-kbuild,
	linux-kernel, linux-arch, linux-modules, linux-security-module,
	linux-doc, linuxppc-dev, linux-integrity
In-Reply-To: <20251119154834.A-tQsLzh@linutronix.de>

On 2025-11-19 16:48:34 [+0100], Sebastian Andrzej Siewior wrote:
> I fully agree with this approach. I don't like the big hash array but I
> have an idea how to optimize that part. So I don't see a problem in the
> long term.

The following PoC creates a merkle tree from a set files ending with .ko
within the specified directory. It will write a .hash files containing
the required hash for each file for its validation. The root hash is
saved as "hash_root" and "hash_root.h" in the directory.

The Debian kernel shipps 4256 modules:

| $ time ./compute_hashes mods_deb
| Files 4256 levels: 13 root hash: 97f8f439d63938ed74f48ec46dbd75c2b5e5b49f012a414e89b6f0e0f06efe84
| 
| real    0m0,732s
| user    0m0,304s
| sys     0m0,427s

This computes the hashes for all the modules it found in the mods_deb
folder.
The kernel needs the root hash (for sha256 32 bytes) and the depth of
the tree (4 bytes). That are 36 bytes regardless of the number of
modules that are built.
In this case, the attached hash for each module is 420 bytes. This is 4
bytes (position in the tree) + 13 (depth) * 32.
The verification process requires 13 hash operation to hash through the
tree and verify against the root hash.

For convience, the following PoC can also be found at
	https://git.kernel.org/pub/scm/linux/kernel/git/bigeasy/mtree-hashed-mods.git/

which also includes a small testsuite.

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000..e4a35c15f0a94
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+CC := gcc
+CFLAGS := -O2 -g -Wall
+LDLIBS := -lcrypto
+
+all: compute_hashes mk-files verify_hash
+test: compute_hashes mk-files verify_hash
+	./verify_test.sh
diff --git a/compute_hashes.c b/compute_hashes.c
new file mode 100644
index 0000000000000..da61b214137b8
--- /dev/null
+++ b/compute_hashes.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Compute hashes for individual files and build a merkle tree.
+ *
+ * Author: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
+ *
+ */
+#define _GNU_SOURCE 1
+#include <ftw.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <openssl/evp.h>
+
+#include "helpers.h"
+
+struct file_entry {
+	char *name;
+	size_t fsize;
+	unsigned int pos;
+	unsigned char hash[EVP_MAX_MD_SIZE];
+};
+
+static struct file_entry *fh_list;
+static size_t num_files;
+
+struct leaf_hash {
+	unsigned char hash[EVP_MAX_MD_SIZE];
+};
+
+struct mtree {
+	struct leaf_hash **l;
+	unsigned int *entries;
+	unsigned int levels;
+};
+
+static unsigned int get_pow2(unsigned int val)
+{
+	return 31 - __builtin_clz(val);
+}
+
+static unsigned int roundup_pow2(unsigned int val)
+{
+	return 1 << (get_pow2(val - 1) + 1);
+}
+
+static unsigned int log2_roundup(unsigned int val)
+{
+	return get_pow2(roundup_pow2(val));
+}
+
+static int str_endswith(const char *s, const char *suffix)
+{
+	size_t ls, lf;
+
+	ls = strlen(s);
+	lf = strlen(suffix);
+
+	if (ls <= lf)
+		return -1;
+	return strcmp(s + ls - lf, suffix);
+}
+
+static void __print_hash(unsigned char *h)
+{
+	int i;
+
+	for (i = 0; i < hash_size; i++)
+		printf("%02x", h[i]);
+}
+
+static void print_hash(unsigned char *h)
+{
+	__print_hash(h);
+	printf("\n");
+}
+
+static int hash_file(struct file_entry *fe)
+{
+	void *mem;
+	int fd;
+
+	fd = open(fe->name, O_RDONLY);
+	if (fd < 0) {
+		printf("Failed to open %s: %m\n", fe->name);
+		exit(1);
+	}
+
+	mem = mmap(NULL, fe->fsize, PROT_READ, MAP_SHARED, fd, 0);
+	close(fd);
+
+	if (mem == MAP_FAILED) {
+		printf("Failed to mmap %s: %m\n", fe->name);
+		exit(1);
+	}
+
+	hash_data(mem, fe->pos, fe->fsize, fe->hash);
+
+	munmap(mem, fe->fsize);
+	return 0;
+}
+
+static int add_files_cb(const char *fpath, const struct stat *sb, int tflag,
+			struct FTW *ftwbuf)
+{
+	if (tflag != FTW_F)
+		return 0;
+
+	if (str_endswith(fpath, ".ko"))
+		return 0;
+
+	fh_list = xrealloc(fh_list, (num_files + 1) * sizeof (struct file_entry));
+
+	fh_list[num_files].name = strdup(fpath);
+	if (!fh_list[num_files].name) {
+		printf("Failed to allocate memory\n");
+		exit(1);
+	}
+
+	fh_list[num_files].fsize = sb->st_size;
+
+	num_files++;
+	return 0;
+}
+
+static int cmp_file_entry(const void *p1, const void *p2)
+{
+	const struct file_entry *f1, *f2;
+
+	f1 = p1;
+	f2 = p2;
+
+	return strcmp(f1->name, f2->name);
+}
+
+static struct mtree *build_merkle(struct file_entry *fh, size_t num)
+{
+	unsigned int i, le;
+	struct mtree *mt;
+
+	mt = xmalloc(sizeof(struct mtree));
+	mt->levels = log2_roundup(num);
+	mt->l = xcalloc(sizeof(struct leaf_hash *), mt->levels);
+
+	mt->entries = xcalloc(sizeof(unsigned int), mt->levels);
+	le = num / 2;
+	if (num & 1)
+		le++;
+	mt->entries[0] = le;
+	mt->l[0] = xcalloc(sizeof(struct leaf_hash), le);
+
+	/* First level of pairs */
+	for (i = 0; i < num; i+= 2) {
+		if (i == num - 1) {
+			/* Odd number of files, no pair. Hash with itself */
+			hash_entry(fh[i].hash, fh[i].hash, mt->l[0][i/2].hash);
+		} else {
+			hash_entry(fh[i].hash, fh[i + 1].hash, mt->l[0][i/2].hash);
+		}
+	}
+	for (i = 1; i < mt->levels; i++) {
+		int n;
+		int odd = 0;
+
+		if (le & 1) {
+			le++;
+			odd++;
+		}
+
+		mt->entries[i] = le / 2;
+		mt->l[i] = xcalloc(sizeof(struct leaf_hash), le);
+
+		for (n = 0; n < le; n += 2) {
+			if (n == le - 2 && odd) {
+				/* Odd number of pairs, no pair. Hash with itself */
+				hash_entry(mt->l[i - 1][n].hash, mt->l[i - 1][n].hash,
+					   mt->l[i][n/2].hash);
+			} else {
+				hash_entry(mt->l[i - 1][n].hash, mt->l[i - 1][n +1].hash,
+					   mt->l[i][n/2].hash);
+			}
+		}
+		le =  mt->entries[i];
+	}
+	return mt;
+}
+
+static void free_mtree(struct mtree *mt)
+{
+	int i;
+
+	for (i = 0; i < mt->levels; i++)
+		free(mt->l[i]);
+
+	free(mt->l);
+	free(mt->entries);
+	free(mt);
+}
+
+static void write_be_int(int fd, unsigned int v)
+{
+	unsigned int be_val = host_to_be32(v);
+
+	if (write(fd, &be_val, sizeof(be_val)) != sizeof(be_val)) {
+		printf("Failed writting to file: %m\n");
+		exit(1);
+	}
+}
+
+static void write_hash(int fd, const void *h)
+{
+	ssize_t wr;
+
+	wr = write(fd, h, hash_size);
+	if (wr != hash_size) {
+		printf("Failed writting to file: %m\n");
+		exit(1);
+	}
+}
+
+static void build_proof(struct mtree *mt, unsigned int n, int fd)
+{
+	unsigned char cur[EVP_MAX_MD_SIZE];
+	unsigned char tmp[EVP_MAX_MD_SIZE];
+	struct file_entry *fe, *fe_sib;
+	unsigned int i;
+
+	fe = &fh_list[n];
+
+	if ((n & 1) == 0) {
+		/* No pair, hash with itself */
+		if (n + 1 == num_files)
+			fe_sib = fe;
+		else
+			fe_sib = &fh_list[n + 1];
+	} else {
+		fe_sib = &fh_list[n - 1];
+	}
+	/* First comes the node position into the file */
+	write_be_int(fd, n);
+
+	if ((n & 1) == 0)
+		hash_entry(fe->hash, fe_sib->hash, cur);
+	else
+		hash_entry(fe_sib->hash, fe->hash, cur);
+
+	/* Next is the sibling hash, followed by hashes in the tree */
+	write_hash(fd, fe_sib->hash);
+
+	for (i = 0; i < mt->levels - 1; i++) {
+		n >>= 1;
+		if ((n & 1) == 0) {
+			void *h;
+
+			/* No pair, hash with itself */
+			if (n + 1 == mt->entries[i])
+				h = cur;
+			else
+				h = mt->l[i][n + 1].hash;
+
+			hash_entry(cur, h, tmp);
+			write_hash(fd, h);
+		} else {
+			hash_entry(mt->l[i][n - 1].hash, cur, tmp);
+			write_hash(fd, mt->l[i][n - 1].hash);
+		}
+		memcpy(cur, tmp, hash_size);
+	}
+
+	 /* After all that, the end hash should match the root hash */
+	if (memcmp(cur, mt->l[mt->levels - 1][0].hash, hash_size))
+		printf("MISS-MATCH\n");
+}
+
+static void write_merkle_root(struct mtree *mt, const char *fp)
+{
+	char buf[1024];
+	int fd;
+
+	if (snprintf(buf, sizeof(buf), "%s/hash_root", fp) >= sizeof(buf)) {
+		printf("Root dir too long\n");
+		exit(1);
+	}
+	fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, DEF_F_PERM);
+	if (fd < 0) {
+		printf("Failed to create %s: %m\n", buf);
+		exit(1);
+	}
+
+	write_be_int(fd, mt->levels);
+	write_hash(fd, mt->l[mt->levels - 1][0].hash);
+	close(fd);
+	printf("Files %ld levels: %d root hash: ", num_files, mt->levels);
+	print_hash(mt->l[mt->levels - 1][0].hash);
+}
+
+static void write_merkle_root_h(struct mtree *mt, const char *fp)
+{
+	char buf[1024];
+	unsigned int i;
+	unsigned char *h;
+	FILE *f;
+
+	if (snprintf(buf, sizeof(buf), "%s/hash_root.h", fp) >= sizeof(buf)) {
+		printf("Root dir too long\n");
+		exit(1);
+	}
+	f = fopen(buf, "w");
+	if (!f) {
+		printf("Failed to create %s: %m\n", buf);
+		exit(1);
+	}
+	h = mt->l[mt->levels - 1][0].hash;
+
+	fprintf(f, "#ifndef __HASH_ROOT_TREE_H__\n");
+	fprintf(f, "#define __HASH_ROOT_TREE_H__\n\n");
+	fprintf(f, "unsigned int hashed_mods_levels = %u;\n", mt->levels);
+	fprintf(f, "unsigned char hashed_mods_root[%d] = {", hash_size);
+	for (i = 0; i < hash_size; i++) {
+		char *space = "";
+
+		if (!(i % 8))
+			fprintf(f, "\n\t");
+
+		if ((i + 1) % 8)
+			space = " ";
+
+		fprintf(f, "0x%02x,%s", h[i], space);
+	}
+	fprintf(f, "\n};\n#endif\n");
+	fclose(f);
+}
+
+int main(int argc, char *argv[])
+{
+	const EVP_MD *hash_evp;
+	char *fp;
+	struct mtree *mt;
+	int i;
+
+	ctx = EVP_MD_CTX_new();
+	if (!ctx)
+		goto err_ossl;
+
+	if (argc != 2) {
+		printf("%s: folder\n", argv[0]);
+		return 1;
+	}
+	fp = argv[1];
+
+	hash_evp = EVP_sha256();
+	hash_size = EVP_MD_get_size(hash_evp);
+	if (hash_size <= 0)
+		goto err_ossl;
+
+	if (EVP_DigestInit_ex(ctx, hash_evp, NULL) != 1)
+		goto err_ossl;
+
+	nftw(fp, add_files_cb, 64, 0);
+
+	qsort(fh_list, num_files, sizeof(struct file_entry), cmp_file_entry);
+
+	for (i = 0; i < num_files; i++) {
+		fh_list[i].pos = i;
+		hash_file(&fh_list[i]);
+	}
+
+	mt = build_merkle(fh_list, num_files);
+	write_merkle_root(mt, fp);
+	write_merkle_root_h(mt, fp);
+	for (i = 0; i < num_files; i++) {
+		char signame[1024];
+		int fd;
+		int ret;
+
+		ret = snprintf(signame, sizeof(signame), "%s.hash", fh_list[i].name);
+		if (ret >= sizeof(signame)) {
+			printf("path + name too long\n");
+			return 1;
+		}
+		fd = open(signame, O_WRONLY | O_CREAT | O_TRUNC, DEF_F_PERM);
+		if (fd < 0) {
+			printf("Can't create %s: %m\n", signame);
+			return 1;
+		}
+		build_proof(mt, i, fd);
+		close(fd);
+	}
+
+	free_mtree(mt);
+	for (i = 0; i < num_files; i++)
+		free(fh_list[i].name);
+	free(fh_list);
+
+	EVP_MD_CTX_free(ctx);
+	return 0;
+
+err_ossl:
+	printf("libssl operation failed\n");
+	return 1;
+}
diff --git a/helpers.h b/helpers.h
new file mode 100644
index 0000000000000..f52ad3543f890
--- /dev/null
+++ b/helpers.h
@@ -0,0 +1,109 @@
+#ifndef __HELPERS_H__
+#define __HELPERS_H__
+
+static EVP_MD_CTX *ctx;
+static int hash_size;
+
+#define DEF_F_PERM (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) /* 0644*/
+#define DEF_D_PERM (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) /* 0755*/
+
+static unsigned int host_to_be32(unsigned int v)
+{
+#if __BYTE_ORDER__ == __LITTLE_ENDIAN
+	return __builtin_bswap32(v);
+#elif __BYTE_ORDER__ == __BIG_ENDIAN
+	return  v;
+#else
+#error Missing endian define
+#endif
+}
+
+static inline void *xcalloc(size_t n, size_t size)
+{
+	void *p;
+
+	p = calloc(n, size);
+	if (p)
+		return p;
+	printf("Memory allocation failed\n");
+	exit(1);
+}
+
+static void *xmalloc(size_t size)
+{
+	void *p;
+
+	p = malloc(size);
+	if (p)
+		return p;
+	printf("Memory allocation failed\n");
+	exit(1);
+}
+
+static inline void *xrealloc(void *oldp, size_t size)
+{
+	void *p;
+
+	p = realloc(oldp, size);
+	if (p)
+		return p;
+	printf("Memory allocation failed\n");
+	exit(1);
+}
+
+static void hash_data(void *p, unsigned int pos, size_t size, void *ret_hash)
+{
+	unsigned char magic = 0x01;
+	unsigned int pos_be;
+
+	pos_be = host_to_be32(pos);
+	if (EVP_DigestInit_ex(ctx, NULL, NULL) != 1)
+		goto err;
+
+	if (EVP_DigestUpdate(ctx, &magic, sizeof(magic)) != 1)
+		goto err;
+
+	if (EVP_DigestUpdate(ctx, &pos_be, sizeof(pos_be)) != 1)
+		goto err;
+
+	if (EVP_DigestUpdate(ctx, p, size) != 1)
+		goto err;
+
+	if (EVP_DigestFinal_ex(ctx, ret_hash, NULL) != 1)
+		goto err;
+
+	return;
+
+err:
+	printf("libssl operation failed\n");
+	exit(1);
+}
+static void hash_entry(void *left, void *right, void *ret_hash)
+{
+	unsigned char magic = 0x02;
+
+	if (EVP_DigestInit_ex(ctx, NULL, NULL) != 1)
+		goto err;
+
+	if (EVP_DigestUpdate(ctx, &magic, sizeof(magic)) != 1)
+		goto err;
+
+	if (EVP_DigestUpdate(ctx, left, hash_size) != 1)
+		goto err;
+
+	if (EVP_DigestUpdate(ctx, right, hash_size) != 1)
+		goto err;
+
+	if (EVP_DigestFinal_ex(ctx, ret_hash, NULL) != 1)
+		goto err;
+
+	return;
+
+err:
+	printf("libssl operation failed\n");
+	exit(1);
+}
+
+
+
+#endif
diff --git a/verify_hash.c b/verify_hash.c
new file mode 100644
index 0000000000000..0a842f27f1ebc
--- /dev/null
+++ b/verify_hash.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Verify a file against and its hash against a merkle tree hash.
+ *
+ * Author: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
+ *
+ */
+#define _GNU_SOURCE 1
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <openssl/evp.h>
+
+#include "helpers.h"
+
+struct hash_root {
+	unsigned int level;
+	unsigned char hash[EVP_MAX_MD_SIZE];
+};
+
+struct verify_sig {
+	unsigned int pos;
+	char hash_sigs[];
+};
+
+static int hash_file(const char *f, unsigned char *hash, unsigned int pos)
+{
+	struct stat sb;
+	int fd, ret;
+	void *mem;
+
+	fd = open(f, O_RDONLY);
+	if (fd < 0) {
+		printf("Failed to open %s: %m\n", f);
+		exit(1);
+	}
+
+	ret = fstat(fd, &sb);
+	if (ret) {
+		printf("stat failed %m\n");
+		exit(1);
+	}
+
+	mem = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+	close(fd);
+
+	if (mem == MAP_FAILED) {
+		printf("Failed to mmap file: %m\n");
+		exit(1);
+	}
+
+	hash_data(mem, pos, sb.st_size, hash);
+
+	munmap(mem, sb.st_size);
+	return 0;
+}
+
+static void verify_hash(struct hash_root *hr, struct verify_sig *vs, unsigned char *cur,
+			const char *fn)
+{
+	unsigned char tmp[EVP_MAX_MD_SIZE];
+	unsigned sig_ofs = 0;
+	unsigned int i, n;
+
+	n = vs->pos;
+	if ((n & 1) == 0)
+		hash_entry(cur, &vs->hash_sigs[sig_ofs], tmp);
+	else
+		hash_entry(&vs->hash_sigs[sig_ofs], cur, tmp);
+
+	memcpy(cur, tmp, hash_size);
+	sig_ofs += hash_size;
+	for (i = 0; i < hr->level - 1; i++) {
+		n >>= 1;
+		if ((n & 1) == 0) {
+			hash_entry(cur, &vs->hash_sigs[sig_ofs], tmp);
+		} else {
+			hash_entry(&vs->hash_sigs[sig_ofs], cur, tmp);
+		}
+		memcpy(cur, tmp, hash_size);
+		sig_ofs += hash_size;
+	}
+
+	if (!memcmp(cur, hr->hash, hash_size)) {
+		exit(0);
+	} else {
+		printf("MISS-MATCH on %s\n", fn);
+		exit(1);
+	}
+}
+
+static void read_be_int(int fd, unsigned int *val)
+{
+	unsigned int val_be;
+
+	if (read(fd, &val_be, sizeof(val_be)) != sizeof(val_be)) {
+		printf("Can't read from file\n");
+		exit(1);
+	}
+	*val = host_to_be32(val_be);
+}
+
+struct hash_root *read_root_hash(const char *f)
+{
+	int fd;
+	struct hash_root *hr;
+
+	hr = xmalloc(sizeof(*hr));
+	fd = open(f, O_RDONLY);
+	if (fd < 0) {
+		printf("Can't open %s: %m\n", f);
+		exit(1);
+	}
+	read_be_int(fd, &hr->level);
+	if (read(fd, hr->hash, hash_size) != hash_size) {
+		printf("Can't read complete hash (%u): %m\n",
+		       hash_size);
+		exit(1);
+	}
+	close(fd);
+	return hr;
+}
+
+static void load_hash_sig(const char *f, struct verify_sig *verify_sig,
+			  unsigned int sig_num)
+{
+	ssize_t total_hash_size;
+	struct stat sb;
+	char buf[1024];
+	int fd;
+	int ret;
+
+	total_hash_size = sig_num * hash_size;
+
+	ret = snprintf(buf, sizeof(buf), "%s.hash", f);
+	if (ret >= sizeof(buf)) {
+		printf("Too long\n");
+		exit(1);
+	}
+	fd = open(buf, O_RDONLY);
+	if (fd < 0) {
+		printf("Failed to open %s\n", buf);
+		exit(1);
+	}
+	read_be_int(fd, &verify_sig->pos);
+
+	ret = fstat(fd, &sb);
+	if (ret < 0) {
+		printf("Failed to stat %s: %m\n", f);
+		exit(1);
+	}
+
+	if (sb.st_size != total_hash_size + 4) {
+		printf("Unexpected signature size: Expected %ld vs found %ld\n",
+		       total_hash_size + 4, sb.st_size);
+		exit(1);
+	}
+	if (read(fd, verify_sig->hash_sigs, total_hash_size) != total_hash_size) {
+		printf("Failed to read the signature: %m\n");
+		exit(1);
+	}
+	close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+	struct hash_root *hash_root;
+	struct verify_sig *vsig;
+	unsigned char fhash[EVP_MAX_MD_SIZE];
+	const EVP_MD *hash_evp;
+
+	ctx = EVP_MD_CTX_new();
+	if (!ctx)
+		goto err;
+
+	if (argc != 3) {
+		printf("%s: hash_root module\n", argv[0]);
+		return 1;
+	}
+
+	hash_evp = EVP_sha256();
+	hash_size = EVP_MD_get_size(hash_evp);
+	if (hash_size <= 0)
+		goto err;
+
+	if (EVP_DigestInit_ex(ctx, hash_evp, NULL) != 1)
+		goto err;
+
+	hash_root = read_root_hash(argv[1]);
+	vsig = xmalloc(sizeof(struct verify_sig) + hash_root->level * hash_size);
+
+	load_hash_sig(argv[2], vsig, hash_root->level);
+	hash_file(argv[2], fhash, vsig->pos);
+	verify_hash(hash_root, vsig, fhash, argv[2]);
+
+	EVP_MD_CTX_free(ctx);
+	return 0;
+err:
+	printf("libssl operation failed\n");
+	return 1;
+}
-- 
2.51.0


Sebastian

^ permalink raw reply related

* Re: [PATCH 1/4] lib/crypto: Add ML-DSA verification support
From: Eric Biggers @ 2025-11-21 22:48 UTC (permalink / raw)
  To: Lukas Wunner
  Cc: David Howells, linux-crypto, Herbert Xu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld,
	Ard Biesheuvel, Stephan Mueller, Ignat Korchagin, keyrings,
	linux-modules, linux-kernel
In-Reply-To: <aSDnvNGos9qM2Uj5@wunner.de>

On Fri, Nov 21, 2025 at 11:29:16PM +0100, Lukas Wunner wrote:
> On Fri, Nov 21, 2025 at 10:23:09PM +0000, Eric Biggers wrote:
> > That list actually includes the same three files that use -EKEYREJECTED.
> > It looks like if the signature verification fails "early" it's -EBADMSG,
> > whereas if it fails "late" it's -EKEYREJECTED?
> 
> -EBADMSG denotes malformed data (e.g. incorrectly formatted ASN.1 payload).
> 
> -EKEYREJECTED denotes a well-formed, but incorrect signature (e.g. made
> by a wrong key).
> 
> I think it's important and useful to be able to differentiate that.

I guess.  The pseudocode in the ML-DSA specification is clear that
signature verification returns a boolean, regardless of whether the
signature is invalid due to the ctilde check, the coefficients of the
reponse vector being out of range, or the encoded hint vector being
malformed.  But if we really think it's useful we could disregard that
and use EKEYREJECTED for the ctilde check and EBADMSG for the other
cases.  I think that would align with what you're suggesting.  This is
inconsistent with the kernel's symmetric crypto code, but oh well.

- Eric

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox