Commit eea8db4124 for openssl.org

commit eea8db4124f765d20b8699994cd3b57a54d74f2b
Author: Simo Sorce <simo@redhat.com>
Date:   Mon Dec 15 11:23:57 2025 -0500

    Harden digest context deserialization

    The deserialization functions for SHA2 and SHA3 digest contexts did not
    sufficiently validate the incoming data. Corruption in transmission or
    on saved disk data could cause a out-of-bounds memory access if buffer
    sizes did not match expected values.

    Add sanity checks to the SHA2 and SHA3 deserialization functions to validate
    buffer-related fields before they are used. The serialization format for these
    digests has been changed to place these critical fields early in the stream to
    enable this validation.

    Additionally, add a note to the EVP_DigestInit man page to warn users that
    deserialization should only be performed on trusted data. The checks we
    implement are not meant to address processing of untrusted data
    maliciously crafted by an attacker.

    Application that need to store data or transmit it through untrusted
    media SHOULD implement proper encryption and message authentication
    on their own using things like CMS or other appropriate secure message
    containers.

    These check have been added also to quiet a bit security researchers
    that try to find any way to claim CVE bounties even in completely
    unlikely or invalid scenarios.

    Signed-off-by: Simo Sorce <simo@redhat.com>

    Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
    Reviewed-by: Tomas Mraz <tomas@openssl.org>
    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    (Merged from https://github.com/openssl/openssl/pull/29404)

diff --git a/doc/man3/EVP_DigestInit.pod b/doc/man3/EVP_DigestInit.pod
index 14d4f7fa9c..54a9a92f7c 100644
--- a/doc/man3/EVP_DigestInit.pod
+++ b/doc/man3/EVP_DigestInit.pod
@@ -340,6 +340,11 @@ different OpenSSL version and does not guarantee interoperability between
 different providers. Some providers may not allow export/import across
 process boundaries.

+NOTE: Applications must guarantee that only trusted data is used during
+deserialization. The deserialization operation is not built to address
+adversarial data compromise, and only basic checks to protect from simple
+mistakes are implemented.
+
 =item EVP_MD_CTX_dup()

 Can be used to duplicate the message digest state from I<in>.  This is useful
diff --git a/providers/implementations/digests/sha2_prov.c b/providers/implementations/digests/sha2_prov.c
index 31a6b85450..c75b6d9b3f 100644
--- a/providers/implementations/digests/sha2_prov.c
+++ b/providers/implementations/digests/sha2_prov.c
@@ -58,10 +58,10 @@ static const unsigned char sha256magic[] = "SHA256v1";
     (                                                 \
         SHA256MAGIC_LEN /* magic */                   \
         + sizeof(uint32_t) /* c->md_len */            \
+        + sizeof(uint32_t) /* c->num */               \
         + sizeof(uint32_t) * 8 /* c->h */             \
         + sizeof(uint32_t) * 2 /* c->Nl + c->Nh */    \
         + sizeof(uint32_t) * SHA_LBLOCK /* c->data */ \
-        + sizeof(uint32_t) /* c->num */               \
     )

 static int SHA256_Serialize(SHA256_CTX *c, unsigned char *out,
@@ -90,6 +90,9 @@ static int SHA256_Serialize(SHA256_CTX *c, unsigned char *out,
     /* md_len */
     p = OPENSSL_store_u32_le(p, c->md_len);

+    /* num */
+    p = OPENSSL_store_u32_le(p, c->num);
+
     /* h */
     for (i = 0; i < sizeof(c->h) / sizeof(SHA_LONG); i++)
         p = OPENSSL_store_u32_le(p, c->h[i]);
@@ -102,15 +105,17 @@ static int SHA256_Serialize(SHA256_CTX *c, unsigned char *out,
     for (i = 0; i < SHA_LBLOCK; i++)
         p = OPENSSL_store_u32_le(p, c->data[i]);

-    /* num */
-    p = OPENSSL_store_u32_le(p, c->num);
-
     if (outlen != NULL)
         *outlen = SHA256_SERIALIZATION_LEN;

     return 1;
 }

+/*
+ * This function only performs basic input sanity checks and is not
+ * built to handle malicious input data. Only trusted input should be
+ * fed to this function
+ */
 static int SHA256_Deserialize(SHA256_CTX *c, const unsigned char *in,
     size_t inlen)
 {
@@ -133,6 +138,12 @@ static int SHA256_Deserialize(SHA256_CTX *c, const unsigned char *in,
         return 0;
     }

+    /* num check */
+    p = OPENSSL_load_u32_le(&val, p);
+    if (val >= sizeof(c->data))
+        return 0;
+    c->num = (unsigned int)val;
+
     /* h */
     for (i = 0; i < (sizeof(c->h) / sizeof(SHA_LONG)); i++) {
         p = OPENSSL_load_u32_le(&val, p);
@@ -151,10 +162,6 @@ static int SHA256_Deserialize(SHA256_CTX *c, const unsigned char *in,
         c->data[i] = (SHA_LONG)val;
     }

-    /* num */
-    p = OPENSSL_load_u32_le(&val, p);
-    c->num = (unsigned int)val;
-
     return 1;
 }

@@ -164,10 +171,10 @@ static const unsigned char sha512magic[] = "SHA512v1";
     (                                              \
         SHA512MAGIC_LEN /* magic */                \
         + sizeof(uint32_t) /* c->md_len */         \
+        + sizeof(uint32_t) /* c->num */            \
         + sizeof(uint64_t) * 8 /* c->h */          \
         + sizeof(uint64_t) * 2 /* c->Nl + c->Nh */ \
         + SHA512_CBLOCK /* c->u.d/c->u.p */        \
-        + sizeof(uint32_t) /* c->num */            \
     )

 static int SHA512_Serialize(SHA512_CTX *c, unsigned char *out,
@@ -196,6 +203,9 @@ static int SHA512_Serialize(SHA512_CTX *c, unsigned char *out,
     /* md_len */
     p = OPENSSL_store_u32_le(p, c->md_len);

+    /* num */
+    p = OPENSSL_store_u32_le(p, c->num);
+
     /* h */
     for (i = 0; i < sizeof(c->h) / sizeof(SHA_LONG64); i++)
         p = OPENSSL_store_u64_le(p, c->h[i]);
@@ -208,15 +218,17 @@ static int SHA512_Serialize(SHA512_CTX *c, unsigned char *out,
     memcpy(p, c->u.p, SHA512_CBLOCK);
     p += SHA512_CBLOCK;

-    /* num */
-    p = OPENSSL_store_u32_le(p, c->num);
-
     if (outlen != NULL)
         *outlen = SHA512_SERIALIZATION_LEN;

     return 1;
 }

+/*
+ * This function only performs basic input sanity checks and is not
+ * built to handle malicious input data. Only trusted input should be
+ * fed to this function
+ */
 static int SHA512_Deserialize(SHA512_CTX *c, const unsigned char *in,
     size_t inlen)
 {
@@ -239,6 +251,12 @@ static int SHA512_Deserialize(SHA512_CTX *c, const unsigned char *in,
     if ((unsigned int)val32 != c->md_len)
         return 0;

+    /* num check */
+    p = OPENSSL_load_u32_le(&val32, p);
+    if (val32 >= sizeof(c->u.d))
+        return 0;
+    c->num = (unsigned int)val32;
+
     /* h */
     for (i = 0; i < (sizeof(c->h) / sizeof(SHA_LONG64)); i++) {
         p = OPENSSL_load_u64_le(&val, p);
@@ -255,10 +273,6 @@ static int SHA512_Deserialize(SHA512_CTX *c, const unsigned char *in,
     memcpy(c->u.p, p, SHA512_CBLOCK);
     p += SHA512_CBLOCK;

-    /* num */
-    p = OPENSSL_load_u32_le(&val32, p);
-    c->num = (unsigned int)val32;
-
     return 1;
 }

diff --git a/providers/implementations/digests/sha3_prov.c b/providers/implementations/digests/sha3_prov.c
index 39afa0e15c..5072cd8e79 100644
--- a/providers/implementations/digests/sha3_prov.c
+++ b/providers/implementations/digests/sha3_prov.c
@@ -586,8 +586,8 @@ static const unsigned char keccakmagic[] = "KECCAKv1";
         KECCAKMAGIC_LEN /* magic string */                                           \
         + sizeof(uint64_t) /* impl-ID */                                             \
         + sizeof(uint64_t) /* c->md_size */                                          \
-        + (sizeof(uint64_t) * 5 * 5) /* c->A */                                      \
         + (sizeof(uint64_t) * 4) /* c->block_size, c->bufsz, c->pad, c->xof_state */ \
+        + (sizeof(uint64_t) * 5 * 5) /* c->A */                                      \
         + (KECCAK1600_WIDTH / 8 - 32) /* c->buf */                                   \
     )

@@ -618,17 +618,17 @@ static int KECCAK_Serialize(KECCAK1600_CTX *c, int impl_id,
     p = OPENSSL_store_u64_le(p, impl_id);
     p = OPENSSL_store_u64_le(p, c->md_size);

+    p = OPENSSL_store_u64_le(p, c->block_size);
+    p = OPENSSL_store_u64_le(p, c->bufsz);
+    p = OPENSSL_store_u64_le(p, c->pad);
+    p = OPENSSL_store_u64_le(p, c->xof_state);
+
     /* A matrix */
     for (i = 0; i < 5; i++) {
         for (j = 0; j < 5; j++)
             p = OPENSSL_store_u64_le(p, c->A[i][j]);
     }

-    p = OPENSSL_store_u64_le(p, c->block_size);
-    p = OPENSSL_store_u64_le(p, c->bufsz);
-    p = OPENSSL_store_u64_le(p, c->pad);
-    p = OPENSSL_store_u64_le(p, c->xof_state);
-
     if (outlen != NULL)
         *outlen = KECCAK_SERIALIZATION_LEN;

@@ -638,6 +638,11 @@ static int KECCAK_Serialize(KECCAK1600_CTX *c, int impl_id,
     return 1;
 }

+/*
+ * This function only performs basic input sanity checks and is not
+ * built to handle malicious input data. Only trusted input should be
+ * fed to this function
+ */
 static int KECCAK_Deserialize(KECCAK1600_CTX *c, int impl_id,
     const unsigned char *input, size_t len)
 {
@@ -664,6 +669,21 @@ static int KECCAK_Deserialize(KECCAK1600_CTX *c, int impl_id,
     if (val != (uint64_t)c->md_size)
         return 0;

+    /* check that block_size is congruent with the initialized value */
+    p = OPENSSL_load_u64_le(&val, p);
+    if (val != c->block_size)
+        return 0;
+    /* check that bufsz does not exceed block_size */
+    p = OPENSSL_load_u64_le(&val, p);
+    if (val > c->block_size)
+        return 0;
+    c->bufsz = (size_t)val;
+    p = OPENSSL_load_u64_le(&val, p);
+    if (val != c->pad)
+        return 0;
+    p = OPENSSL_load_u64_le(&val, p);
+    c->xof_state = (int)val;
+
     /* A matrix */
     for (i = 0; i < 5; i++) {
         for (j = 0; j < 5; j++) {
@@ -672,15 +692,6 @@ static int KECCAK_Deserialize(KECCAK1600_CTX *c, int impl_id,
         }
     }

-    p = OPENSSL_load_u64_le(&val, p);
-    c->block_size = (size_t)val;
-    p = OPENSSL_load_u64_le(&val, p);
-    c->bufsz = (size_t)val;
-    p = OPENSSL_load_u64_le(&val, p);
-    c->pad = (unsigned char)val;
-    p = OPENSSL_load_u64_le(&val, p);
-    c->xof_state = (int)val;
-
     /* buf */
     memcpy(c->buf, p, sizeof(c->buf));