[ruby/openssl] ssl: fix errno display in exception messages

The errno reported in an OpenSSL::SSL::SSLError raised by
SSLSocket#accept and #connect sometimes does not match what SSL_accept()
or SSL_connect() actually encountered. Depending on the evaluation order
of arguments passed to ossl_raise(), errno may be overwritten by
peeraddr_ip_str().

While we could just fix peeraddr_ip_str(), we should avoid passing
around errno since it is error-prone. Replace rb_sys_fail() and
rb_io_{maybe_,}wait_{read,writ}able() with equivalents that do not read
errno.

https://github.com/ruby/openssl/commit/bfc7df860f
This commit is contained in:
Kazuki Yamaguchi 2025-12-03 02:57:18 +09:00 committed by git
parent 05b85fc1ab
commit 0379aab6c0

View File

@ -1714,11 +1714,15 @@ ossl_ssl_setup(VALUE self)
return Qtrue;
}
static int
errno_mapped(void)
{
#ifdef _WIN32
#define ssl_get_error(ssl, ret) (errno = rb_w32_map_errno(WSAGetLastError()), SSL_get_error((ssl), (ret)))
return rb_w32_map_errno(WSAGetLastError());
#else
#define ssl_get_error(ssl, ret) SSL_get_error((ssl), (ret))
return errno;
#endif
}
static void
write_would_block(int nonblock)
@ -1759,13 +1763,13 @@ static void
io_wait_writable(VALUE io)
{
#ifdef HAVE_RB_IO_MAYBE_WAIT
if (!rb_io_maybe_wait_writable(errno, io, RUBY_IO_TIMEOUT_DEFAULT)) {
if (!rb_io_wait(io, INT2NUM(RUBY_IO_WRITABLE), RUBY_IO_TIMEOUT_DEFAULT)) {
rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become writable!");
}
#else
rb_io_t *fptr;
GetOpenFile(io, fptr);
rb_io_wait_writable(fptr->fd);
rb_thread_fd_writable(fptr->fd);
#endif
}
@ -1773,13 +1777,13 @@ static void
io_wait_readable(VALUE io)
{
#ifdef HAVE_RB_IO_MAYBE_WAIT
if (!rb_io_maybe_wait_readable(errno, io, RUBY_IO_TIMEOUT_DEFAULT)) {
if (!rb_io_wait(io, INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT)) {
rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become readable!");
}
#else
rb_io_t *fptr;
GetOpenFile(io, fptr);
rb_io_wait_readable(fptr->fd);
rb_thread_wait_fd(fptr->fd);
#endif
}
@ -1787,7 +1791,6 @@ static VALUE
ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts)
{
SSL *ssl;
int ret, ret2;
VALUE cb_state;
int nonblock = opts != Qfalse;
@ -1797,7 +1800,8 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts)
VALUE io = rb_attr_get(self, id_i_io);
for (;;) {
ret = func(ssl);
int ret = func(ssl);
int saved_errno = errno_mapped();
cb_state = rb_attr_get(self, ID_callback_state);
if (!NIL_P(cb_state)) {
@ -1809,7 +1813,8 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts)
if (ret > 0)
break;
switch ((ret2 = ssl_get_error(ssl, ret))) {
int code = SSL_get_error(ssl, ret);
switch (code) {
case SSL_ERROR_WANT_WRITE:
if (no_exception_p(opts)) { return sym_wait_writable; }
write_would_block(nonblock);
@ -1823,10 +1828,11 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts)
case SSL_ERROR_SYSCALL:
#ifdef __APPLE__
/* See ossl_ssl_write_internal() */
if (errno == EPROTOTYPE)
if (saved_errno == EPROTOTYPE)
continue;
#endif
if (errno) rb_sys_fail(funcname);
if (saved_errno)
rb_exc_raise(rb_syserr_new(saved_errno, funcname));
/* fallthrough */
default: {
VALUE error_append = Qnil;
@ -1847,9 +1853,9 @@ ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts)
ossl_raise(eSSLError,
"%s%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s%"PRIsVALUE,
funcname,
ret2 == SSL_ERROR_SYSCALL ? " SYSCALL" : "",
ret2,
errno,
code == SSL_ERROR_SYSCALL ? " SYSCALL" : "",
code,
saved_errno,
peeraddr_ip_str(self),
SSL_state_string_long(ssl),
error_append);
@ -1992,6 +1998,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
for (;;) {
rb_str_locktmp(str);
int nread = SSL_read(ssl, RSTRING_PTR(str), ilen);
int saved_errno = errno_mapped();
rb_str_unlocktmp(str);
cb_state = rb_attr_get(self, ID_callback_state);
@ -2001,7 +2008,7 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
rb_jump_tag(NUM2INT(cb_state));
}
switch (ssl_get_error(ssl, nread)) {
switch (SSL_get_error(ssl, nread)) {
case SSL_ERROR_NONE:
rb_str_set_len(str, nread);
return str;
@ -2024,8 +2031,8 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
break;
case SSL_ERROR_SYSCALL:
if (!ERR_peek_error()) {
if (errno)
rb_sys_fail(0);
if (saved_errno)
rb_exc_raise(rb_syserr_new(saved_errno, "SSL_read"));
else {
/*
* The underlying BIO returned 0. This is actually a
@ -2110,6 +2117,7 @@ ossl_ssl_write_internal_safe(VALUE _args)
for (;;) {
int nwritten = SSL_write(ssl, RSTRING_PTR(str), num);
int saved_errno = errno_mapped();
cb_state = rb_attr_get(self, ID_callback_state);
if (!NIL_P(cb_state)) {
@ -2118,7 +2126,7 @@ ossl_ssl_write_internal_safe(VALUE _args)
rb_jump_tag(NUM2INT(cb_state));
}
switch (ssl_get_error(ssl, nwritten)) {
switch (SSL_get_error(ssl, nwritten)) {
case SSL_ERROR_NONE:
return INT2NUM(nwritten);
case SSL_ERROR_WANT_WRITE:
@ -2139,10 +2147,11 @@ ossl_ssl_write_internal_safe(VALUE _args)
* make the error handling in line with the socket library.
* [Bug #14713] https://bugs.ruby-lang.org/issues/14713
*/
if (errno == EPROTOTYPE)
if (saved_errno == EPROTOTYPE)
continue;
#endif
if (errno) rb_sys_fail(0);
if (saved_errno)
rb_exc_raise(rb_syserr_new(saved_errno, "SSL_write"));
/* fallthrough */
default:
ossl_raise(eSSLError, "SSL_write");