Hi Pádraig,

Collin Funk <[email protected]> writes:

>> So in summary, I'd revert the previous patch,
>> and we'll figure out how to proceed from there.
>
> Thanks, I'll have a look later today. I'll probably just move SHA-3 to
> returning bool for errors (e.g. OOM). In coreutils we can xalloc_die, in
> libraries they decision on how to handle that can be left up to them.

Thanks again for noticing this.

What do you think of the attached patch?

    # Before patch
    $ valgrind ./src/cksum -a sha3 -l 256 Makefile Makefile.am \
        COPYING README 2>&1 | grep -F 'definitely lost'
    ==148531==    definitely lost: 1,664 bytes in 4 blocks

    # After patch
    $ valgrind ./src/cksum -a sha3 -l 256 Makefile Makefile.am \
        COPYING README 2>&1 | grep -F 'definitely lost'
    ==151416==    definitely lost: 0 bytes in 0 blocks

Since OpenSSL is just going to allocate a bunch of memory anyways, there
is no point in trying to avoid EVP_MD_CTX_create, in my opinion. It also
makes the module more library friendly, which should hopefully alleviate
Simon's concerns. And makes it so we don't have to worry about
EVP_MD_CTX increasing in future versions of OpenSSL, however unlikely
that is.

For the purposes of Coreutils, we just have to add an extra xalloc_die
ourselves, which is no big deal.

Collin

>From bfbfac66b8fb954ba3e9a611b30c7406d5c41c86 Mon Sep 17 00:00:00 2001
Message-ID: <bfbfac66b8fb954ba3e9a611b30c7406d5c41c86.1757125942.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Fri, 5 Sep 2025 19:19:23 -0700
Subject: [PATCH] crypto/sha3, crypto/sha3-buffer: Don't leak memory when using
 OpenSSL.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Reported by Pádraig Brady in:
<https://lists.gnu.org/archive/html/bug-gnulib/2025-09/msg00058.html>.

* lib/sha3.c (DEFINE_SHA3_INIT_CTX) [!HAVE_OPENSSL_SHA3]: Always return
true.
[HAVE_OPENSSL_SHA3]: Call EVP_MD_CTX_create to allocate an EVP_MD_CTX.
(DEFINE_SHA3_BUFFER, sha3_process_bytes, sha3_process_block)
[!HAVE_OPENSSL_SHA3]: Always return true.
[HAVE_OPENSSL_SHA3]: Return NULL if any function calls fail.
(sha3_finish_ctx) [HAVE_OPENSSL_SHA3]: Free memory allocated by
EV_MD_CTX_create.
(sha3_free_ctx): New function.
* lib/sha3.h (struct sha3_ctx): Use a heap allocated EVP_MD_CTX.
(sha3_224_init_ctx, sha3_256_init_ctx, sha3_384_init_ctx)
(sha3_512_init_ctx, sha3_process_block, sha3_process_bytes): Change
prototype to return a bool. Mention that they return false if an OpenSSL
function fails.
(sha3_finish_ctx, sha3_read_ctx, sha3_224_buffer, sha3_256_buffer)
(sha3_384_buffer, sha3_512_buffer): Mention that these functions return
NULL if an OpenSSL function fails.
(sha3_free_ctx): New function.
* lib/sha3-stream.c (sha3_xxx_stream): Expect a function parameter that
returns bool. Check the return values of the sha3 functions, cleaning up
memory on failure.
* modules/crypto/sha3-buffer: Add bool.
---
 ChangeLog                  | 26 ++++++++++++++
 lib/sha3-stream.c          | 34 ++++++++++++++----
 lib/sha3.c                 | 72 +++++++++++++++++++++++++-------------
 lib/sha3.h                 | 36 +++++++++++--------
 modules/crypto/sha3-buffer |  1 +
 5 files changed, 125 insertions(+), 44 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7e019dfb78..03fe2b514a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,31 @@
 2025-09-05  Collin Funk  <[email protected]>
 
+	crypto/sha3, crypto/sha3-buffer: Don't leak memory when using OpenSSL.
+	Reported by Pádraig Brady in:
+	<https://lists.gnu.org/archive/html/bug-gnulib/2025-09/msg00058.html>.
+	* lib/sha3.c (DEFINE_SHA3_INIT_CTX) [!HAVE_OPENSSL_SHA3]: Always return
+	true.
+	[HAVE_OPENSSL_SHA3]: Call EVP_MD_CTX_create to allocate an EVP_MD_CTX.
+	(DEFINE_SHA3_BUFFER, sha3_process_bytes, sha3_process_block)
+	[!HAVE_OPENSSL_SHA3]: Always return true.
+	[HAVE_OPENSSL_SHA3]: Return NULL if any function calls fail.
+	(sha3_finish_ctx) [HAVE_OPENSSL_SHA3]: Free memory allocated by
+	EV_MD_CTX_create.
+	(sha3_free_ctx): New function.
+	* lib/sha3.h (struct sha3_ctx): Use a heap allocated EVP_MD_CTX.
+	(sha3_224_init_ctx, sha3_256_init_ctx, sha3_384_init_ctx)
+	(sha3_512_init_ctx, sha3_process_block, sha3_process_bytes): Change
+	prototype to return a bool. Mention that they return false if an OpenSSL
+	function fails.
+	(sha3_finish_ctx, sha3_read_ctx, sha3_224_buffer, sha3_256_buffer)
+	(sha3_384_buffer, sha3_512_buffer): Mention that these functions return
+	NULL if an OpenSSL function fails.
+	(sha3_free_ctx): New function.
+	* lib/sha3-stream.c (sha3_xxx_stream): Expect a function parameter that
+	returns bool. Check the return values of the sha3 functions, cleaning up
+	memory on failure.
+	* modules/crypto/sha3-buffer: Add bool.
+
 	quotearg: Remove an unused variable.
 	* lib/quotearg.c (gettext_quote): Remove a variable that is no longer
 	used after yesterdays changes.
diff --git a/lib/sha3-stream.c b/lib/sha3-stream.c
index 708dafa7dc..43c056e22a 100644
--- a/lib/sha3-stream.c
+++ b/lib/sha3-stream.c
@@ -41,7 +41,7 @@
    successful.  */
 static int
 sha3_xxx_stream (FILE *stream, char const *alg, void *resblock,
-                 ssize_t hashlen, void (*init_ctx) (struct sha3_ctx *))
+                 ssize_t hashlen, bool (*init_ctx) (struct sha3_ctx *))
 {
   switch (afalg_stream (stream, alg, resblock, hashlen))
     {
@@ -54,7 +54,11 @@ sha3_xxx_stream (FILE *stream, char const *alg, void *resblock,
     return 1;
 
   struct sha3_ctx ctx;
-  init_ctx (&ctx);
+  if (! init_ctx (&ctx))
+    {
+      free (buffer);
+      return 1;
+    }
   size_t sum;
 
   /* Iterate over full file contents.  */
@@ -92,6 +96,7 @@ sha3_xxx_stream (FILE *stream, char const *alg, void *resblock,
               if (ferror (stream))
                 {
                   free (buffer);
+                  sha3_free_ctx (&ctx);
                   return 1;
                 }
               goto process_partial_block;
@@ -101,18 +106,35 @@ sha3_xxx_stream (FILE *stream, char const *alg, void *resblock,
       /* Process buffer with BLOCKSIZE bytes.  Note that
                         BLOCKSIZE % ctx.blocklen == 0
        */
-      sha3_process_block (buffer, BLOCKSIZE, &ctx);
+      if (! sha3_process_block (buffer, BLOCKSIZE, &ctx))
+        {
+          free (buffer);
+          sha3_free_ctx (&ctx);
+          return 1;
+        }
     }
 
  process_partial_block:;
 
   /* Process any remaining bytes.  */
   if (sum > 0)
-    sha3_process_bytes (buffer, sum, &ctx);
+    {
+      if (! sha3_process_bytes (buffer, sum, &ctx))
+        {
+          free (buffer);
+          sha3_free_ctx (&ctx);
+          return 1;
+        }
+    }
 
-  /* Construct result in desired memory.  */
-  sha3_finish_ctx (&ctx, resblock);
   free (buffer);
+
+  /* Construct result in desired memory.  */
+  if (sha3_finish_ctx (&ctx, resblock) == NULL)
+    {
+      sha3_free_ctx (&ctx);
+      return 1;
+    }
   return 0;
 }
 
diff --git a/lib/sha3.c b/lib/sha3.c
index e30210fb3e..e7b3b9c7c6 100644
--- a/lib/sha3.c
+++ b/lib/sha3.c
@@ -50,13 +50,14 @@ static const u64 rc[] = {
 };
 
 #define DEFINE_SHA3_INIT_CTX(SIZE)                                      \
-  void                                                                  \
+  bool                                                                  \
   sha3_##SIZE##_init_ctx (struct sha3_ctx *ctx)                         \
   {                                                                     \
     memset (&ctx->state, '\0', sizeof ctx->state);                      \
     ctx->buflen = 0;                                                    \
     ctx->digestlen = SHA3_##SIZE##_DIGEST_SIZE;                         \
     ctx->blocklen = SHA3_##SIZE##_BLOCK_SIZE;                           \
+    return true;                                                        \
   }
 
 DEFINE_SHA3_INIT_CTX (224)
@@ -64,6 +65,12 @@ DEFINE_SHA3_INIT_CTX (256)
 DEFINE_SHA3_INIT_CTX (384)
 DEFINE_SHA3_INIT_CTX (512)
 
+void
+sha3_free_ctx (_GL_UNUSED struct sha3_ctx *ctx)
+{
+  /* Do nothing.  */
+}
+
 /* Copy the value from V into the memory location pointed to by *CP,
    If your architecture allows unaligned access, this is equivalent to
    * (__typeof__ (v) *) cp = v  */
@@ -127,7 +134,7 @@ DEFINE_SHA3_BUFFER (256)
 DEFINE_SHA3_BUFFER (384)
 DEFINE_SHA3_BUFFER (512)
 
-void
+bool
 sha3_process_bytes (const void *buffer, size_t len, struct sha3_ctx *ctx)
 {
   if (0 < ctx->buflen)
@@ -138,7 +145,7 @@ sha3_process_bytes (const void *buffer, size_t len, struct sha3_ctx *ctx)
           /* Not enough to fill a full block.  */
           memcpy (ctx->buffer + ctx->buflen, buffer, len);
           ctx->buflen += len;
-          return;
+          return true;
         }
       /* Process the block that already had bytes buffered.  */
       memcpy (ctx->buffer + ctx->buflen, buffer, left);
@@ -156,9 +163,10 @@ sha3_process_bytes (const void *buffer, size_t len, struct sha3_ctx *ctx)
       memcpy (ctx->buffer, buffer, len);
       ctx->buflen = len;
     }
+  return true;
 }
 
-void
+bool
 sha3_process_block (const void *buffer, size_t len, struct sha3_ctx *ctx)
 {
   u64 *a = ctx->state;
@@ -315,21 +323,27 @@ sha3_process_block (const void *buffer, size_t len, struct sha3_ctx *ctx)
           a[0] = u64xor (a[0], rc[i]);
         }
     }
+  return true;
 }
 
 #else /* OpenSSL implementation.  */
 
 #define DEFINE_SHA3_INIT_CTX(SIZE)                                      \
-  void                                                                  \
+  bool                                                                  \
   sha3_##SIZE##_init_ctx (struct sha3_ctx *ctx)                         \
   {                                                                     \
-    /* EVP_DigestInit_ex expects all bytes to be zero.  */              \
-    memset (ctx, 0, sizeof *ctx);                                       \
-    EVP_MD_CTX *evp_ctx = (EVP_MD_CTX *) ctx->evp_ctx_buffer;           \
-    int rc = EVP_DigestInit_ex (evp_ctx, EVP_sha3_##SIZE (), NULL);     \
-    /* This should never fail.  */                                      \
-    if (rc == 0)                                                        \
-      abort ();                                                         \
+    int result;                                                         \
+    ctx->evp_ctx = EVP_MD_CTX_create ();                                \
+    if (ctx->evp_ctx == NULL)                                           \
+      return false;                                                     \
+    result = EVP_DigestInit_ex (ctx->evp_ctx, EVP_sha3_##SIZE (),       \
+                                NULL);                                  \
+    if (result == 0)                                                    \
+      {                                                                 \
+        sha3_free_ctx (ctx);                                            \
+        return false;                                                   \
+      }                                                                 \
+    return true;                                                        \
   }
 
 DEFINE_SHA3_INIT_CTX (224)
@@ -337,6 +351,12 @@ DEFINE_SHA3_INIT_CTX (256)
 DEFINE_SHA3_INIT_CTX (384)
 DEFINE_SHA3_INIT_CTX (512)
 
+void
+sha3_free_ctx (struct sha3_ctx *ctx)
+{
+  EVP_MD_CTX_free (ctx->evp_ctx);
+}
+
 void *
 sha3_read_ctx (const struct sha3_ctx *ctx, void *resbuf)
 {
@@ -347,11 +367,10 @@ sha3_read_ctx (const struct sha3_ctx *ctx, void *resbuf)
 void *
 sha3_finish_ctx (struct sha3_ctx *ctx, void *resbuf)
 {
-  EVP_MD_CTX *evp_ctx = (EVP_MD_CTX *) ctx->evp_ctx_buffer;
-  /* This should never fail.  */
-  int result = EVP_DigestFinal_ex (evp_ctx, resbuf, NULL);
+  int result = EVP_DigestFinal_ex (ctx->evp_ctx, resbuf, NULL);
+  sha3_free_ctx (ctx);
   if (result == 0)
-    abort ();
+    return NULL;
   return resbuf;
 }
 
@@ -360,8 +379,10 @@ sha3_finish_ctx (struct sha3_ctx *ctx, void *resbuf)
   sha3_##SIZE##_buffer (const char *buffer, size_t len, void *resblock) \
   {                                                                     \
     struct sha3_ctx ctx;                                                \
-    sha3_##SIZE##_init_ctx (&ctx);                                      \
-    sha3_process_bytes (buffer, len, &ctx);                             \
+    if (! sha3_##SIZE##_init_ctx (&ctx))                                \
+      return NULL;                                                      \
+    if (! sha3_process_bytes (buffer, len, &ctx))                       \
+      return NULL;                                                      \
     return sha3_finish_ctx (&ctx, resblock);                            \
   }
 
@@ -370,19 +391,22 @@ DEFINE_SHA3_BUFFER (256)
 DEFINE_SHA3_BUFFER (384)
 DEFINE_SHA3_BUFFER (512)
 
-void
+bool
 sha3_process_bytes (const void *buffer, size_t len, struct sha3_ctx *ctx)
 {
-  EVP_MD_CTX *evp_ctx = (EVP_MD_CTX *) ctx->evp_ctx_buffer;
-  int result = EVP_DigestUpdate (evp_ctx, buffer, len);
+  int result = EVP_DigestUpdate (ctx->evp_ctx, buffer, len);
   if (result == 0)
-    abort ();
+    {
+      sha3_free_ctx (ctx);
+      return false;
+    }
+  return true;
 }
 
-void
+bool
 sha3_process_block (const void *buffer, size_t len, struct sha3_ctx *ctx)
 {
-  sha3_process_bytes (buffer, len, ctx);
+  return sha3_process_bytes (buffer, len, ctx);
 }
 
 #endif
diff --git a/lib/sha3.h b/lib/sha3.h
index adce43eac7..259f0d1f56 100644
--- a/lib/sha3.h
+++ b/lib/sha3.h
@@ -51,9 +51,8 @@ enum { SHA3_512_BLOCK_SIZE = 576 / 8 };
 struct sha3_ctx
 {
 # if HAVE_OPENSSL_SHA3
-  /* EVP_MD_CTX is an incomplete type.  EVP_MD_CTX_create allocates 72 bytes of
-     memory as of 2025-09-02.  */
-  max_align_t evp_ctx_buffer[256 / sizeof (max_align_t)];
+  /* EVP_MD_CTX is an incomplete type.  It cannot be placed on the stack.  */
+  EVP_MD_CTX *evp_ctx;
 # else
   u64 state[25];
   uint8_t buffer[144]; /* Up to BLOCKLEN in use.  */
@@ -64,40 +63,49 @@ struct sha3_ctx
 };
 
 /* Initialize structure containing state of computation.  */
-extern void sha3_224_init_ctx (struct sha3_ctx *ctx);
-extern void sha3_256_init_ctx (struct sha3_ctx *ctx);
-extern void sha3_384_init_ctx (struct sha3_ctx *ctx);
-extern void sha3_512_init_ctx (struct sha3_ctx *ctx);
+extern bool sha3_224_init_ctx (struct sha3_ctx *ctx);
+extern bool sha3_256_init_ctx (struct sha3_ctx *ctx);
+extern bool sha3_384_init_ctx (struct sha3_ctx *ctx);
+extern bool sha3_512_init_ctx (struct sha3_ctx *ctx);
+
+/* Free memory allocated by the init_structure.  This is only required if
+   OpenSSL is used.  */
+extern void sha3_free_ctx (struct sha3_ctx *ctx);
 
 /* Starting with the result of former calls of this function (or the
    initialization function update the context for the next LEN bytes
    starting at BUFFER.
-   It is necessary that LEN is a multiple of the BLOCKLEN member of CTX!!!  */
-extern void sha3_process_block (const void *buffer, size_t len,
+   It is necessary that LEN is a multiple of the BLOCKLEN member of CTX!!!
+   Return false if an OpenSSL function fails.  */
+extern bool sha3_process_block (const void *buffer, size_t len,
                                 struct sha3_ctx *ctx);
 
 /* Starting with the result of former calls of this function (or the
    initialization function update the context for the next LEN bytes
    starting at BUFFER.
-   It is NOT required that LEN is a multiple of the BLOCKLEN member of CTX.  */
-extern void sha3_process_bytes (const void *buffer, size_t len,
+   It is NOT required that LEN is a multiple of the BLOCKLEN member of CTX.
+   Return false if an OpenSSL function fails.  */
+extern bool sha3_process_bytes (const void *buffer, size_t len,
                                 struct sha3_ctx *ctx);
 
 /* Process the remaining bytes in the buffer and put result from CTX in RESBUF.
    The result is always in little endian byte order, so that a byte-wise output
-   yields to the wanted ASCII representation of the message digest.  */
+   yields to the wanted ASCII representation of the message digest.
+   Return NULL if an OpenSSL function fails.  */
 extern void *sha3_finish_ctx (struct sha3_ctx *ctx, void *restrict resbuf);
 
 /* Put result from CTX in RESBUF.  The result is always in little endian byte
    order, so that a byte-wise output yields to the wanted ASCII representation
-   of the message digest.  */
+   of the message digest.
+   Return NULL if an OpenSSL function fails.  */
 extern void *sha3_read_ctx (const struct sha3_ctx *ctx,
                             void *restrict resbuf);
 
 /* Compute a SHA-3 message digest for LEN bytes beginning at BUFFER.
    The result is always in little endian byte order, so that a byte-wise
    output yields to the wanted ASCII representation of the message
-   digest.  */
+   digest.
+   Return NULL if an OpenSSL function fails.  */
 extern void *sha3_224_buffer (const char *buffer, size_t len,
                               void *restrict resblock);
 extern void *sha3_256_buffer (const char *buffer, size_t len,
diff --git a/modules/crypto/sha3-buffer b/modules/crypto/sha3-buffer
index e0e654e563..7ab61dea30 100644
--- a/modules/crypto/sha3-buffer
+++ b/modules/crypto/sha3-buffer
@@ -8,6 +8,7 @@ m4/gl-openssl.m4
 m4/sha3.m4
 
 Depends-on:
+bool
 byteswap
 c99
 stddef-h
-- 
2.51.0

Reply via email to