tls.c C 593 lines View on github.com → Search inside
1#include "memcached.h"23#ifdef TLS45#include "tls.h"6#include <string.h>7#include <sysexits.h>8#include <sys/param.h>9#include <openssl/ssl.h>10#include <openssl/err.h>1112/* constant session ID context for application-level SSL session scoping.13 * used in server-side SSL session caching, when enabled. */14#define SESSION_ID_CONTEXT "memcached"1516#ifndef MAXPATHLEN17#define MAXPATHLEN 409618#endif1920static ssize_t ssl_read(conn *c, void *buf, size_t count);21static ssize_t ssl_sendmsg(conn *c, struct msghdr *msg, int flags);22static ssize_t ssl_write(conn *c, void *buf, size_t count);23static void print_ssl_error(char *buff, size_t len);24static void ssl_callback(const SSL *s, int where, int ret);25static int ssl_new_session_callback(SSL *s, SSL_SESSION *sess);2627static pthread_mutex_t ssl_ctx_lock = PTHREAD_MUTEX_INITIALIZER;2829const unsigned ERROR_MSG_SIZE = 64;30const size_t SSL_ERROR_MSG_SIZE = 256;3132static void SSL_LOCK(void) {33    pthread_mutex_lock(&(ssl_ctx_lock));34}3536static void SSL_UNLOCK(void) {37    pthread_mutex_unlock(&(ssl_ctx_lock));38}3940void *ssl_accept(conn *c, int sfd, bool *fail) {41    SSL *ssl = NULL;42    if (c->ssl_enabled) {43        assert(IS_TCP(c->transport) && settings.ssl_enabled);4445        if (settings.ssl_ctx == NULL) {46            if (settings.verbose) {47                fprintf(stderr, "SSL context is not initialized\n");48            }49            *fail = true;50            return NULL;51        }52        SSL_LOCK();53        ssl = SSL_new(settings.ssl_ctx);54        SSL_UNLOCK();55        if (ssl == NULL) {56            if (settings.verbose) {57                fprintf(stderr, "Failed to created the SSL object\n");58            }59            *fail = true;60            ERR_clear_error();61            return NULL;62        }63        SSL_set_fd(ssl, sfd);6465        if (c->ssl_enabled == MC_SSL_ENABLED_NOPEER) {66            // Don't enforce peer certs for this socket.67            SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);68        } else if (c->ssl_enabled == MC_SSL_ENABLED_PEER) {69            // Force peer validation for this socket.70            SSL_set_verify(ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);71        }7273        ERR_clear_error();74        int ret = SSL_accept(ssl);75        if (ret <= 0) {76            int err = SSL_get_error(ssl, ret);77            if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {78                // we're actually fine, let the worker thread continue.79                ERR_clear_error();80            } else {81                // TODO: ship full error to log stream? conn events?82                // SYSCALL specifically means we need to check errno/strerror.83                // Else we need to look at the main error stack.84                if (err == SSL_ERROR_SYSCALL) {85                    LOGGER_LOG(NULL, LOG_CONNEVENTS, LOGGER_CONNECTION_TLSERROR,86                            NULL, c->sfd, strerror(errno));87                } else {88                    char ssl_err[SSL_ERROR_MSG_SIZE];89                    ssl_err[0] = '\0';90                    // OpenSSL internal error. One or more, but lets only care about91                    // the top error for now.92                    print_ssl_error(ssl_err, SSL_ERROR_MSG_SIZE);93                    LOGGER_LOG(NULL, LOG_CONNEVENTS, LOGGER_CONNECTION_TLSERROR,94                            NULL, c->sfd, ssl_err);95                }96                ERR_clear_error();97                SSL_free(ssl);98                STATS_LOCK();99                stats.ssl_handshake_errors++;100                STATS_UNLOCK();101                *fail = true;102                return NULL;103            }104        }105    }106107    return ssl;108}109110/*111 * Note on setting errno in the follow functions:112 * We either have to refactor callers of read/write/sendmsg to take an error113 * flag to find out if we're in an EAGAIN state, or we ensure the errno is set114 * properly before returning from our TLS call. We do this because it's115 * _possible_ for OpenSSL to do something weird and land with an errno that116 * doesn't match the WANT_READ|WRITE state.117 *118 * Also: we _might_ have to communicate from these calls if we need to wait on119 * reads or write. Since I haven't yet proved that's even possible I'll save120 * that for a future refactor.121 */122123// TODO: add int offset, and find the nth NID here.124// or different function that accepts a string, then does etc?125// Caller _must immediately_ use the string and not store the pointer.126const unsigned char *ssl_get_peer_cn(conn *c, int *len) {127    if (!c->ssl) {128        return NULL;129    }130131    // can't use get0 to avoid getting a reference since that requires 3.0.0+132    X509 *cert = SSL_get_peer_certificate(c->ssl);133    if (cert == NULL) {134        return NULL;135    }136    X509_NAME *name = X509_get_subject_name(cert);137    if (name == NULL) {138        X509_free(cert);139        return NULL;140    }141142    int r = X509_NAME_get_index_by_NID(name, NID_commonName, -1);143    if (r == -1) {144        X509_free(cert);145        return NULL;146    }147    ASN1_STRING *asn1 = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, r));148149    if (asn1 == NULL) {150        X509_free(cert);151        return NULL;152    }153    *len = ASN1_STRING_length(asn1);154    X509_free(cert);155    return ASN1_STRING_get0_data(asn1);156}157158/*159 * Reads decrypted data from the underlying BIO read buffers,160 * which reads from the socket.161 */162static ssize_t ssl_read(conn *c, void *buf, size_t count) {163    assert (c != NULL);164    /* TODO : document the state machine interactions for SSL_read with165        non-blocking sockets/ SSL re-negotiations166    */167168    ssize_t ret = SSL_read(c->ssl, buf, count);169    if (ret <= 0) {170        int err = SSL_get_error(c->ssl, ret);171        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {172            errno = EAGAIN;173        } else if (err == SSL_ERROR_ZERO_RETURN) {174            // TLS session is closed... let the caller move this along.175            return 0;176        } else if (err == SSL_ERROR_SYSCALL) {177            // need to rely on errno to find out what happened178            LOGGER_LOG(c->thread->l, LOG_CONNEVENTS, LOGGER_CONNECTION_TLSERROR,179                    NULL, c->sfd, strerror(errno));180        } else if (ret != 0) {181            char ssl_err[SSL_ERROR_MSG_SIZE];182            // OpenSSL internal error. One or more, but lets only care about183            // the top error for now.184            print_ssl_error(ssl_err, SSL_ERROR_MSG_SIZE);185            LOGGER_LOG(c->thread->l, LOG_CONNEVENTS, LOGGER_CONNECTION_TLSERROR,186                    NULL, c->sfd, ssl_err);187            STATS_LOCK();188            stats.ssl_proto_errors++;189            STATS_UNLOCK();190        }191        ERR_clear_error();192    }193194    return ret;195}196197/*198 * Writes data to the underlying BIO write buffers,199 * which encrypt and write them to the socket.200 */201static ssize_t ssl_write(conn *c, void *buf, size_t count) {202    assert (c != NULL);203204    ssize_t ret = SSL_write(c->ssl, buf, count);205    if (ret <= 0) {206        int err = SSL_get_error(c->ssl, ret);207        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {208            errno = EAGAIN;209        } else if (err == SSL_ERROR_ZERO_RETURN) {210            // TLS session is closed... let the caller move this along.211            return 0;212        } else if (err == SSL_ERROR_SYSCALL) {213            // need to rely on errno to find out what happened214            LOGGER_LOG(c->thread->l, LOG_CONNEVENTS, LOGGER_CONNECTION_TLSERROR,215                    NULL, c->sfd, strerror(errno));216        } else if (ret != 0) {217            char ssl_err[SSL_ERROR_MSG_SIZE];218            // OpenSSL internal error. One or more, but lets only care about219            // the top error for now.220            print_ssl_error(ssl_err, SSL_ERROR_MSG_SIZE);221            LOGGER_LOG(c->thread->l, LOG_CONNEVENTS, LOGGER_CONNECTION_TLSERROR,222                    NULL, c->sfd, ssl_err);223            STATS_LOCK();224            stats.ssl_proto_errors++;225            STATS_UNLOCK();226        }227        ERR_clear_error();228    }229    return ret;230}231232/*233 * SSL sendmsg implementation. Perform a SSL_write.234 */235static ssize_t ssl_sendmsg(conn *c, struct msghdr *msg, int flags) {236    assert (c != NULL);237    size_t buf_remain = settings.ssl_wbuf_size;238    size_t bytes = 0;239    size_t to_copy;240    int i;241242    // ssl_wbuf is pointing to the buffer allocated in the worker thread.243    assert(c->ssl_wbuf);244    // TODO: allocate a fix buffer in crawler/logger if they start using245    // the sendmsg method. Also, set c->ssl_wbuf  when the side thread246    // start owning the connection and reset the pointer in247    // conn_worker_readd.248    // Currently this connection would not be served by a different thread249    // than the one it's assigned.250    assert(pthread_equal(c->thread->thread_id, pthread_self()) != 0);251252    char *bp = c->ssl_wbuf;253    for (i = 0; i < msg->msg_iovlen; i++) {254        size_t len = msg->msg_iov[i].iov_len;255        to_copy = len < buf_remain ? len : buf_remain;256257        memcpy(bp + bytes, (void*)msg->msg_iov[i].iov_base, to_copy);258        buf_remain -= to_copy;259        bytes += to_copy;260        if (buf_remain == 0)261            break;262    }263    /* TODO : document the state machine interactions for SSL_write with264        non-blocking sockets/ SSL re-negotiations265    */266    return ssl_write(c, c->ssl_wbuf, bytes);267}268269/*270 * Prints an SSL error into the buff, if there's any.271 */272static void print_ssl_error(char *buff, size_t len) {273    unsigned long err;274    if ((err = ERR_get_error()) != 0) {275        ERR_error_string_n(err, buff, len);276    }277}278279/*280 * Loads server certificates to the SSL context and validate them.281 * @return whether certificates are successfully loaded and verified or not.282 * @param error_msg contains the error when unsuccessful.283 */284static bool load_server_certificates(char **errmsg) {285    bool success = false;286287    const size_t CRLF_NULLCHAR_LEN = 3;288    char *error_msg = malloc(MAXPATHLEN + ERROR_MSG_SIZE +289        SSL_ERROR_MSG_SIZE);290    size_t errmax = MAXPATHLEN + ERROR_MSG_SIZE + SSL_ERROR_MSG_SIZE -291        CRLF_NULLCHAR_LEN;292293    if (error_msg == NULL) {294        *errmsg = NULL;295        return false;296    }297298    if (settings.ssl_ctx == NULL) {299        snprintf(error_msg, errmax, "Error TLS not enabled\r\n");300        *errmsg = error_msg;301        return false;302    }303304    char *ssl_err_msg = malloc(SSL_ERROR_MSG_SIZE);305    if (ssl_err_msg == NULL) {306        free(error_msg);307        *errmsg = NULL;308        return false;309    }310    bzero(ssl_err_msg, SSL_ERROR_MSG_SIZE);311    size_t err_msg_size = 0;312313    SSL_LOCK();314    if (!SSL_CTX_use_certificate_chain_file(settings.ssl_ctx,315        settings.ssl_chain_cert)) {316        print_ssl_error(ssl_err_msg, SSL_ERROR_MSG_SIZE);317        err_msg_size = snprintf(error_msg, errmax, "Error loading the certificate chain: "318            "%s : %s", settings.ssl_chain_cert, ssl_err_msg);319    } else if (!SSL_CTX_use_PrivateKey_file(settings.ssl_ctx, settings.ssl_key,320                                        settings.ssl_keyformat)) {321        print_ssl_error(ssl_err_msg, SSL_ERROR_MSG_SIZE);322        err_msg_size = snprintf(error_msg, errmax, "Error loading the key: %s : %s",323            settings.ssl_key, ssl_err_msg);324    } else if (!SSL_CTX_check_private_key(settings.ssl_ctx)) {325        print_ssl_error(ssl_err_msg, SSL_ERROR_MSG_SIZE);326        err_msg_size = snprintf(error_msg, errmax, "Error validating the certificate: %s",327            ssl_err_msg);328    } else if (settings.ssl_ca_cert) {329        if (!SSL_CTX_load_verify_locations(settings.ssl_ctx,330          settings.ssl_ca_cert, NULL)) {331            print_ssl_error(ssl_err_msg, SSL_ERROR_MSG_SIZE);332            err_msg_size = snprintf(error_msg, errmax,333              "Error loading the CA certificate: %s : %s",334              settings.ssl_ca_cert, ssl_err_msg);335        } else {336            SSL_CTX_set_client_CA_list(settings.ssl_ctx,337              SSL_load_client_CA_file(settings.ssl_ca_cert));338            success = true;339        }340    } else {341        success = true;342    }343    SSL_UNLOCK();344    free(ssl_err_msg);345    if (success) {346        settings.ssl_last_cert_refresh_time = current_time;347        free(error_msg);348    } else {349        *errmsg = error_msg;350        error_msg += (err_msg_size >= errmax ? errmax - 1: err_msg_size);351        snprintf(error_msg, CRLF_NULLCHAR_LEN, "\r\n");352        // Print if there are more errors and drain the queue.353        ERR_print_errors_fp(stderr);354    }355    return success;356}357358void ssl_conn_close(void *ssl_in) {359    SSL *ssl = ssl_in;360    SSL_shutdown(ssl);361    SSL_free(ssl);362}363364int ssl_pending(void *ssl_in) {365    SSL *ssl = ssl_in;366    return SSL_pending(ssl);367}368369void ssl_init_conn(conn *c, void *ssl_in) {370    if (ssl_in) {371        SSL *ssl = ssl_in;372        c->ssl = (SSL*)ssl;373        c->read = ssl_read;374        c->sendmsg = ssl_sendmsg;375        c->write = ssl_write;376        SSL_set_info_callback(c->ssl, ssl_callback);377    }378}379380void ssl_init_settings(void) {381    settings.ssl_enabled = false;382    settings.ssl_ctx = NULL;383    settings.ssl_chain_cert = NULL;384    settings.ssl_key = NULL;385    settings.ssl_verify_mode = SSL_VERIFY_NONE;386    settings.ssl_keyformat = SSL_FILETYPE_PEM;387    settings.ssl_ciphers = NULL;388    settings.ssl_ca_cert = NULL;389    settings.ssl_last_cert_refresh_time = current_time;390    settings.ssl_wbuf_size = 16 * 1024; // default is 16KB (SSL max frame size is 17KB)391    settings.ssl_session_cache = false;392    settings.ssl_kernel_tls = false;393    settings.ssl_min_version = TLS1_2_VERSION;394}395396/*397 * Verify SSL settings and initiates the SSL context.398 */399int ssl_init(void) {400    assert(settings.ssl_enabled);401402    OPENSSL_init_ssl(0, NULL);403404    // SSL context for the process. All connections will share one405    // process level context.406    settings.ssl_ctx = SSL_CTX_new(TLS_server_method());407408    SSL_CTX_set_min_proto_version(settings.ssl_ctx, settings.ssl_min_version);409410    // The server certificate, private key and validations.411    char *error_msg;412    if (!load_server_certificates(&error_msg)) {413        fprintf(stderr, "%s", error_msg);414        free(error_msg);415        exit(EX_USAGE);416    }417418    // The verification mode of client certificate, default is SSL_VERIFY_PEER.419    SSL_CTX_set_verify(settings.ssl_ctx, settings.ssl_verify_mode, NULL);420    if (settings.ssl_ciphers && !SSL_CTX_set_cipher_list(settings.ssl_ctx,421                                                    settings.ssl_ciphers)) {422        fprintf(stderr, "Error setting the provided cipher(s): %s\n",423                settings.ssl_ciphers);424        exit(EX_USAGE);425    }426427    // Optional session caching; default disabled.428    if (settings.ssl_session_cache) {429        SSL_CTX_sess_set_new_cb(settings.ssl_ctx, ssl_new_session_callback);430        SSL_CTX_set_session_cache_mode(settings.ssl_ctx, SSL_SESS_CACHE_SERVER);431        SSL_CTX_set_session_id_context(settings.ssl_ctx,432                                       (const unsigned char *) SESSION_ID_CONTEXT,433                                       strlen(SESSION_ID_CONTEXT));434    } else {435        SSL_CTX_set_session_cache_mode(settings.ssl_ctx, SSL_SESS_CACHE_OFF);436    }437438    // Optional kernel TLS offload; default disabled.439    if (settings.ssl_kernel_tls) {440#if defined(SSL_OP_ENABLE_KTLS)441        SSL_CTX_set_options(settings.ssl_ctx, SSL_OP_ENABLE_KTLS);442#else443        fprintf(stderr, "Kernel TLS offload is not available\n");444        exit(EX_USAGE);445#endif446    }447448#ifdef SSL_OP_NO_RENEGOTIATION449    // Disable TLS re-negotiation if SSL_OP_NO_RENEGOTIATION is defined for450    // openssl 1.1.0h or above451    SSL_CTX_set_options(settings.ssl_ctx, SSL_OP_NO_RENEGOTIATION);452#endif453454    // Release TLS read/write buffers of idle connections455    SSL_CTX_set_mode(settings.ssl_ctx, SSL_MODE_RELEASE_BUFFERS);456457    return 0;458}459460/*461 * This method is registered with each SSL connection and abort the SSL session462 * if a client initiates a renegotiation for openssl versions before 1.1.0h.463 * For openssl 1.1.0h and above, TLS re-negotiation is disabled by setting the464 * SSL_OP_NO_RENEGOTIATION option in SSL_CTX_set_options.465 */466void ssl_callback(const SSL *s, int where, int ret) {467    // useful for debugging.468    // fprintf(stderr, "WHERE: %d RET: %d CODE: %s LONG: %s\n", where, ret, SSL_state_string(s), SSL_state_string_long(s));469#ifndef SSL_OP_NO_RENEGOTIATION470    SSL* ssl = (SSL*)s;471    if (SSL_in_before(ssl)) {472        fprintf(stderr, "%d: SSL renegotiation is not supported, "473                "closing the connection\n", SSL_get_fd(ssl));474        SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);475        return;476    }477#endif478}479480/*481 * This method is invoked with every new successfully negotiated SSL session,482 * when server-side session caching is enabled. Note that this method is not483 * invoked when a session is reused.484 */485int ssl_new_session_callback(SSL *s, SSL_SESSION *sess) {486    STATS_LOCK();487    stats.ssl_new_sessions++;488    STATS_UNLOCK();489490    return 0;491}492493bool refresh_certs(char **errmsg) {494    return load_server_certificates(errmsg);495}496497void ssl_help(void) {498    printf("   - ssl_chain_cert:      certificate chain file in PEM format\n"499           "   - ssl_key:             private key, if not part of the -ssl_chain_cert\n"500           "   - ssl_keyformat:       private key format (PEM, DER or ENGINE) (default: PEM)\n");501    printf("   - ssl_verify_mode:     peer certificate verification mode, default is 0(None).\n"502           "                          valid values are 0(None), 1(Request), 2(Require)\n"503           "                          or 3(Once)\n");504    printf("   - ssl_ciphers:         specify cipher list to be used\n"505           "   - ssl_ca_cert:         PEM format file of acceptable client CA's\n"506           "   - ssl_wbuf_size:       size in kilobytes of per-connection SSL output buffer\n"507           "                          (default: %u)\n", settings.ssl_wbuf_size / (1 << 10));508    printf("   - ssl_session_cache:   enable server-side SSL session cache, to support session\n"509           "                          resumption\n"510           "   - ssl_kernel_tls:      enable kernel TLS offload\n"511           "   - ssl_min_version:     minimum protocol version to accept (default: %s)\n",512           ssl_proto_text(settings.ssl_min_version));513#if defined(TLS1_3_VERSION)514    printf("                          valid values are 0(%s), 1(%s), 2(%s), or 3(%s).\n",515           ssl_proto_text(TLS1_VERSION), ssl_proto_text(TLS1_1_VERSION),516           ssl_proto_text(TLS1_2_VERSION), ssl_proto_text(TLS1_3_VERSION));517#else518    printf("                          valid values are 0(%s), 1(%s), or 2(%s).\n",519           ssl_proto_text(TLS1_VERSION), ssl_proto_text(TLS1_1_VERSION),520           ssl_proto_text(TLS1_2_VERSION));521#endif522    verify_default("ssl_keyformat", settings.ssl_keyformat == SSL_FILETYPE_PEM);523    verify_default("ssl_verify_mode", settings.ssl_verify_mode == SSL_VERIFY_NONE);524    verify_default("ssl_min_version", settings.ssl_min_version == TLS1_2_VERSION);525}526527const char *ssl_proto_text(int version) {528    switch (version) {529        case TLS1_VERSION:530            return "tlsv1.0";531        case TLS1_1_VERSION:532            return "tlsv1.1";533        case TLS1_2_VERSION:534            return "tlsv1.2";535#if defined(TLS1_3_VERSION)536        case TLS1_3_VERSION:537            return "tlsv1.3";538#endif539        default:540            return "unknown";541    }542}543544// TODO: would be nice to pull the entire set of startup option parsing into545// here like we do with extstore. To save time I'm only pulling subsection546// that require openssl headers to start.547bool ssl_set_verify_mode(int verify) {548    switch(verify) {549        case 0:550            settings.ssl_verify_mode = SSL_VERIFY_NONE;551            break;552        case 1:553            settings.ssl_verify_mode = SSL_VERIFY_PEER;554            break;555        case 2:556            settings.ssl_verify_mode = SSL_VERIFY_PEER |557                                        SSL_VERIFY_FAIL_IF_NO_PEER_CERT;558            break;559        case 3:560            settings.ssl_verify_mode = SSL_VERIFY_PEER |561                                        SSL_VERIFY_FAIL_IF_NO_PEER_CERT |562                                        SSL_VERIFY_CLIENT_ONCE;563            break;564        default:565            return false;566    }567    return true;568}569570bool ssl_set_min_version(int version) {571    switch (version) {572        case 0:573            settings.ssl_min_version = TLS1_VERSION;574            break;575        case 1:576            settings.ssl_min_version = TLS1_1_VERSION;577            break;578        case 2:579            settings.ssl_min_version = TLS1_2_VERSION;580            break;581#if defined(TLS1_3_VERSION)582        case 3:583            settings.ssl_min_version = TLS1_3_VERSION;584            break;585#endif586        default:587            return false;588    }589    return true;590}591592#endif // ifdef TLS

Code quality findings 1

Warning: bzero() is deprecated. Use memset(ptr, 0, size) instead.
warning deprecated deprecated-bzero
bzero(ssl_err_msg, SSL_ERROR_MSG_SIZE);

Security findings 1

Warning: Potential use-after-free. Pointer should be set to NULL after free() and not used again without reassignment.
warning security use-after-free
free(error_msg);

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings