From 3ca6db643e3eab73e948d2ef932d1811f612695f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 14 Oct 2020 11:38:51 +0900
Subject: [PATCH 2/2] Switch sha2_openssl.c to use EVP

Postgres is two decades late for this switch.
---
 src/common/sha2_openssl.c        | 191 +++++++++++++++++++++++--------
 src/tools/pgindent/typedefs.list |   1 +
 2 files changed, 143 insertions(+), 49 deletions(-)

diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
index 57de96df90..3ccfc3a22e 100644
--- a/src/common/sha2_openssl.c
+++ b/src/common/sha2_openssl.c
@@ -20,9 +20,13 @@
 #include "postgres_fe.h"
 #endif
 
-#include <openssl/sha.h>
+#include <openssl/evp.h>
 
 #include "common/sha2.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#endif
 
 /*
  * In backend, use palloc/pfree to ease the error handling.  In frontend,
@@ -36,6 +40,68 @@
 #define FREE(ptr) free(ptr)
 #endif
 
+/*
+ * As the allocations of the EVP context data happens within the allocator
+ * used by OpenSSL, use resource owner callbacks to free them on abort with
+ * a linked list of items saved in TopMemoryContext.
+ */
+#ifndef FRONTEND
+typedef struct EVPContext
+{
+	EVP_MD_CTX *md_ctx;
+
+	ResourceOwner owner;
+	struct EVPContext *next;
+	struct EVPContext *prev;
+} EVPContext;
+
+static EVPContext *open_evp_contexts = NULL;
+static bool evp_resowner_callback_registered = false;
+
+/*
+ * Utility wrapper to clean up an opened EVP context.
+ */
+static void
+free_evp_context(EVPContext *evp_ctx)
+{
+	EVP_MD_CTX_destroy(evp_ctx->md_ctx);
+	if (evp_ctx->prev)
+		evp_ctx->prev->next = evp_ctx->next;
+	else
+		open_evp_contexts = evp_ctx->next;
+	if (evp_ctx->next)
+		evp_ctx->next->prev = evp_ctx->prev;
+	pfree(evp_ctx);
+}
+
+static void
+evp_context_free_callback(ResourceReleasePhase phase,
+						  bool isCommit,
+						  bool isTopLevel,
+						  void *arg)
+{
+	EVPContext *curr;
+	EVPContext *next;
+
+	if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
+		return;
+
+	next = open_evp_contexts;
+	while (next)
+	{
+		curr = next;
+		next = curr->next;
+
+		if (curr->owner == CurrentResourceOwner)
+		{
+			if (isCommit)
+				elog(WARNING, "evp context reference leak: context %p still referenced", curr);
+			free_evp_context(curr);
+		}
+	}
+}
+#endif
+
 /*
  * pg_sha2_create
  *
@@ -45,6 +111,9 @@ pg_sha2_ctx *
 pg_sha2_create(pg_sha2_type type)
 {
 	pg_sha2_ctx *ctx;
+#ifndef FRONTEND
+	EVPContext *evp_ctx;
+#endif
 
 	ctx = ALLOC(sizeof(pg_sha2_ctx));
 	if (ctx == NULL)
@@ -52,25 +121,47 @@ pg_sha2_create(pg_sha2_type type)
 
 	ctx->type = type;
 
-	switch (type)
+#ifndef FRONTEND
+
+	/*
+	 * Keep track of any opened EVP context data allocated in OpenSSL to avoid
+	 * any leaks.  The EVP data is built before being assigned to the list, so
+	 * the order is important here.
+	 */
+	if (!evp_resowner_callback_registered)
 	{
-		case PG_SHA224:
-		case PG_SHA256:
-			ctx->data = ALLOC(sizeof(SHA256_CTX));
-			break;
-		case PG_SHA384:
-		case PG_SHA512:
-			ctx->data = ALLOC(sizeof(SHA512_CTX));
-			break;
+		RegisterResourceReleaseCallback(evp_context_free_callback, NULL);
+		evp_resowner_callback_registered = true;
 	}
 
+	evp_ctx = MemoryContextAlloc(TopMemoryContext, sizeof(EVPContext));
+#endif
+
+	/*
+	 * Initialization takes care of assigning the correct type for OpenSSL.
+	 */
+	ctx->data = EVP_MD_CTX_create();
+
 	if (ctx->data == NULL)
 	{
+#ifndef FRONTEND
+		FREE(evp_ctx);
+		elog(ERROR, "out of memory");
+#else
 		explicit_bzero(ctx, sizeof(pg_sha2_ctx));
 		FREE(ctx);
 		return NULL;
+#endif
 	}
 
+#ifndef FRONTEND
+	evp_ctx->md_ctx = ctx->data;
+	evp_ctx->owner = CurrentResourceOwner;
+	evp_ctx->next = open_evp_contexts;
+	evp_ctx->prev = NULL;
+	open_evp_contexts = evp_ctx;
+#endif
+
 	return ctx;
 }
 
@@ -80,7 +171,7 @@ pg_sha2_create(pg_sha2_type type)
  * Initialize a SHA2 context.  Returns 0 on success, and -1 on failure.
  */
 int
-pg_sha2_init(pg_sha2_ctx *ctx)
+pg_sha2_init(pg_sha2_ctx * ctx)
 {
 	int			status = 0;
 
@@ -90,16 +181,20 @@ pg_sha2_init(pg_sha2_ctx *ctx)
 	switch (ctx->type)
 	{
 		case PG_SHA224:
-			status = SHA224_Init((SHA256_CTX *) ctx->data);
+			status = EVP_DigestInit_ex((EVP_MD_CTX *) ctx->data,
+									   EVP_sha224(), NULL);
 			break;
 		case PG_SHA256:
-			status = SHA256_Init((SHA256_CTX *) ctx->data);
+			status = EVP_DigestInit_ex((EVP_MD_CTX *) ctx->data,
+									   EVP_sha256(), NULL);
 			break;
 		case PG_SHA384:
-			status = SHA384_Init((SHA512_CTX *) ctx->data);
+			status = EVP_DigestInit_ex((EVP_MD_CTX *) ctx->data,
+									   EVP_sha384(), NULL);
 			break;
 		case PG_SHA512:
-			status = SHA512_Init((SHA512_CTX *) ctx->data);
+			status = EVP_DigestInit_ex((EVP_MD_CTX *) ctx->data,
+									   EVP_sha512(), NULL);
 			break;
 	}
 
@@ -115,28 +210,14 @@ pg_sha2_init(pg_sha2_ctx *ctx)
  * Update a SHA2 context.  Returns 0 on success, and -1 on failure.
  */
 int
-pg_sha2_update(pg_sha2_ctx *ctx, const uint8 *data, size_t len)
+pg_sha2_update(pg_sha2_ctx * ctx, const uint8 *data, size_t len)
 {
 	int			status;
 
 	if (ctx == NULL)
 		return 0;
 
-	switch (ctx->type)
-	{
-		case PG_SHA224:
-			status = SHA224_Update((SHA256_CTX *) ctx->data, data, len);
-			break;
-		case PG_SHA256:
-			status = SHA256_Update((SHA256_CTX *) ctx->data, data, len);
-			break;
-		case PG_SHA384:
-			status = SHA384_Update((SHA512_CTX *) ctx->data, data, len);
-			break;
-		case PG_SHA512:
-			status = SHA512_Update((SHA512_CTX *) ctx->data, data, len);
-			break;
-	}
+	status = EVP_DigestUpdate((EVP_MD_CTX *) ctx->data, data, len);
 
 	/* OpenSSL internals return 1 on success, 0 on failure */
 	if (status <= 0)
@@ -150,28 +231,14 @@ pg_sha2_update(pg_sha2_ctx *ctx, const uint8 *data, size_t len)
  * Finalize a SHA2 context.  Returns 0 on success, and -1 on failure.
  */
 int
-pg_sha2_final(pg_sha2_ctx *ctx, uint8 *dest)
+pg_sha2_final(pg_sha2_ctx * ctx, uint8 *dest)
 {
 	int			status;
 
 	if (ctx == NULL)
 		return 0;
 
-	switch (ctx->type)
-	{
-		case PG_SHA224:
-			status = SHA224_Final(dest, (SHA256_CTX *) ctx->data);
-			break;
-		case PG_SHA256:
-			status = SHA256_Final(dest, (SHA256_CTX *) ctx->data);
-			break;
-		case PG_SHA384:
-			status = SHA384_Final(dest, (SHA512_CTX *) ctx->data);
-			break;
-		case PG_SHA512:
-			status = SHA512_Final(dest, (SHA512_CTX *) ctx->data);
-			break;
-	}
+	status = EVP_DigestFinal_ex((EVP_MD_CTX *) ctx->data, dest, 0);
 
 	/* OpenSSL internals return 1 on success, 0 on failure */
 	if (status <= 0)
@@ -185,11 +252,37 @@ pg_sha2_final(pg_sha2_ctx *ctx, uint8 *dest)
  * Free a SHA2 context.
  */
 void
-pg_sha2_free(pg_sha2_ctx *ctx)
+pg_sha2_free(pg_sha2_ctx * ctx)
 {
+#ifndef FRONTEND
+	EVPContext *curr;
+	EVPContext *next;
+#endif
+
 	if (ctx == NULL)
 		return;
-	FREE(ctx->data);
+
+#ifndef FRONTEND
+
+	/*
+	 * Look at the list of opened EVP contexts for the one to free.
+	 */
+	next = open_evp_contexts;
+	while (next)
+	{
+		curr = next;
+		next = curr->next;
+
+		if (curr->md_ctx == ctx->data)
+		{
+			free_evp_context(curr);
+			break;
+		}
+	}
+#else
+	EVP_MD_CTX_destroy((EVP_MD_CTX *) ctx->data);
+#endif
+
 	explicit_bzero(ctx, sizeof(pg_sha2_ctx));
 	FREE(ctx);
 }
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c52f20d4ba..5773aaa1f9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -569,6 +569,7 @@ EVP_CIPHER_CTX
 EVP_MD
 EVP_MD_CTX
 EVP_PKEY
+EVPContext
 EachState
 Edge
 EditableObjectType
-- 
2.28.0

