]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
src: add `curlx_memzero()` to clear buffers securely
authorViktor Szakats <commit@vsz.me>
Wed, 13 May 2026 16:20:33 +0000 (18:20 +0200)
committerViktor Szakats <commit@vsz.me>
Fri, 15 May 2026 22:26:46 +0000 (00:26 +0200)
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

.github/workflows/macos.yml
CMake/unix-cache.cmake
CMakeLists.txt
configure.ac
lib/cf-socket.c
lib/curl_config-cmake.h.in
lib/curl_setup.h
lib/curl_sha512_256.c
lib/curlx/strdup.c
m4/curl-functions.m4

index 3cda27766adadbb2cc5bfd6923ef90ab7558ab4d..e0250f562421cca9d61d0f7fd8231b93f9c68833 100644 (file)
@@ -36,7 +36,7 @@ permissions: {}
 # or runtime:
 #
 # - 10.7   Lion (2011)          - GSS (build-time, deprecated MIT Kerberos shim)
-# - 10.9   Mavericks (2013)     - LDAP (build-time, deprecated), OCSP (runtime)
+# - 10.9   Mavericks (2013)     - LDAP (build-time, deprecated), memset_s(), OCSP (runtime)
 # - 10.11  El Capitan (2015)    - connectx() (runtime)
 # - 10.12  Sierra (2016)        - clock_gettime() (build-time, runtime)
 # - 10.14  Mojave (2018)        - SecTrustEvaluateWithError() (runtime)
index 8ecd20618645223dbf82a0f270da5fe0f6ecb77b..e69ea5f6088ebb6cf71c20f60f326ffd637e347a 100644 (file)
@@ -65,6 +65,16 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
        CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
   set(HAVE_EVENTFD 1)
 endif()
+if(ANDROID AND ANDROID_PLATFORM_LEVEL GREATER_EQUAL 34)
+  set(HAVE_MEMSET_EXPLICIT 1)
+endif()
+if((APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.9) OR
+   CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR  # v6+
+   CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")  # v11.2+
+  set(HAVE_MEMSET_S 1)
+elseif(NOT APPLE)
+  set(HAVE_MEMSET_S 0)
+endif()
 set(HAVE_FCNTL 1)
 set(HAVE_FCNTL_H 1)
 set(HAVE_FCNTL_O_NONBLOCK 1)
index 0573d2f14337a0dc878e0a45d2472ebd2335274f..89d7f8a9321fe3ef85e35797f51ca38f7b769e35 100644 (file)
@@ -1672,6 +1672,11 @@ if(NOT WIN32)
   check_symbol_exists("strcasecmp"      "string.h" HAVE_STRCASECMP)
   check_symbol_exists("stricmp"         "string.h" HAVE_STRICMP)
   check_symbol_exists("strcmpi"         "string.h" HAVE_STRCMPI)
+
+  check_symbol_exists("memset_s" "string.h" HAVE_MEMSET_S)
+  if(NOT HAVE_MEMSET_S)
+    check_function_exists("memset_explicit" HAVE_MEMSET_EXPLICIT)
+  endif()
 endif()
 
 if(AMIGA)
index d06024bc106494aed35f91f31d67b140dbfbb8cd..3e8569de3eae6cdd45c2cebaf2a9d03158a6abb5 100644 (file)
@@ -4152,6 +4152,11 @@ if test "$curl_cv_native_windows" != "yes"; then
   CURL_CHECK_FUNC_STRCASECMP
   CURL_CHECK_FUNC_STRCMPI
   CURL_CHECK_FUNC_STRICMP
+
+  CURL_CHECK_FUNC_MEMSET_S
+  if test "$curl_cv_func_memset_s" = "no"; then
+    AC_CHECK_FUNCS([memset_explicit])
+  fi
 fi
 
 if test -z "$ssl_backends"; then
index 1e244671f3948c24396c36a3096a074826788204..0edd0efe746b225d517dd51ee8c403e0962c821e 100644 (file)
 #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"
index 41b0ddf073753497845a4c8a2cb69ccb7c1d8739..31e94d0691e77a3869598803d15510242aa53e9f 100644 (file)
 /* 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
 
index 9329f5605f02a4036f379a1eb042bde3474f4840..d4b805f9e20d412b891e9d132a7e00d4a9f37ce8 100644 (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 */
index f8f2053b5d6c2ce3c6c4ca74c28227ab4fe9e69b..7b851788ad8dfe6494372db89b8944ca99a87ee0 100644 (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;
index 3c967dbe0a5d144dc4c77ad8e9ffac848d3c6d22..8e788ea34c01c777a1fb090387c1f14de00cfe59 100644 (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);
+}
index 9d80f2f5384aaeab3a110eaf417fcec65e0dc5a1..999f96add0e9a119c0d3afcc9e64a66ab091271e 100644 (file)
@@ -4111,7 +4111,6 @@ AC_DEFUN([CURL_CHECK_FUNC_STRERROR_R], [
      test "$tst_allow_strerror_r" = "unknown"; then
     AC_MSG_WARN([cannot determine strerror_r() style: edit lib/curl_config.h manually.])
   fi
-
 ])
 
 
@@ -4199,6 +4198,92 @@ AC_DEFUN([CURL_CHECK_FUNC_STRICMP], [
   fi
 ])
 
+
+dnl CURL_CHECK_FUNC_MEMSET_S
+dnl -------------------------------------------------
+dnl Verify if memset_s is available, prototyped, and
+dnl can be compiled. If all of these are true, and
+dnl usage has not been previously disallowed with
+dnl shell variable curl_disallow_memset_s, then
+dnl HAVE_MEMSET_S will be defined.
+
+AC_DEFUN([CURL_CHECK_FUNC_MEMSET_S], [
+  AC_REQUIRE([CURL_INCLUDES_STRING])
+
+  tst_links_memset_s="unknown"
+  tst_proto_memset_s="unknown"
+  tst_compi_memset_s="unknown"
+  tst_allow_memset_s="unknown"
+
+  AC_MSG_CHECKING([if memset_s can be linked])
+  AC_LINK_IFELSE([
+    AC_LANG_FUNC_LINK_TRY([memset_s])
+  ],[
+    AC_MSG_RESULT([yes])
+    tst_links_memset_s="yes"
+  ],[
+    AC_MSG_RESULT([no])
+    tst_links_memset_s="no"
+  ])
+
+  if test "$tst_links_memset_s" = "yes"; then
+    AC_MSG_CHECKING([if memset_s is prototyped])
+    AC_EGREP_CPP([memset_s],[
+      $curl_includes_string
+    ],[
+      AC_MSG_RESULT([yes])
+      tst_proto_memset_s="yes"
+    ],[
+      AC_MSG_RESULT([no])
+      tst_proto_memset_s="no"
+    ])
+  fi
+
+  if test "$tst_proto_memset_s" = "yes"; then
+    AC_MSG_CHECKING([if memset_s is compilable])
+    AC_COMPILE_IFELSE([
+      AC_LANG_PROGRAM([[
+        $curl_includes_string
+      ]],[[
+        char buf[2];
+        if(memset_s(buf, sizeof(buf), 0, sizeof(buf)) != 0)
+          return 1;
+      ]])
+    ],[
+      AC_MSG_RESULT([yes])
+      tst_compi_memset_s="yes"
+    ],[
+      AC_MSG_RESULT([no])
+      tst_compi_memset_s="no"
+    ])
+  fi
+
+  if test "$tst_compi_memset_s" = "yes"; then
+    AC_MSG_CHECKING([if memset_s usage allowed])
+    if test "x$curl_disallow_memset_s" != "xyes"; then
+      AC_MSG_RESULT([yes])
+      tst_allow_memset_s="yes"
+    else
+      AC_MSG_RESULT([no])
+      tst_allow_memset_s="no"
+    fi
+  fi
+
+  AC_MSG_CHECKING([if memset_s might be used])
+  if test "$tst_links_memset_s" = "yes" &&
+     test "$tst_proto_memset_s" = "yes" &&
+     test "$tst_compi_memset_s" = "yes" &&
+     test "$tst_allow_memset_s" = "yes"; then
+    AC_MSG_RESULT([yes])
+    AC_DEFINE_UNQUOTED(HAVE_MEMSET_S, 1,
+      [Define to 1 if you have the memset_s function.])
+    curl_cv_func_memset_s="yes"
+  else
+    AC_MSG_RESULT([no])
+    curl_cv_func_memset_s="no"
+  fi
+])
+
 dnl CURL_RUN_IFELSE
 dnl -------------------------------------------------
 dnl Wrapper macro to use instead of AC_RUN_IFELSE. It