Skip to content

Commit bf78074

Browse files
committed
ssl: disallow reading/writing to unstarted SSL socket
OpenSSL::SSL::SSLSocket allowed #read and #write to be called before an SSL/TLS handshake is completed. They passed unencrypted data to the underlying socket. This behavior is very odd to have in this library. A verbose mode warning "SSL session is not started yet" was emitted whenever this happened. It also didn't behave well with OpenSSL::Buffering. Let's just get rid of it. Fixes: ruby#9
1 parent 9b4f761 commit bf78074

File tree

2 files changed

+104
-190
lines changed

2 files changed

+104
-190
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 92 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,8 +1697,7 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts)
16971697
* call-seq:
16981698
* ssl.connect => self
16991699
*
1700-
* Initiates an SSL/TLS handshake with a server. The handshake may be started
1701-
* after unencrypted data has been sent over the socket.
1700+
* Initiates an SSL/TLS handshake with a server.
17021701
*/
17031702
static VALUE
17041703
ossl_ssl_connect(VALUE self)
@@ -1745,8 +1744,7 @@ ossl_ssl_connect_nonblock(int argc, VALUE *argv, VALUE self)
17451744
* call-seq:
17461745
* ssl.accept => self
17471746
*
1748-
* Waits for a SSL/TLS client to initiate a handshake. The handshake may be
1749-
* started after unencrypted data has been sent over the socket.
1747+
* Waits for a SSL/TLS client to initiate a handshake.
17501748
*/
17511749
static VALUE
17521750
ossl_ssl_accept(VALUE self)
@@ -1793,7 +1791,7 @@ static VALUE
17931791
ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
17941792
{
17951793
SSL *ssl;
1796-
int ilen, nread = 0;
1794+
int ilen;
17971795
VALUE len, str;
17981796
rb_io_t *fptr;
17991797
VALUE io, opts = Qnil;
@@ -1803,6 +1801,9 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
18031801
} else {
18041802
rb_scan_args(argc, argv, "11", &len, &str);
18051803
}
1804+
GetSSL(self, ssl);
1805+
if (!ssl_started(ssl))
1806+
rb_raise(eSSLError, "SSL session is not started yet");
18061807

18071808
ilen = NUM2INT(len);
18081809
if (NIL_P(str))
@@ -1818,85 +1819,60 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
18181819
if (ilen == 0)
18191820
return str;
18201821

1821-
GetSSL(self, ssl);
18221822
io = rb_attr_get(self, id_i_io);
18231823
GetOpenFile(io, fptr);
1824-
if (ssl_started(ssl)) {
1825-
rb_str_locktmp(str);
1826-
for (;;) {
1827-
nread = SSL_read(ssl, RSTRING_PTR(str), ilen);
1828-
switch(ssl_get_error(ssl, nread)){
1829-
case SSL_ERROR_NONE:
1824+
1825+
rb_str_locktmp(str);
1826+
for (;;) {
1827+
int nread = SSL_read(ssl, RSTRING_PTR(str), ilen);
1828+
switch (ssl_get_error(ssl, nread)) {
1829+
case SSL_ERROR_NONE:
1830+
rb_str_unlocktmp(str);
1831+
rb_str_set_len(str, nread);
1832+
return str;
1833+
case SSL_ERROR_ZERO_RETURN:
1834+
rb_str_unlocktmp(str);
1835+
if (no_exception_p(opts)) { return Qnil; }
1836+
rb_eof_error();
1837+
case SSL_ERROR_WANT_WRITE:
1838+
if (nonblock) {
18301839
rb_str_unlocktmp(str);
1831-
goto end;
1832-
case SSL_ERROR_ZERO_RETURN:
1840+
if (no_exception_p(opts)) { return sym_wait_writable; }
1841+
write_would_block(nonblock);
1842+
}
1843+
io_wait_writable(fptr);
1844+
continue;
1845+
case SSL_ERROR_WANT_READ:
1846+
if (nonblock) {
18331847
rb_str_unlocktmp(str);
1834-
if (no_exception_p(opts)) { return Qnil; }
1835-
rb_eof_error();
1836-
case SSL_ERROR_WANT_WRITE:
1837-
if (nonblock) {
1838-
rb_str_unlocktmp(str);
1839-
if (no_exception_p(opts)) { return sym_wait_writable; }
1840-
write_would_block(nonblock);
1841-
}
1842-
io_wait_writable(fptr);
1843-
continue;
1844-
case SSL_ERROR_WANT_READ:
1845-
if (nonblock) {
1846-
rb_str_unlocktmp(str);
1847-
if (no_exception_p(opts)) { return sym_wait_readable; }
1848-
read_would_block(nonblock);
1849-
}
1850-
io_wait_readable(fptr);
1851-
continue;
1852-
case SSL_ERROR_SYSCALL:
1853-
if (!ERR_peek_error()) {
1854-
rb_str_unlocktmp(str);
1855-
if (errno)
1856-
rb_sys_fail(0);
1857-
else {
1858-
/*
1859-
* The underlying BIO returned 0. This is actually a
1860-
* protocol error. But unfortunately, not all
1861-
* implementations cleanly shutdown the TLS connection
1862-
* but just shutdown/close the TCP connection. So report
1863-
* EOF for now...
1864-
*/
1865-
if (no_exception_p(opts)) { return Qnil; }
1866-
rb_eof_error();
1867-
}
1868-
}
1869-
/* fall through */
1870-
default:
1848+
if (no_exception_p(opts)) { return sym_wait_readable; }
1849+
read_would_block(nonblock);
1850+
}
1851+
io_wait_readable(fptr);
1852+
continue;
1853+
case SSL_ERROR_SYSCALL:
1854+
if (!ERR_peek_error()) {
18711855
rb_str_unlocktmp(str);
1872-
ossl_raise(eSSLError, "SSL_read");
1873-
}
1874-
}
1875-
}
1876-
else {
1877-
ID meth = nonblock ? rb_intern("read_nonblock") : rb_intern("sysread");
1878-
1879-
rb_warning("SSL session is not started yet.");
1880-
#if defined(RB_PASS_KEYWORDS)
1881-
if (nonblock) {
1882-
VALUE argv[3];
1883-
argv[0] = len;
1884-
argv[1] = str;
1885-
argv[2] = opts;
1886-
return rb_funcallv_kw(io, meth, 3, argv, RB_PASS_KEYWORDS);
1887-
}
1888-
#else
1889-
if (nonblock) {
1890-
return rb_funcall(io, meth, 3, len, str, opts);
1856+
if (errno)
1857+
rb_sys_fail(0);
1858+
else {
1859+
/*
1860+
* The underlying BIO returned 0. This is actually a
1861+
* protocol error. But unfortunately, not all
1862+
* implementations cleanly shutdown the TLS connection
1863+
* but just shutdown/close the TCP connection. So report
1864+
* EOF for now...
1865+
*/
1866+
if (no_exception_p(opts)) { return Qnil; }
1867+
rb_eof_error();
1868+
}
1869+
}
1870+
/* fall through */
1871+
default:
1872+
rb_str_unlocktmp(str);
1873+
ossl_raise(eSSLError, "SSL_read");
18911874
}
1892-
#endif
1893-
else
1894-
return rb_funcall(io, meth, 2, len, str);
18951875
}
1896-
1897-
end:
1898-
rb_str_set_len(str, nread);
1899-
return str;
19001876
}
19011877

19021878
/*
@@ -1936,78 +1912,55 @@ static VALUE
19361912
ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
19371913
{
19381914
SSL *ssl;
1939-
int nwrite = 0;
19401915
rb_io_t *fptr;
1941-
int nonblock = opts != Qfalse;
1916+
int num, nonblock = opts != Qfalse;
19421917
VALUE tmp, io;
19431918

1944-
tmp = rb_str_new_frozen(StringValue(str));
19451919
GetSSL(self, ssl);
1920+
if (!ssl_started(ssl))
1921+
rb_raise(eSSLError, "SSL session is not started yet");
1922+
1923+
tmp = rb_str_new_frozen(StringValue(str));
19461924
io = rb_attr_get(self, id_i_io);
19471925
GetOpenFile(io, fptr);
1948-
if (ssl_started(ssl)) {
1949-
for (;;) {
1950-
int num = RSTRING_LENINT(tmp);
1951-
1952-
/* SSL_write(3ssl) manpage states num == 0 is undefined */
1953-
if (num == 0)
1954-
goto end;
1955-
1956-
nwrite = SSL_write(ssl, RSTRING_PTR(tmp), num);
1957-
switch(ssl_get_error(ssl, nwrite)){
1958-
case SSL_ERROR_NONE:
1959-
goto end;
1960-
case SSL_ERROR_WANT_WRITE:
1961-
if (no_exception_p(opts)) { return sym_wait_writable; }
1962-
write_would_block(nonblock);
1963-
io_wait_writable(fptr);
1964-
continue;
1965-
case SSL_ERROR_WANT_READ:
1966-
if (no_exception_p(opts)) { return sym_wait_readable; }
1967-
read_would_block(nonblock);
1968-
io_wait_readable(fptr);
1969-
continue;
1970-
case SSL_ERROR_SYSCALL:
1926+
1927+
/* SSL_write(3ssl) manpage states num == 0 is undefined */
1928+
num = RSTRING_LENINT(tmp);
1929+
if (num == 0)
1930+
return INT2FIX(0);
1931+
1932+
for (;;) {
1933+
int nwritten = SSL_write(ssl, RSTRING_PTR(tmp), num);
1934+
switch (ssl_get_error(ssl, nwritten)) {
1935+
case SSL_ERROR_NONE:
1936+
return INT2NUM(nwritten);
1937+
case SSL_ERROR_WANT_WRITE:
1938+
if (no_exception_p(opts)) { return sym_wait_writable; }
1939+
write_would_block(nonblock);
1940+
io_wait_writable(fptr);
1941+
continue;
1942+
case SSL_ERROR_WANT_READ:
1943+
if (no_exception_p(opts)) { return sym_wait_readable; }
1944+
read_would_block(nonblock);
1945+
io_wait_readable(fptr);
1946+
continue;
1947+
case SSL_ERROR_SYSCALL:
19711948
#ifdef __APPLE__
1972-
/*
1973-
* It appears that send syscall can return EPROTOTYPE if the
1974-
* socket is being torn down. Retry to get a proper errno to
1975-
* make the error handling in line with the socket library.
1976-
* [Bug #14713] https://bugs.ruby-lang.org/issues/14713
1977-
*/
1978-
if (errno == EPROTOTYPE)
1979-
continue;
1949+
/*
1950+
* It appears that send syscall can return EPROTOTYPE if the
1951+
* socket is being torn down. Retry to get a proper errno to
1952+
* make the error handling in line with the socket library.
1953+
* [Bug #14713] https://bugs.ruby-lang.org/issues/14713
1954+
*/
1955+
if (errno == EPROTOTYPE)
1956+
continue;
19801957
#endif
1981-
if (errno) rb_sys_fail(0);
1982-
/* fallthrough */
1983-
default:
1984-
ossl_raise(eSSLError, "SSL_write");
1985-
}
1958+
if (errno) rb_sys_fail(0);
1959+
/* fallthrough */
1960+
default:
1961+
ossl_raise(eSSLError, "SSL_write");
19861962
}
19871963
}
1988-
else {
1989-
ID meth = nonblock ?
1990-
rb_intern("write_nonblock") : rb_intern("syswrite");
1991-
1992-
rb_warning("SSL session is not started yet.");
1993-
#if defined(RB_PASS_KEYWORDS)
1994-
if (nonblock) {
1995-
VALUE argv[2];
1996-
argv[0] = str;
1997-
argv[1] = opts;
1998-
return rb_funcallv_kw(io, meth, 2, argv, RB_PASS_KEYWORDS);
1999-
}
2000-
#else
2001-
if (nonblock) {
2002-
return rb_funcall(io, meth, 2, str, opts);
2003-
}
2004-
#endif
2005-
else
2006-
return rb_funcall(io, meth, 1, str);
2007-
}
2008-
2009-
end:
2010-
return INT2NUM(nwrite);
20111964
}
20121965

20131966
/*

test/openssl/test_ssl.rb

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -373,59 +373,20 @@ def test_client_ca
373373
}
374374
end
375375

376-
def test_read_nonblock_without_session
377-
EnvUtil.suppress_warning do
378-
start_server(start_immediately: false) { |port|
379-
sock = TCPSocket.new("127.0.0.1", port)
380-
ssl = OpenSSL::SSL::SSLSocket.new(sock)
381-
ssl.sync_close = true
382-
383-
assert_equal :wait_readable, ssl.read_nonblock(100, exception: false)
384-
ssl.write("abc\n")
385-
IO.select [ssl]
386-
assert_equal('a', ssl.read_nonblock(1))
387-
assert_equal("bc\n", ssl.read_nonblock(100))
388-
assert_equal :wait_readable, ssl.read_nonblock(100, exception: false)
389-
ssl.close
390-
}
391-
end
392-
end
393-
394-
def test_starttls
395-
server_proc = -> (ctx, ssl) {
396-
while line = ssl.gets
397-
if line =~ /^STARTTLS$/
398-
ssl.write("x")
399-
ssl.flush
400-
ssl.accept
401-
break
402-
end
403-
ssl.write(line)
404-
end
405-
readwrite_loop(ctx, ssl)
406-
}
407-
408-
EnvUtil.suppress_warning do # read/write on not started session
409-
start_server(start_immediately: false,
410-
server_proc: server_proc) { |port|
411-
begin
412-
sock = TCPSocket.new("127.0.0.1", port)
413-
ssl = OpenSSL::SSL::SSLSocket.new(sock)
414-
415-
ssl.puts "plaintext"
416-
assert_equal "plaintext\n", ssl.gets
376+
def test_unstarted_session
377+
start_server do |port|
378+
sock = TCPSocket.new("127.0.0.1", port)
379+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
417380

418-
ssl.puts("STARTTLS")
419-
ssl.read(1)
420-
ssl.connect
381+
assert_raise(OpenSSL::SSL::SSLError) { ssl.syswrite("data") }
382+
assert_raise(OpenSSL::SSL::SSLError) { ssl.sysread(1) }
421383

422-
ssl.puts "over-tls"
423-
assert_equal "over-tls\n", ssl.gets
424-
ensure
425-
ssl&.close
426-
sock&.close
427-
end
428-
}
384+
ssl.connect
385+
ssl.puts "abc"
386+
assert_equal "abc\n", ssl.gets
387+
ensure
388+
ssl&.close
389+
sock&.close
429390
end
430391
end
431392

0 commit comments

Comments
 (0)