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); }