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);
