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