Skip to content

Commit aac9ce1

Browse files
committed
ssl: handle callback exceptions in SSLSocket#sysread and #syswrite
Check the ID_callback_state ivar after SSL_read() or SSL_write() returns, similar to what ossl_start_ssl() does. Previously, callbacks that can raise a Ruby exception were only called from ossl_start_ssl(). This has changed in OpenSSL 1.1.1. Particularly, the session_new_cb will be called whenever a client receives a NewSessionTicket message, which can happen at any time during a TLS 1.3 connection.
1 parent 0759018 commit aac9ce1

File tree

2 files changed

+67
-8
lines changed

2 files changed

+67
-8
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
19261926
{
19271927
SSL *ssl;
19281928
int ilen;
1929-
VALUE len, str;
1929+
VALUE len, str, cb_state;
19301930
VALUE opts = Qnil;
19311931

19321932
if (nonblock) {
@@ -1959,6 +1959,14 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
19591959
rb_str_locktmp(str);
19601960
for (;;) {
19611961
int nread = SSL_read(ssl, RSTRING_PTR(str), ilen);
1962+
1963+
cb_state = rb_attr_get(self, ID_callback_state);
1964+
if (!NIL_P(cb_state)) {
1965+
rb_ivar_set(self, ID_callback_state, Qnil);
1966+
ossl_clear_error();
1967+
rb_jump_tag(NUM2INT(cb_state));
1968+
}
1969+
19621970
switch (ssl_get_error(ssl, nread)) {
19631971
case SSL_ERROR_NONE:
19641972
rb_str_unlocktmp(str);
@@ -2048,7 +2056,7 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
20482056
SSL *ssl;
20492057
rb_io_t *fptr;
20502058
int num, nonblock = opts != Qfalse;
2051-
VALUE tmp;
2059+
VALUE tmp, cb_state;
20522060

20532061
GetSSL(self, ssl);
20542062
if (!ssl_started(ssl))
@@ -2065,6 +2073,14 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
20652073

20662074
for (;;) {
20672075
int nwritten = SSL_write(ssl, RSTRING_PTR(tmp), num);
2076+
2077+
cb_state = rb_attr_get(self, ID_callback_state);
2078+
if (!NIL_P(cb_state)) {
2079+
rb_ivar_set(self, ID_callback_state, Qnil);
2080+
ossl_clear_error();
2081+
rb_jump_tag(NUM2INT(cb_state));
2082+
}
2083+
20682084
switch (ssl_get_error(ssl, nwritten)) {
20692085
case SSL_ERROR_NONE:
20702086
return INT2NUM(nwritten);

test/openssl/test_ssl_session.rb

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,11 @@ def test_server_session_cache
219219
# deadlock.
220220
TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_ALL"] == "1"
221221

222-
def test_ctx_client_session_cb
223-
ctx_proc = proc { |ctx| ctx.ssl_version = :TLSv1_2 }
224-
start_server(ctx_proc: ctx_proc) do |port|
222+
def test_ctx_client_session_cb_tls12
223+
start_server do |port|
225224
called = {}
226225
ctx = OpenSSL::SSL::SSLContext.new
226+
ctx.min_version = ctx.max_version = :TLS1_2
227227
ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
228228
ctx.session_new_cb = lambda { |ary|
229229
sock, sess = ary
@@ -233,23 +233,66 @@ def test_ctx_client_session_cb
233233
ctx.session_remove_cb = lambda { |ary|
234234
ctx, sess = ary
235235
called[:remove] = [ctx, sess]
236-
# any resulting value is OK (ignored)
237236
}
238237
end
239238

240239
server_connect_with_session(port, ctx, nil) { |ssl|
241240
assert_equal(1, ctx.session_cache_stats[:cache_num])
242241
assert_equal(1, ctx.session_cache_stats[:connect_good])
243242
assert_equal([ssl, ssl.session], called[:new])
244-
assert(ctx.session_remove(ssl.session))
245-
assert(!ctx.session_remove(ssl.session))
243+
assert_equal(true, ctx.session_remove(ssl.session))
244+
assert_equal(false, ctx.session_remove(ssl.session))
246245
if TEST_SESSION_REMOVE_CB
247246
assert_equal([ctx, ssl.session], called[:remove])
248247
end
249248
}
250249
end
251250
end
252251

252+
def test_ctx_client_session_cb_tls13
253+
omit "TLS 1.3 not supported" unless tls13_supported?
254+
omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl?
255+
256+
start_server do |port|
257+
called = {}
258+
ctx = OpenSSL::SSL::SSLContext.new
259+
ctx.min_version = :TLS1_3
260+
ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
261+
ctx.session_new_cb = lambda { |ary|
262+
sock, sess = ary
263+
called[:new] = [sock, sess]
264+
}
265+
266+
server_connect_with_session(port, ctx, nil) { |ssl|
267+
ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
268+
269+
assert_operator(1, :<=, ctx.session_cache_stats[:cache_num])
270+
assert_operator(1, :<=, ctx.session_cache_stats[:connect_good])
271+
assert_equal([ssl, ssl.session], called[:new])
272+
}
273+
end
274+
end
275+
276+
def test_ctx_client_session_cb_tls13_exception
277+
omit "TLS 1.3 not supported" unless tls13_supported?
278+
omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl?
279+
280+
start_server do |port|
281+
ctx = OpenSSL::SSL::SSLContext.new
282+
ctx.min_version = :TLS1_3
283+
ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
284+
ctx.session_new_cb = lambda { |ary|
285+
raise "in session_new_cb"
286+
}
287+
288+
server_connect_with_session(port, ctx, nil) { |ssl|
289+
assert_raise_with_message(RuntimeError, /in session_new_cb/) {
290+
ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
291+
}
292+
}
293+
end
294+
end
295+
253296
def test_ctx_server_session_cb
254297
connections = nil
255298
called = {}

0 commit comments

Comments
 (0)