Skip to content

Commit d44bee5

Browse files
committed
The later exception wins
1 parent 0eafd72 commit d44bee5

File tree

3 files changed

+81
-25
lines changed

3 files changed

+81
-25
lines changed

ext/openssl/ossl_bio.c

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,56 @@ bio_bwrite0(VALUE args)
175175
}
176176
}
177177

178+
struct call0_args {
179+
VALUE (*func)(VALUE);
180+
VALUE args;
181+
VALUE ret;
182+
};
183+
184+
static VALUE
185+
do_nothing(VALUE _)
186+
{
187+
return Qnil;
188+
}
189+
190+
static VALUE
191+
call_protect1(VALUE args_)
192+
{
193+
struct call0_args *args = (void *)args_;
194+
rb_set_errinfo(Qnil);
195+
args->ret = args->func(args->args);
196+
return Qnil;
197+
}
198+
199+
static VALUE
200+
call_protect0(VALUE args_)
201+
{
202+
/*
203+
* At this point rb_errinfo() may be set by another callback called from
204+
* the same OpenSSL function (e.g., SSL_accept()).
205+
*
206+
* Abusing rb_ensure() to temporarily save errinfo and restore it after
207+
* the BIO callback successfully returns.
208+
*/
209+
rb_ensure(do_nothing, Qnil, call_protect1, args_);
210+
return Qnil;
211+
}
212+
213+
static VALUE
214+
call_protect(VALUE (*func)(VALUE), VALUE args, int *state)
215+
{
216+
/*
217+
* FIXME: should check !NIL_P(rb_ivar_get(ssl_obj, ID_callback_state))
218+
* instead to see if a tag jump is pending or not.
219+
*/
220+
int pending = !NIL_P(rb_errinfo());
221+
struct call0_args call0_args = { func, args, Qfalse };
222+
rb_protect(call_protect0, (VALUE)&call0_args, state);
223+
if (pending && *state)
224+
rb_warn("exception ignored in BIO callback: pending=%d", pending);
225+
return call0_args.ret;
226+
}
227+
178228
static int
179229
bio_bwrite(BIO *bio, const char *data, int dlen)
180230
{
@@ -185,7 +235,7 @@ bio_bwrite(BIO *bio, const char *data, int dlen)
185235
if (ctx->state)
186236
return -1;
187237

188-
VALUE ok = rb_protect(bio_bwrite0, (VALUE)&args, &state);
238+
VALUE ok = call_protect(bio_bwrite0, (VALUE)&args, &state);
189239
if (state) {
190240
ctx->state = state;
191241
return -1;
@@ -250,7 +300,7 @@ bio_bread(BIO *bio, char *data, int dlen)
250300
if (ctx->state)
251301
return -1;
252302

253-
VALUE ok = rb_protect(bio_bread0, (VALUE)&args, &state);
303+
VALUE ok = call_protect(bio_bread0, (VALUE)&args, &state);
254304
if (state) {
255305
ctx->state = state;
256306
return -1;
@@ -280,7 +330,7 @@ bio_ctrl(BIO *bio, int cmd, long larg, void *parg)
280330
case BIO_CTRL_EOF:
281331
return ctx->eof;
282332
case BIO_CTRL_FLUSH:
283-
rb_protect(bio_flush0, (VALUE)ctx, &state);
333+
call_protect(bio_flush0, (VALUE)ctx, &state);
284334
ctx->state = state;
285335
return !state;
286336
default:

ext/openssl/ossl_ssl.c

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,28 +1690,26 @@ ossl_ssl_setup(VALUE self)
16901690
static void
16911691
check_bio_error(VALUE self, SSL *ssl, VALUE bobj, int ret)
16921692
{
1693-
VALUE cb_state = rb_attr_get(self, ID_callback_state);
1694-
if (!NIL_P(cb_state)) {
1695-
/* must cleanup OpenSSL error stack before re-raising */
1696-
ossl_clear_error();
1697-
rb_jump_tag(NUM2INT(cb_state));
1698-
}
1699-
1700-
// Socket BIO -> nothing to do
17011693
if (NIL_P(bobj)) {
17021694
#ifdef _WIN32
17031695
errno = rb_w32_map_errno(WSAGetLastError());
17041696
#endif
1705-
return;
17061697
}
1707-
1708-
int state = ossl_bio_state(bobj);
1709-
if (!state) {
1698+
else {
1699+
int state = ossl_bio_state(bobj);
1700+
if (state) {
1701+
ossl_clear_error();
1702+
rb_jump_tag(state);
1703+
}
17101704
errno = 0;
1711-
return;
17121705
}
1713-
ossl_clear_error();
1714-
rb_jump_tag(state);
1706+
1707+
VALUE cb_state = rb_attr_get(self, ID_callback_state);
1708+
if (!NIL_P(cb_state)) {
1709+
/* must cleanup OpenSSL error stack before re-raising */
1710+
ossl_clear_error();
1711+
rb_jump_tag(NUM2INT(cb_state));
1712+
}
17151713
}
17161714

17171715
static void

test/openssl/test_ssl.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def test_synthetic_io_errors_in_servername_cb
261261
end
262262

263263
def test_synthetic_io_errors_in_callback_and_socket
264-
assert_separately(["-ropenssl"], <<~"end;")
264+
assert_separately(["-ropenssl"], <<~"end;", ignore_stderr: true)
265265
begin
266266
#sock1, sock2 = socketpair
267267
sock1, sock2 = if defined? UNIXSocket
@@ -273,25 +273,33 @@ def test_synthetic_io_errors_in_callback_and_socket
273273
t = Thread.new {
274274
s1 = OpenSSL::SSL::SSLSocket.new(sock1)
275275
s1.hostname = "localhost"
276-
assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) {
276+
begin
277277
s1.connect
278-
}
278+
rescue
279+
end
279280
}
280281
282+
called = []
281283
ctx2 = OpenSSL::SSL::SSLContext.new
282284
ctx2.servername_cb = lambda { |args|
283-
throw :throw_from, :servername_cb
285+
called << :servername_cb
286+
raise "servername_cb"
284287
}
285288
obj = Object.new
286289
obj.define_singleton_method(:method_missing) { |name, *args, **kwargs| sock2.__send__(name, *args, **kwargs) }
287290
obj.define_singleton_method(:respond_to_missing?) { |name, *args, **kwargs| sock2.respond_to?(name, *args, **kwargs) }
288291
obj.define_singleton_method(:write_nonblock) { |*args, **kwargs|
289-
raise "write_nonblock"
292+
called << :write_nonblock
293+
throw :throw_from, :write_nonblock
290294
}
291295
s2 = OpenSSL::SSL::SSLSocket.new(obj, ctx2)
292296
293-
ret = catch(:throw_from) { s2.accept }
294-
assert_equal(:servername_cb, ret)
297+
ret = assert_warning(/exception ignored/) {
298+
catch(:throw_from) { s2.accept }
299+
}
300+
assert_equal(:write_nonblock, ret)
301+
assert_equal([:servername_cb, :write_nonblock], called)
302+
sock2.close
295303
assert t.join
296304
ensure
297305
sock1.close

0 commit comments

Comments
 (0)