Skip to content

Windows SSL: redisCreateSSLContextWithOptions crashes when capath is set and cacert_filename is NULL #1362

Description

@yayong3

Summary

redisCreateSSLContextWithOptions() documents cacert_filename and capath as independent optional CA sources. OpenSSL’s SSL_CTX_load_verify_locations(ctx, NULL, capath) is the intended way to load a CA directory without a bundle file.

On Windows the function enters the CA block with if (capath || cacert_filename) and then calls strcmp(cacert_filename, "wincert") with no NULL check. A caller who sets only capath (the portable OpenSSL pattern) therefore passes NULL to strcmp and crashes inside redisCreateSSLContextWithOptions, before any TLS handshake and before CertOpenSystemStore.

Non-Windows builds skip that strcmp and call SSL_CTX_load_verify_locations with a NULL file name, which is valid. The same redisSSLOptions therefore works on Linux/macOS and aborts on Windows.

This is not a general TLS vulnerability and does not affect "wincert" or a non-NULL cacert_filename. It is a missing NULL guard on the Windows-only "wincert" special case.

Present on 1.5.0-dev (29ea279), ssl.c.

Code

/* ssl.c — redisCreateSSLContextWithOptions */

const char *cacert_filename = options->cacert_filename;
const char *capath = options->capath;
...
if (capath || cacert_filename) {
#ifdef _WIN32
    if (0 == strcmp(cacert_filename, "wincert")) {   /* :373; cacert_filename may be NULL */
        win_store = CertOpenSystemStore(NULL, "Root");
        ...
    } else
#endif
    if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
        ...
    }
}

Public options (hiredis_ssl.h): both fields are optional. "wincert" is the Windows store sentinel; it is not required in order to use capath. redisCreateSSLContext(NULL, capath, ...) is the same call.

Reproduce

Windows PE linked against the in-tree ssl.c and OpenSSL. The crash is inside redisCreateSSLContextWithOptions after SSL_CTX_new succeeds, on the strcmp at ssl.c:373.

The I/O stubs below exist only so ssl.c links without the rest of libhiredis. They are not on the crashing path.

#include "hiredis.h"
#include "hiredis_ssl.h"
#include "async.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void __redisSetError(redisContext *c, int type, const char *str)
{ (void)c; (void)type; (void)str; }
void redisNetClose(redisContext *c) { (void)c; }
int redisBufferRead(redisContext *c) { (void)c; return REDIS_ERR; }
int redisBufferWrite(redisContext *c, int *done)
{ (void)c; (void)done; return REDIS_ERR; }
void __redisAsyncDisconnect(redisAsyncContext *ac) { (void)ac; }
void redisProcessCallbacks(redisAsyncContext *ac) { (void)ac; }

static void on_crash(int sig)
{
    fprintf(stderr, "signal %d in redisCreateSSLContextWithOptions\n", sig);
    fflush(stderr);
    _Exit(0);
}

int main(void)
{
    redisSSLContextError err = REDIS_SSL_CTX_NONE;
    redisSSLOptions o = {0};

    signal(SIGSEGV, on_crash);

    o.capath = "C:\\certs";
    o.cacert_filename = NULL;
    o.verify_mode = REDIS_SSL_VERIFY_PEER;

    redisCreateSSLContextWithOptions(&o, &err);
    fprintf(stderr, "no crash, err=%d\n", (int)err);
    return 1;
}
x86_64-w64-mingw32-gcc -O0 -g -I. -I$OPENSSL/include \
    -o poc.exe poc.c ssl.c alloc.c \
    $OPENSSL/lib64/libssl.a $OPENSSL/lib64/libcrypto.a \
    -lws2_32 -lcrypt32 -ladvapi32 -lgdi32 -lbcrypt
poc.exe

A native Windows USE_SSL=1 build of libhiredis_ssl with the same main (no I/O stubs, -lhiredis_ssl -lhiredis) is the same call.

Linux/macOS hiredis_ssl does not compile that strcmp and does not crash with the same options.

Observed

Windows host, PE32+ x86-64, OpenSSL 3.0.15 (mingw64), hiredis ssl.c from 29ea279:

signal 11 in redisCreateSSLContextWithOptions

MinGW’s CRT maps the access violation to SIGSEGV. The process never returns from redisCreateSSLContextWithOptions and never reaches CertOpenSystemStore.

Suggested fix

Do not call strcmp on a NULL file name. Only the "wincert" sentinel should select the system store; a NULL file name with a non-NULL capath should fall through to SSL_CTX_load_verify_locations.

--- a/ssl.c
+++ b/ssl.c
@@ -370,7 +370,7 @@ redisSSLContext *redisCreateSSLContextWithOptions(...)
     if (capath || cacert_filename) {
 #ifdef _WIN32
-        if (0 == strcmp(cacert_filename, "wincert")) {
+        if (cacert_filename && 0 == strcmp(cacert_filename, "wincert")) {
             win_store = CertOpenSystemStore(NULL, "Root");

Environment

  • redis/hiredis 29ea279 (1.5.0-dev)
  • OpenSSL 3.0.15, mingw64, linked into the same PE as ssl.c
  • Windows host running that PE: crash inside redisCreateSSLContextWithOptions
  • Defect is compiled only for _WIN32

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions