Skip to content

Commit c74362c

Browse files
authored
Merge pull request #469 from rhenium/ky/ssl-unstarted-io
ssl: disallow reading/writing to unstarted SSL socket
2 parents 74041a3 + bf78074 commit c74362c

File tree

2 files changed

+104
-190
lines changed

2 files changed

+104
-190
lines changed

ext/openssl/ossl_ssl.c

+92-139
Original file line numberDiff line numberDiff line change
@@ -1704,8 +1704,7 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts)
17041704
* call-seq:
17051705
* ssl.connect => self
17061706
*
1707-
* Initiates an SSL/TLS handshake with a server. The handshake may be started
1708-
* after unencrypted data has been sent over the socket.
1707+
* Initiates an SSL/TLS handshake with a server.
17091708
*/
17101709
static VALUE
17111710
ossl_ssl_connect(VALUE self)
@@ -1752,8 +1751,7 @@ ossl_ssl_connect_nonblock(int argc, VALUE *argv, VALUE self)
17521751
* call-seq:
17531752
* ssl.accept => self
17541753
*
1755-
* Waits for a SSL/TLS client to initiate a handshake. The handshake may be
1756-
* started after unencrypted data has been sent over the socket.
1754+
* Waits for a SSL/TLS client to initiate a handshake.
17571755
*/
17581756
static VALUE
17591757
ossl_ssl_accept(VALUE self)
@@ -1800,7 +1798,7 @@ static VALUE
18001798
ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
18011799
{
18021800
SSL *ssl;
1803-
int ilen, nread = 0;
1801+
int ilen;
18041802
VALUE len, str;
18051803
rb_io_t *fptr;
18061804
VALUE io, opts = Qnil;
@@ -1810,6 +1808,9 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
18101808
} else {
18111809
rb_scan_args(argc, argv, "11", &len, &str);
18121810
}
1811+
GetSSL(self, ssl);
1812+
if (!ssl_started(ssl))
1813+
rb_raise(eSSLError, "SSL session is not started yet");
18131814

18141815
ilen = NUM2INT(len);
18151816
if (NIL_P(str))
@@ -1825,85 +1826,60 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
18251826
if (ilen == 0)
18261827
return str;
18271828

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

19091885
/*
@@ -1943,78 +1919,55 @@ static VALUE
19431919
ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
19441920
{
19451921
SSL *ssl;
1946-
int nwrite = 0;
19471922
rb_io_t *fptr;
1948-
int nonblock = opts != Qfalse;
1923+
int num, nonblock = opts != Qfalse;
19491924
VALUE tmp, io;
19501925

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

20201973
/*

test/openssl/test_ssl.rb

+12-51
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)