src: add curlx_memzero() to clear buffers securely

To safely zero memory, introduce `curlx_memzero()`, and map it to
`memset_s()` (C11) or `memset_explicit()` (C23) if auto-detected, or
`explicit_bzero()` or `explicit_memset()` for platforms opted-in, or
fall back to a local workaround if all unavailable. On Windows, always
use `SecureZeroMemory()`, or `SecureZeroMemory2()` with Visual Studio
and Windows SDK 10.0.26100.0+.

Details above are experimental and may change if they cause issues.

Also add macros/functions that zero memory before freeing a buffer:
- `curlx_safefreezero()`: for buffers with size.
- `curlx_safefreezeroz()`: for null-terminated buffers.
- `curlx_freezero()`: for buffers with size.
- `curlx_freezeroz()`: for null-terminated buffers.

`curlx_memzero()` must not be passed a NULL pointer because in some
implementations it is undefined behavior.

Also:
- curl_sha512_256: Replace hard-wired `explicit_memset()` call with new
  `curlx_memzero()`.

Refs:
https://en.cppreference.com/c/string/byte/memset
https://man7.org/linux/man-pages/man3/explicit_bzero.3.html
https://man.freebsd.org/cgi/man.cgi?query=explicit_bzero
https://man.netbsd.org/NetBSD-7.2/explicit_memset.3
https://learn.microsoft.com/previous-versions/windows/desktop/legacy/aa366877(v=vs.85)
https://learn.microsoft.com/windows/win32/memory/winbase-securezeromemory2
https://learn.microsoft.com/cpp/overview/compiler-versions
https://learn.microsoft.com/windows/apps/windows-sdk/downloads
https://jtsoya539.github.io/windows-sdk-versions/

Credits-to: Daniel Gustafsson
Credits-to: Will Cosgrove and co-authors in libssh2
Ref: #13589 (original attempt)
Ref: #21588

Closes #21598
This commit is contained in:
Viktor Szakats 2026-05-13 18:20:33 +02:00
parent 831a151484
commit 066478f634
No known key found for this signature in database
10 changed files with 195 additions and 9 deletions

View file

@ -49,11 +49,6 @@
#include <inet.h>
#endif
#ifdef __DragonFly__
/* Required for __DragonFly_version */
#include <sys/param.h>
#endif
#include "urldata.h"
#include "curl_trc.h"
#include "if2ip.h"

View file

@ -234,6 +234,12 @@
/* Define to 1 if you have the `opendir' function. */
#cmakedefine HAVE_OPENDIR 1
/* Define to 1 if you have the memset_explicit (C23) function. */
#cmakedefine HAVE_MEMSET_EXPLICIT 1
/* Define to 1 if you have the memset_s (C11) function. */
#cmakedefine HAVE_MEMSET_S 1
/* Define to 1 if you have the fcntl function. */
#cmakedefine HAVE_FCNTL 1

View file

@ -1329,6 +1329,20 @@ extern curl_calloc_callback Curl_ccalloc;
(ptr) = NULL; \
} while(0)
/* Same as curlx_safefree() but zeroes memory before freeing */
#define curlx_safefreezero(ptr, size) \
do { \
curlx_freezero(ptr, size); \
(ptr) = NULL; \
} while(0)
/* Same as curlx_safefreezero() but determines length with strlen() */
#define curlx_safefreezeroz(ptr) \
do { \
curlx_freezeroz(ptr); \
(ptr) = NULL; \
} while(0)
#include <curl/curl.h> /* for CURL_EXTERN, curl_socket_t, mprintf.h */
#ifdef DEBUGBUILD
@ -1608,4 +1622,42 @@ typedef struct sockaddr_un {
#define NOVERBOSE(x) x
#endif
/* For FreeBSD it is included from curl/curl.h */
#if defined(__DragonFly__) || defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/param.h> /* for __DragonFly_version, OpenBSD,
__NetBSD_Version__ */
#endif
#ifndef _CURL_LOCAL_MEMZERO /* to be removed after a couple of releases */
#ifdef _WIN32
#if defined(_MSC_VER) && defined(NTDDI_VERSION) && \
(NTDDI_VERSION >= 0x0A000010) /* MS SDK 10.0.26100.0+ */
#pragma comment(lib, "volatileaccessu.lib")
#define curlx_memzero(buf, size) SecureZeroMemory2(buf, size)
#else
#define curlx_memzero(buf, size) SecureZeroMemory(buf, size)
#endif
#elif defined(HAVE_MEMSET_S)
#define curlx_memzero(buf, size) (void)memset_s(buf, size, 0, size)
#elif defined(HAVE_MEMSET_EXPLICIT)
#define curlx_memzero(buf, size) (void)memset_explicit(buf, 0, size)
#elif defined(__CYGWIN__) || defined(__NEWLIB__) || \
(defined(__GLIBC__) && \
(__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) || \
(defined(__DragonFly__) && __DragonFly_version >= 500600 /* v5.6+ */) || \
(defined(__FreeBSD__) && __FreeBSD_version >= 1100037 /* v11.0+ */) || \
(defined(__OpenBSD__) && OpenBSD >= 201405 /* v5.5+ */)
#define curlx_memzero(buf, size) explicit_bzero(buf, size)
#elif defined(__NetBSD__) && __NetBSD_Version__ >= 702000000 /* v7.2+ */
#define curlx_memzero(buf, size) (void)explicit_memset(buf, 0, size)
#endif
#endif /* !_CURL_LOCAL_MEMZERO */
#ifndef curlx_memzero
#define USE_CURLX_MEMZERO
void curlx_memzero(void *buf, size_t size);
#endif
void curlx_freezero(void *buf, size_t size);
void curlx_freezeroz(void *buf);
#endif /* HEADER_CURL_SETUP_H */

View file

@ -54,7 +54,6 @@
* NetBSD 10.99.11 development.
* It is safe to apply the workaround even if the bug is not present, as
* the workaround reduces performance slightly. */
# include <sys/param.h>
# if __NetBSD_Version__ < 904000000 || \
(__NetBSD_Version__ >= 999000000 && \
__NetBSD_Version__ < 1000000000) || \
@ -173,7 +172,7 @@ static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context)
tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER;
if(result == CURLE_OK)
memcpy(digest, tmp_digest, CURL_SHA512_256_DIGEST_SIZE);
explicit_memset(tmp_digest, 0, sizeof(tmp_digest));
curlx_memzero(tmp_digest, sizeof(tmp_digest));
#else /* !NEED_NETBSD_SHA512_256_WORKAROUND */
result = EVP_DigestFinal_ex(*ctx, digest, NULL) ?
CURLE_OK : CURLE_SSL_CIPHER;

View file

@ -94,3 +94,32 @@ void *curlx_memdup0(const char *src, size_t length)
buf[length] = 0;
return buf;
}
#ifdef USE_CURLX_MEMZERO
static void *(* const volatile p_curlx_memset)(void *buf, int val,
size_t size) = memset;
/* Local fallback in case there is no system function to securely zero a memory
buffer. */
void curlx_memzero(void *buf, size_t size)
{
if(buf)
p_curlx_memset(buf, 0, size);
}
#endif
/* Free 'buf' after zeroing its content. */
void curlx_freezero(void *buf, size_t size)
{
if(buf)
curlx_memzero(buf, size);
curlx_free(buf);
}
/* Free 'buf' after zeroing its content, where 'buf' is null-terminated. */
void curlx_freezeroz(void *buf)
{
if(buf)
curlx_memzero(buf, strlen(buf));
curlx_free(buf);
}