Bug#914034: Bug#911938: libhttp-daemon-ssl-perl FTBFS: tests fail: Connection refused

Guilhem Moulin guilhem at debian.org
Mon May 13 00:02:45 BST 2019


Thanks for your analysis, Steffen.  Dropping the Debian-specific patch
is definitely the way to go for libwww/LWP.  However I still believe
IO::Socket::SSL should provide a way to clear SSL_MODE_AUTO_RETRY in
order to fix applications relying on the former OpenSSL defaults, as
suggested in the OpenSSL changelog:

    “SSL_MODE_AUTO_RETRY is enabled by default. Applications that use
    blocking I/O in combination with something like select() or poll()
    will hang. This can be turned off again using SSL_CTX_clear_mode().”

Otherwise the “usual” way to write event loops in blocking I/O won't be
possible with IO::Socket::SSL.

On Sat, 11 May 2019 at 21:56:01 +0200, Steffen Ullrich wrote:
> As far as I can see it has nothing to do with SSL_MODE_AUTO_RETRY but
> instead is caused by expectations on the behavior of select which are
> wrong with TLS 1.3.

Please consider the enclosed netcat-like program.  I don't think I'm
relying on any particular behavior of a specific TLS version, and
follow the practices for polling blocking sockets, as documented in
libssl, Net::SSLeay, and IO::Socket::SSL, namely:

  - If SSL_pending() > 0, skip the (blocking) select() call and instead
    call SSL_read() to process remaining bytes in the current SSL frame.
  - If SSL_read() fails and sets SSL_ERROR_WANT_READ, don't treat it as
    a read error.

The last point however relies on SSL_MODE_AUTO_RETRY being *unset*, like
it used to be with OpenSSL <1.1.1a.  With SSL_MODE_AUTO_RETRY being set,
the program doesn't work properly.  (*Not only for TLSv1.3, but also for
TLSv1.2*).  This is expected with the new default:

    “If the underlying BIO is blocking, a read function will only return
    once the read operation has been finished or an error occurred,
    except when a non-application data record has been processed and
    SSL_MODE_AUTO_RETRY is not set. Note that if SSL_MODE_AUTO_RETRY is
    set and only non-application data is available the call will hang.”
    — https://www.openssl.org/docs/manmaster/man3/SSL_read.html

As seen below, this also breaks with ≤TLSv1.2; but only when the TLS
session is renegotiated, not during the initial handshake.

Generate a self-signed certificate:

    $ openssl req -x509 -keyout /tmp/key.pem -out /tmp/cert.pem -subj /CN=127.0.0.1 -nodes

Start a TLSv1.2 server on [127.0.0.1]:4433:

    $ openssl s_server -accept 127.0.0.1:4433 -key /tmp/key.pem -cert /tmp/cert.pem -tls1_2

Now start the enclosed program in another terminal.  What's being
written in the s_server(1ssl) TTY is echoed on the netcat.pl side, and
vice versa.  All good.  Now trigger renegotiate the TLS session by
pressing ‘r\n’.  The server prints

    SSL_do_handshake -> 1
    Read BLOCK

and netcat ends up being stuck in a blocking read().  So what's being
written client-side won't show up anymore in the server window, until
data is being sent from the server to the client and makes read()
return.

    openssl s_server … -tls1_2                  netcat.pl
    -----------------------------------         ---------
    S: Using default temp DH parameters  
    S: ACCEPT                                 
    S: -----BEGIN SSL SESSION PARAMETERS-----
    S: […]
    S: ---
    S: No server certificate CA names sent
    S: CIPHER is ECDHE-RSA-AES128-GCM-SHA256
    S: Secure Renegotiation IS supported
                                                S: Entering loop...
                                                C: can you hear me now?
    S: can you hear me now?
    C: yes
                                                S: yes 
                                                C: good
    S: good
    C: starving you now
                                                S: starving you now
    C: r
    S: SSL_do_handshake -> 1
    S: Read BLOCK
                                                C: meh, I'm muted
    C: unstarving
    S: meh, I'm muted
                                                S: unstarving

(The ‘C: ’ prefix indicates a line written to the standard input, and
the ‘S: ’ prefix a line written to the standard output or error output.)

After renegotiation, the client is stuck in a blocking read() until the
server sends some data.  Same thing with TLSv1.3, but of course without
the renegotiation part: this happens right at the begining.

    openssl s_server … -tls1_3                  netcat.pl
    -----------------------------------         ---------
    S: Using default temp DH parameters  
    S: ACCEPT                                 
    S: -----BEGIN SSL SESSION PARAMETERS-----
    S: […]
    S: ---
    S: No server certificate CA names sent
    S: CIPHER is TLS_AES_256_GCM_SHA384
    S: Secure Renegotiation IS supported
                                                S: Entering loop...
                                                C: can you hear me now?
                                                C: I guess no...
    C: unstarving
    S: can you hear me now?
    S: I guess no...
                                                S: unstarving

Now the same trace, clearing SSL_MODE_AUTO_RETRY:

    openssl s_server … -tls1_2                  netcat.pl
    -----------------------------------         ---------
    S: Using default temp DH parameters  
    S: ACCEPT                                 
    S: -----BEGIN SSL SESSION PARAMETERS-----
    S: […]
    S: ---
    S: No server certificate CA names sent
    S: CIPHER is ECDHE-RSA-AES128-GCM-SHA256
    S: Secure Renegotiation IS supported
                                                S: Entering loop...
                                                C: can you hear me now?
    S: can you hear me now?
    C: yes
                                                S: yes 
                                                C: good
    S: good
    C: starving you now
                                                S: starving you now
    C: r
    S: SSL_do_handshake -> 1
    S: Read BLOCK
                                                S: SSL want read!
                                                C: haha, I'm not muted
    S: haha, I'm not muted
    C: indeed you're not
                                                S: indeed you're not

And for TLSv1.3:

    openssl s_server … -tls1_3                  netcat.pl
    -----------------------------------         ---------
    S: Using default temp DH parameters  
    S: ACCEPT                                 
    S: -----BEGIN SSL SESSION PARAMETERS-----
    S: […]
    S: ---
    S: No server certificate CA names sent
    S: CIPHER is TLS_AES_256_GCM_SHA384
    S: Secure Renegotiation IS supported
                                                S: Entering loop...
                                                S: SSL want read!
                                                S: SSL want read!
                                                C: can you hear me now?
    S: can you here me now?
    C: yup
                                                S: yup
                                                C: woo
    S: woo

So with SSL_MODE_AUTO_RETRY being unset, SSL_read() fails and set
SSL_ERROR_WANT_READ (the “SSL want read!” messages above). That's AFAICT
the only expectation of that select() loop, and isn't depended on the
TLS version.  As mentioned in the OpenSSL changelog, setting
SSL_MODE_AUTO_RETRY by default breaks programs using select() with
blocking I/O.  For the convenience of “poorly written” programs that do
not properly handle non-application data records.  (With the former
defaults they would break on renegotiation with ≤TLSv1.2; and right at
the beginning for TLSv1.3.  For programs with event loops it's the other
way around.)

> But sysread on a blocking socket is supposed to only return if there
> was at least 1 byte read or if the peer closed the connection or if a
> permanent error occured. And since no application data were send it
> just hangs.

It doesn't do a direct sysread on the raw socket, does it?  AFAICT
$socket->sysread() calls Net::SSLeay::read(), which might fail and
return SSL_ERROR_WANT_READ when receiving a non-application data record.
With SSL_MODE_AUTO_RETRY being cleared, that is.

As neither IO::Socket nor Net::SSLeay forbids the use of polling &
blocking sockets, and the new OpenSSL defaults breaks event loops in
blocking I/O (and that also for <v1.3), please provide an option to turn
off SSL_MODE_AUTO_RETRY on object creation, as suggested by the OpenSSL
developers :-)

-- 
Guilhem.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: netcat.pl
Type: text/x-perl
Size: 1860 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/pkg-perl-maintainers/attachments/20190513/09d1e4b7/attachment.pl>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/pkg-perl-maintainers/attachments/20190513/09d1e4b7/attachment.sig>


More information about the pkg-perl-maintainers mailing list