Skip to content

src/ssl_sock.c: Segmentation fault when using certificates in configuration on windows #1765

@sgnn7

Description

@sgnn7

Detailed Description of the Problem

When running HAProxy 2.4.17 on Windows built/run against OpenSSL 1.0.2 series and using certificates on the frontend, application crashes with a SIGSEGV. Removal of the ssl/cert in the bind command makes the HAProxy start up without issues.

Expected Behavior

HAProxy starts up without a segmentation fault

Steps to Reproduce the Behavior

Pre-requisites:

  • Windows
  • OpenSSL 1.0.2u dlls built with MSYS2/MinGW-x64
  • HAProxy 2.4.17
    • Built using Cygwin and following flags:
      make PREFIX="${dist_dir}" \
         TARGET=cygwin \
         USE_LIBCRYPT= \
         USE_PCRE=  \
         USE_PCRE2=  \
         USE_STATIC_PCRE2=  \
         USE_PCRE2_JIT=  \
         USE_THREAD=0 \
         USE_ZLIB=1 \
         USE_OPENSSL=1 \
         DEBUG_FULL=1 \
         SSL_INC="${ssl_dir_prefix}/include" \
         SSL_LIB="${ssl_dir_prefix}/lib" \
         CFLAGS="-O0 -g -w -fwrapv" \
         LDFLAGS="-Wl,-rpath=${ssl_dir_prefix}/lib" \
         -j8 all
    
  • Run ./haproxy.exe -f ./haproxy.cfg with following in the settings (full config provided as part of this issue):
...

frontend foo
  # bind *:3834 # working
  bind *:3834 ssl crt "./certs/openssl_rsa_server.pem" ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384
  mode http
  option tcplog
  option http-server-close
  option forwardfor
  default_backend foo_backend
  monitor-uri /haproxy_test
  ...

Do you have any idea what may have caused this?

Only an educated guess given that this codebase is new to me and that the source file in question is massive:

The context being freed using SSL_CTX_free(bind_conf->default_ctx) here (L2994 in ssl_sock.c) is assumed not to be the same pointer as ckch_inst->ctx but that's not the case:

...
(gdb) p ckch_inst->ctx
$17 = (SSL_CTX *) 0x40f2e0
(gdb) p bind_conf->default_ctx
$18 = (SSL_CTX *) 0x40f2e0

Assumption here from reading the code is that we want to dispose of the old bind_conf->default_ctx which is in actuality also the memory location of ckch_inst->ctx (an incomplete struct that we want to use moving forward) so when SSL_CTX_free(bind_conf->default_ctx) is trying to zeroize this struct, it hits invalid memory pointers. Excerpt from the backtrace:

...
0x000000006318ce8a in x509_verify_param_zero (param=0xfeeefeeefeeefeee) at C:/Users/foo/tmp/haproxy-cert-crash/openssl/openssl-1.0.2u/crypto/x509/x509_vpm.c:138
138         param->name = NULL;

Do you have an idea how to solve the issue?

My guess for the fix patch:

From ae274daea27899bdbebb500510dd2034fdeebf56 Mon Sep 17 00:00:00 2001
From: Srdjan Grubor <sgnn7@sgnn7.org>
Date: Wed, 29 Jun 2022 15:55:54 -0500
Subject: [PATCH] BUG/MEDIUM: ssl: Fix crash when loading certificates from
 file

When using certificates from file and running against OpenSSL, the
assumption that the current and the old context are different pointers
may not be valid. This change ensures that we do not zeroize an
incompletely-initialized SSL context.
---
 src/ssl_sock.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 7f74977e1..52d9bb953 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2991,7 +2991,7 @@ void ssl_sock_load_cert_sni(struct ckch_inst *ckch_inst, struct bind_conf *bind_
 	}
 
 	/* replace the default_ctx if required with the instance's ctx. */
-	if (ckch_inst->is_default) {
+	if (ckch_inst->is_default && bind_conf->default_ctx != ckch_inst->ctx) {
 		SSL_CTX_free(bind_conf->default_ctx);
 		SSL_CTX_up_ref(ckch_inst->ctx);
 		bind_conf->default_ctx = ckch_inst->ctx;
-- 
2.36.1

What is your configuration?

# Basic configuration

global
    log 127.0.0.1 local0 debug
    ssl-default-server-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384

# Some sane defaults
defaults
    log     global
    option  dontlognull
    retries 3
    # option  redispatch
    option  redispatch httplog
    timeout client 5s
    timeout server 5s
    timeout connect 5s

# This declares a view into HAProxy statistics, on port 3833
# You do not need credentials to view this page and you can
# turn it off once you are done with setup.
listen stats
    bind *:3833
    mode http
    stats enable
    stats uri /

# This section is to reload DNS Records
# Replace these addresses with your DNS Server IP addresses.
resolvers my-dns
    nameserver dns1 8.8.8.8:53
    nameserver dns2 1.1.1.1:53
    resolve_retries 3
    timeout resolve 2s
    timeout retry 1s
    accepted_payload_size 8192
    hold valid 10s
    hold obsolete 60s

frontend e2e_request_frontend
#    bind *:3834
    bind *:3834 ssl crt "./certs/openssl_rsa_server.pem" ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384
    mode http
    option tcplog
    option http-server-close
    option forwardfor
    default_backend e2e_request_backend
    monitor-uri /haproxy_test


backend e2e_request_backend
    # default server ssl ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384
    balance roundrobin
    mode http
    server-template mothership 5 10.17.1.10:443 check port 443 ssl verify none check
    # server server1 server:443 check port 443 ssl verify none check
    # server server1 server:443 ssl verify none check resolvers my-dns init-addr none resolve-prefer ipv4 maxconn 20

Output of haproxy -vv

$ ./haproxy -vv
HAProxy version 2.4.17-9f97155 2022/05/13 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-2.4.17.html
Running on: CYGWIN_NT-10.0-17763 3.3.5-341.x86_64 2022-05-13 12:27 UTC x86_64
Build options :
  TARGET  = cygwin
  CPU     = generic
  CC      = cc
  CFLAGS  = -O0 -g -w -fwrapv
  OPTIONS = USE_PCRE= USE_PCRE2= USE_PCRE2_JIT= USE_THREAD=0 USE_STATIC_PCRE2= USE_LIBCRYPT= USE_OPENSSL=1 USE_ZLIB=1
  DEBUG   =

Feature list : -EPOLL -KQUEUE -NETFILTER -PCRE -PCRE_JIT -PCRE2 -PCRE2_JIT +POLL -PRIVATE_CACHE +THREAD -PTHREAD_PSHARED -BACKTRACE -STATIC_PCRE -STATIC_PCRE2 +TPROXY -LINUX_TPROXY -LINUX_SPLICE -LIBCRYPT -CRYPT_H -GETADDRINFO +OPENSSL -LUA -FUTEX -ACCEPT4 -CLOSEFROM +ZLIB -SLZ -CPU_AFFINITY -TFO -NS -DL -RT -DEVICEATLAS -51DEGREES -WURFL -SYSTEMD +OBSOLETE_LINKER -PRCTL -PROCCTL -THREAD_DUMP -EVPORTS -OT -QUIC -PROMEX -MEMORY_PROFILING

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_THREADS=64, default=1).
Built with OpenSSL version : OpenSSL 1.0.2u 20 Dec 2019
Running on OpenSSL version : OpenSSL 1.0.2u  20 Dec 2019
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2
Built with zlib version : 1.2.12
Running on zlib version : 1.2.12
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Built with transparent proxy support using:
Built without PCRE or PCRE2 support (using libc's regex instead)
Encrypted password support via crypt(3): no
Built with gcc compiler version 11.3.0

Available polling systems :
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 2 (2 usable), will use poll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
              h2 : mode=HTTP       side=FE|BE     mux=H2       flags=HTX|CLEAN_ABRT|HOL_RISK|NO_UPG
            fcgi : mode=HTTP       side=BE        mux=FCGI     flags=HTX|HOL_RISK|NO_UPG
              h1 : mode=HTTP       side=FE|BE     mux=H1       flags=HTX|NO_UPG
       <default> : mode=HTTP       side=FE|BE     mux=H1       flags=HTX
            none : mode=TCP        side=FE|BE     mux=PASS     flags=NO_UPG
       <default> : mode=TCP        side=FE|BE     mux=PASS     flags=

Available services : none

Available filters :
        [SPOE] spoe
        [CACHE] cache
        [FCGI] fcgi-app
        [COMP] compression
        [TRACE] trace

Last Outputs and Backtraces

(gdb) bt
#0  0x000000006318ce8a in x509_verify_param_zero (param=0xfeeefeeefeeefeee) at C:/Users/foo/tmp/haproxy-cert-crash/openssl/openssl-1.0.2u/crypto/x509/x509_vpm.c:138
#1  0x000000006318d0f7 in X509_VERIFY_PARAM_free (param=0xfeeefeeefeeefeee) at C:/Users/foo/tmp/haproxy-cert-crash/openssl/openssl-1.0.2u/crypto/x509/x509_vpm.c:202
#2  0x000000027d140e07 in SSL_CTX_free (a=0x40f410) at C:/Users/foo/tmp/haproxy-cert-crash/openssl/openssl-1.0.2u/ssl/ssl_lib.c:2137
#3  0x000000010040bb6e in ssl_sock_load_cert_sni (ckch_inst=ckch_inst@entry=0x8000cae90, bind_conf=bind_conf@entry=0x8000c6920) at src/ssl_sock.c:2995
#4  0x000000010041030e in ssl_sock_load_ckchs (path=0x80007ee44 "./certs/openssl_rsa_server.pem", ssl_conf=0x0, sni_filter=0x0, fcount=0, err=0xffffc380, ckch_inst=0xffffb198,
    bind_conf=0x8000c6920, ckchs=0x8000c6d80) at src/ssl_sock.c:3539
#5  ssl_sock_load_ckchs (err=0xffffc380, ckch_inst=0xffffb198, fcount=0, sni_filter=0x0, ssl_conf=0x0, bind_conf=0x8000c6920, ckchs=0x8000c6d80,
    path=0x80007ee44 "./certs/openssl_rsa_server.pem") at src/ssl_sock.c:3527
#6  ssl_sock_load_cert (path=0x80007ee44 "./certs/openssl_rsa_server.pem", bind_conf=0x8000c6920, err=0xffffc380) at src/ssl_sock.c:3705
#7  0x000000010051c2b7 in cfg_parse_listen (file=0x80006e230 "haproxy.cfg", linenum=44, args=0xffffc700, kwm=<optimized out>) at src/cfgparse-listen.c:438
#8  0x00000001004dfb0a in readcfgfile (file=0x80006e230 "haproxy.cfg") at src/cfgparse.c:2332
#9  0x00000001005a51cd in init (argv=<optimized out>, argc=<optimized out>) at src/haproxy.c:1834
#10 main (argc=<optimized out>, argv=0xffffcc00) at src/haproxy.c:2921

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: needs-triageThis issue needs to be triaged.type: bugThis issue describes a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions