new file mode 100644
@@ -0,0 +1,316 @@
+From 024391fc505ebf9c47e51953d7d85995ba179576 Mon Sep 17 00:00:00 2001
+From: NIIBE Yutaka <gniibe@fsij.org>
+Date: Mon, 13 Apr 2026 11:51:46 +0900
+Subject: [PATCH] cipher:dilithium: Check the label length by caller.
+
+* cipher/dilithium.h (dilithium_keypair, dilithium_sign)
+(dilithium_verify): Return gpg_err_code_t.
+* cipher/dilithium.c (dilithium_keypair): Return gpg_err_code_t.
+(dilithium_sign, dilithium_verify): Ditto. Check CTXLEN.
+* cipher/pubkey-dilithium.c (mldsa_generate): Follow the change.
+(mldsa_sign, mldsa_verify): Likewise.
+
+--
+
+Reported by Calif.io in collaboration with Claude and Anthropic
+Research.
+
+GnuPG-bug-id: 8208
+Co-authored-by: Thai Duong <thai@calif.io>
+Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
+
+CVE: CVE-2026-41990
+Upstream-Status: Backport [https://github.com/gpg/libgcrypt/commit/905e00f046a71e5670517779afaf85a354952832]
+
+Signed-off-by: Leonid Iziumtsev <leonid.iziumtsev@est.tech>
+---
+ cipher/dilithium.c | 101 ++++++++++++++++++++++++++++----------
+ cipher/dilithium.h | 20 ++++----
+ cipher/pubkey-dilithium.c | 34 +++++--------
+ 3 files changed, 99 insertions(+), 56 deletions(-)
+
+diff --git a/cipher/dilithium.c b/cipher/dilithium.c
+index 955feb2a..212c4afe 100644
+--- a/cipher/dilithium.c
++++ b/cipher/dilithium.c
+@@ -82,6 +82,7 @@
+ #include "gcrypt-int.h"
+ #include "const-time.h"
+
++/* With glue code, we only use the "_internal" API of Dilithium. */
+ #define DILITHIUM_INTERNAL_API_ONLY 1
+
+ #include "dilithium.h"
+@@ -120,23 +121,33 @@ static int crypto_sign_verify_internal_5 (const uint8_t *sig, size_t siglen,
+ const uint8_t *pre, size_t prelen,
+ const uint8_t *pk);
+
+-int
++gpg_err_code_t
+ dilithium_keypair (int algo, uint8_t *pk, uint8_t *sk,
+ const uint8_t seed[SEEDBYTES])
+ {
++ int r;
++
+ switch (algo)
+ {
+ case GCRY_MLDSA44:
+- return crypto_sign_keypair_internal_2 (pk, sk, seed);
++ r = crypto_sign_keypair_internal_2 (pk, sk, seed);
++ break;
+ case GCRY_MLDSA65:
+ default:
+- return crypto_sign_keypair_internal_3 (pk, sk, seed);
++ r = crypto_sign_keypair_internal_3 (pk, sk, seed);
++ break;
+ case GCRY_MLDSA87:
+- return crypto_sign_keypair_internal_5 (pk, sk, seed);
++ r = crypto_sign_keypair_internal_5 (pk, sk, seed);
++ break;
+ }
++
++ if (r < 0)
++ return GPG_ERR_INTERNAL;
++
++ return 0;
+ }
+
+-int
++gpg_err_code_t
+ dilithium_sign (int algo, uint8_t *sig, size_t siglen,
+ const uint8_t *m, size_t mlen,
+ const uint8_t *ctx, size_t ctxlen,
+@@ -145,9 +156,17 @@ dilithium_sign (int algo, uint8_t *sig, size_t siglen,
+ size_t i;
+ uint8_t pre[257];
+ size_t prelen;
++ int r;
+
+- if (ctx == NULL && ctxlen == -1)
+- prelen = 0;
++ if (ctx == NULL)
++ {
++ if (ctxlen == -1)
++ prelen = 0;
++ else
++ return GPG_ERR_INV_DATA;
++ }
++ else if (ctxlen > 255)
++ return GPG_ERR_INV_DATA;
+ else
+ {
+ /* Prepare pre = (0, ctxlen, ctx) */
+@@ -158,28 +177,44 @@ dilithium_sign (int algo, uint8_t *sig, size_t siglen,
+ prelen = 2 + ctxlen;
+ }
+
++ /*
++ * Note that the second argument of the upstream routine is the
++ * pointer to output length of signature. It assumes the first
++ * argument (pointer to output signature) should have correct (or
++ * more) length, beforehand.
++ *
++ * Before calling the routine, we should check the length.
++ */
+ switch (algo)
+ {
+ case GCRY_MLDSA44:
+ if (siglen != CRYPTO_BYTES_2)
+- return -1;
+- return crypto_sign_signature_internal_2 (sig, &siglen, m, mlen,
+- pre, prelen, rnd, sk);
++ return GPG_ERR_INV_DATA;
++ r = crypto_sign_signature_internal_2 (sig, &siglen, m, mlen,
++ pre, prelen, rnd, sk);
++ break;
+ case GCRY_MLDSA65:
+ default:
+ if (siglen != CRYPTO_BYTES_3)
+- return -1;
+- return crypto_sign_signature_internal_3 (sig, &siglen, m, mlen,
+- pre, prelen, rnd, sk);
++ return GPG_ERR_INV_DATA;
++ r = crypto_sign_signature_internal_3 (sig, &siglen, m, mlen,
++ pre, prelen, rnd, sk);
++ break;
+ case GCRY_MLDSA87:
+ if (siglen != CRYPTO_BYTES_5)
+- return -1;
+- return crypto_sign_signature_internal_5 (sig, &siglen, m, mlen,
+- pre, prelen, rnd, sk);
++ return GPG_ERR_INV_DATA;
++ r = crypto_sign_signature_internal_5 (sig, &siglen, m, mlen,
++ pre, prelen, rnd, sk);
++ break;
+ }
++
++ if (r < 0)
++ return GPG_ERR_INTERNAL;
++
++ return 0;
+ }
+
+-int
++gpg_err_code_t
+ dilithium_verify (int algo, const uint8_t *sig, size_t siglen,
+ const uint8_t *m, size_t mlen,
+ const uint8_t *ctx, size_t ctxlen,
+@@ -188,9 +223,17 @@ dilithium_verify (int algo, const uint8_t *sig, size_t siglen,
+ size_t i;
+ uint8_t pre[257];
+ size_t prelen;
++ int r;
+
+- if (ctx == NULL && ctxlen == -1)
+- prelen = 0;
++ if (ctx == NULL)
++ {
++ if (ctxlen == -1)
++ prelen = 0;
++ else
++ return GPG_ERR_INV_DATA;
++ }
++ else if (ctxlen > 255)
++ return GPG_ERR_INV_DATA;
+ else
+ {
+ /* Prepare pre = (0, ctxlen, ctx) */
+@@ -204,16 +247,24 @@ dilithium_verify (int algo, const uint8_t *sig, size_t siglen,
+ switch (algo)
+ {
+ case GCRY_MLDSA44:
+- return crypto_sign_verify_internal_2 (sig, siglen, m, mlen,
+- pre, prelen, pk);
++ r = crypto_sign_verify_internal_2 (sig, siglen, m, mlen,
++ pre, prelen, pk);
++ break;
+ case GCRY_MLDSA65:
+ default:
+- return crypto_sign_verify_internal_3 (sig, siglen, m, mlen,
+- pre, prelen, pk);
++ r = crypto_sign_verify_internal_3 (sig, siglen, m, mlen,
++ pre, prelen, pk);
++ break;
+ case GCRY_MLDSA87:
+- return crypto_sign_verify_internal_5 (sig, siglen, m, mlen,
+- pre, prelen, pk);
++ r = crypto_sign_verify_internal_5 (sig, siglen, m, mlen,
++ pre, prelen, pk);
++ break;
+ }
++
++ if (r < 0)
++ return GPG_ERR_BAD_SIGNATURE;
++
++ return 0;
+ }
+
+ typedef struct {
+diff --git a/cipher/dilithium.h b/cipher/dilithium.h
+index 88a48094..dc18f158 100644
+--- a/cipher/dilithium.h
++++ b/cipher/dilithium.h
+@@ -64,16 +64,16 @@
+ #define DILITHIUM_SIGN_STACK_BURN (161 * 1024)
+ #define DILITHIUM_VERIFY_STACK_BURN (122 * 1024)
+
+-int dilithium_keypair (int algo, uint8_t *pk, uint8_t *sk,
+- const uint8_t seed[SEEDBYTES]);
+-int dilithium_sign (int algo, uint8_t *sig, size_t siglen,
+- const uint8_t *m, size_t mlen,
+- const uint8_t *ctx, size_t ctxlen,
+- const uint8_t *sk, const uint8_t rnd[RNDBYTES]);
+-int dilithium_verify (int algo, const uint8_t *sig, size_t siglen,
+- const uint8_t *m, size_t mlen,
+- const uint8_t *ctx, size_t ctxlen,
+- const uint8_t *pk);
++gpg_err_code_t dilithium_keypair (int algo, uint8_t *pk, uint8_t *sk,
++ const uint8_t seed[SEEDBYTES]);
++gpg_err_code_t dilithium_sign (int algo, uint8_t *sig, size_t siglen,
++ const uint8_t *m, size_t mlen,
++ const uint8_t *ctx, size_t ctxlen,
++ const uint8_t *sk, const uint8_t rnd[RNDBYTES]);
++gpg_err_code_t dilithium_verify (int algo, const uint8_t *sig, size_t siglen,
++ const uint8_t *m, size_t mlen,
++ const uint8_t *ctx, size_t ctxlen,
++ const uint8_t *pk);
+ #endif
+
+ #if defined(DILITHIUM_MODE)
+diff --git a/cipher/pubkey-dilithium.c b/cipher/pubkey-dilithium.c
+index 03958bb0..8c3f650e 100644
+--- a/cipher/pubkey-dilithium.c
++++ b/cipher/pubkey-dilithium.c
+@@ -170,7 +170,7 @@ mldsa_generate (const gcry_sexp_t genparms, gcry_sexp_t *r_skey)
+ memcpy (seed, seed_supplied, SEEDBYTES);
+ }
+
+- dilithium_keypair (info->algo, pk, sk, seed);
++ rc = dilithium_keypair (info->algo, pk, sk, seed);
+ _gcry_burn_stack (DILITHIUM_KEYPAIR_STACK_BURN);
+
+ if (!rc)
+@@ -206,7 +206,6 @@ mldsa_sign (gcry_sexp_t *r_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms)
+ size_t data_len;
+ const unsigned char *sk;
+ const struct mldsa_info *info = mldsa_get_info (keyparms);
+- int r;
+
+ if (!info)
+ return GPG_ERR_PUBKEY_ALGO;
+@@ -258,17 +257,14 @@ mldsa_sign (gcry_sexp_t *r_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms)
+ else
+ randombytes (rnd, RNDBYTES);
+ if (ctx.flags & PUBKEY_FLAG_NO_PREFIX)
+- r = dilithium_sign (info->algo, sig, info->sig_len, data, data_len,
+- NULL, -1, sk, rnd);
++ rc = dilithium_sign (info->algo, sig, info->sig_len, data, data_len,
++ NULL, -1, sk, rnd);
+ else
+- r = dilithium_sign (info->algo, sig, info->sig_len, data, data_len,
+- ctx.label, ctx.labellen, sk, rnd);
++ rc = dilithium_sign (info->algo, sig, info->sig_len, data, data_len,
++ ctx.label, ctx.labellen, sk, rnd);
+ _gcry_burn_stack (DILITHIUM_SIGN_STACK_BURN);
+- if (r < 0)
+- {
+- rc = GPG_ERR_INTERNAL;
+- goto leave;
+- }
++ if (rc)
++ goto leave;
+
+ rc = sexp_build (r_sig, NULL, "(sig-val(%s(s%b)))", info->name,
+ info->sig_len, sig);
+@@ -300,7 +296,6 @@ mldsa_verify (gcry_sexp_t s_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms)
+ size_t data_len;
+ const unsigned char *pk;
+ const struct mldsa_info *info = mldsa_get_info (keyparms);
+- int r;
+
+ if (!info)
+ return GPG_ERR_PUBKEY_ALGO;
+@@ -350,17 +345,14 @@ mldsa_verify (gcry_sexp_t s_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms)
+ }
+
+ if (ctx.flags & PUBKEY_FLAG_NO_PREFIX)
+- r = dilithium_verify (info->algo, sig, info->sig_len, data, data_len,
+- NULL, -1, pk);
++ rc = dilithium_verify (info->algo, sig, info->sig_len, data, data_len,
++ NULL, -1, pk);
+ else
+- r = dilithium_verify (info->algo, sig, info->sig_len, data, data_len,
+- ctx.label, ctx.labellen, pk);
++ rc = dilithium_verify (info->algo, sig, info->sig_len, data, data_len,
++ ctx.label, ctx.labellen, pk);
+ _gcry_burn_stack (DILITHIUM_VERIFY_STACK_BURN);
+- if (r < 0)
+- {
+- rc = GPG_ERR_BAD_SIGNATURE;
+- goto leave;
+- }
++ if (rc)
++ goto leave;
+
+ leave:
+ _gcry_pk_util_free_encoding_ctx (&ctx);
@@ -26,6 +26,7 @@ SRC_URI = "${GNUPG_MIRROR}/libgcrypt/libgcrypt-${PV}.tar.bz2 \
file://no-bench-slope.patch \
file://run-ptest \
file://CVE-2026-41989.patch \
+ file://CVE-2026-41990.patch \
"
SRC_URI[sha256sum] = "7df5c08d952ba33f9b6bdabdb06a61a78b2cf62d2122c2d1d03a91a79832aa3c"
Backport patch to fix CVE-2026-41990. References: https://nvd.nist.gov/vuln/detail/CVE-2026-41990 https://osv.dev/list?q=CVE-2026-41990 Upstream fix: https://github.com/gpg/libgcrypt/commit/905e00f046a71e5670517779afaf85a354952832 Signed-off-by: Leonid Iziumtsev <leonid.iziumtsev@est.tech> --- .../libgcrypt/files/CVE-2026-41990.patch | 316 ++++++++++++++++++ .../libgcrypt/libgcrypt_1.12.1.bb | 1 + 2 files changed, 317 insertions(+) create mode 100644 meta/recipes-support/libgcrypt/files/CVE-2026-41990.patch