ssl3_shutdown() incorrectly indicates SSL_want_read() or 
SSL_want_write() when the underlying read/write results in a permanent 
error. This means that callers of nonblocking SSL_shutdown() will go 
into an infinite loop retrying the shutdown.

This bug appears in both OpenSSL 0.9.8t and 1.0.1-beta3.

Attached are a sample nonblocking server and a client that triggers the 
infinite loop. In my testing it loops wanting read, but with the 
underlying socket returning end of file. In production, I've seen a real 
server loop wanting write but with the underlying socket failing with EPIPE.



#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>

//#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/err.h>


int main(int argc, char **argv)
{
    SSL_library_init();

    SSL_CTX *ctx=SSL_CTX_new(SSLv3_client_method());
    if (!ctx) goto err;

    SSL *con=SSL_new(ctx);
    if (!con) goto err;

    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in sa;
    sa.sin_family = AF_INET;
    sa.sin_port = htons(10001);
    sa.sin_addr.s_addr=htonl(0x7f000001);
    if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
        perror("connect");
        exit(1);
    }

    if (!SSL_set_fd(con, s)) goto err;

    if (!SSL_connect(con)) goto err;

    fprintf(stdout, "Got handshake\n");

    exit(0);


 err:
    ERR_print_errors_fp(stdout);
    exit(1);
}
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <poll.h>

//#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

int main(int argc, char **argv)
{
    SSL_library_init();

    SSL_CTX *ctx=SSL_CTX_new(SSLv23_server_method());
    if (!ctx) goto err;

    SSL_load_error_strings();
    SSL_library_init();

    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
    SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | 
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
    SSL_CTX_set_cipher_list(ctx, "DEFAULT:-SSLv2:-LOW:-EXPORT");
    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
    SSL_CTX_set_session_id_context(ctx, (const unsigned char *)"nbserver", 7);

    if (SSL_CTX_use_PrivateKey_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
        fprintf(stderr, "Cannot load private key\n");
        goto err;
    }
    if (SSL_CTX_use_certificate_chain_file(ctx, "cert.pem") <= 0) {
        fprintf(stderr, "Cannot load server certificate\n");
        goto err;
    }
    if (SSL_CTX_check_private_key(ctx) <= 0) {
        fprintf(stderr, "Certificate/key verification error\n");
        goto err;
    }

    SSL *con=SSL_new(ctx);
    if (!con) goto err;

    SSL_set_verify(con, SSL_VERIFY_NONE, 0);

    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s == -1) {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in sa;
    sa.sin_family = AF_INET;
    sa.sin_port = htons(10001);
    sa.sin_addr.s_addr=htonl(0x7f000001);
    if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
        perror("bind");
        exit(1);
    }
    if (listen(s, 5) == -1) {
        perror("listen");
        exit(1);
    }
    s = accept(s, (struct sockaddr*)0, 0);
    if (s == -1) {
        perror("accept");
        exit(1);
    }

    if (!SSL_set_fd(con, s)) goto err;

    if (!SSL_accept(con)) goto err;

    fprintf(stdout, "Got handshake\n");

    sleep(3);

    unsigned long on = 1;
    ioctl(s, FIONBIO, &on);

    int r;

    while ((r = SSL_shutdown(con)) != 1) {
        struct pollfd pfd;
        pfd.fd = s;
        const char *want;
        
        if (r == 0) continue;

        if (SSL_want_write(con)) {
            want = "write";
            pfd.events = POLLOUT | POLLHUP | POLLERR;
        } else if (SSL_want_read(con)) {
            want = "read";
            pfd.events = POLLIN | POLLERR;
        } else {
            fprintf(stdout, "Permanent error on shutdown");
            break;
        }
        
        fprintf(stdout, "want=%s\n", want);
        fflush(stdout);
        poll(&pfd, 1, -1);
    }

    exit(0);


 err:
    ERR_print_errors_fp(stdout);
    exit(1);
}

Reply via email to