python / cpython

The Python programming language
https://www.python.org/
Other
60.86k stars 29.37k forks source link

SSL_shutdown needs SSL_read() until SSL_ERROR_ZERO_RETURN #74622

Open njsmith opened 7 years ago

njsmith commented 7 years ago
BPO 30437
Nosy @tiran, @alex, @njsmith, @vadmium, @dstufft
PRs
  • python/cpython#6977
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields: ```python assignee = 'https://github.com/tiran' closed_at = None created_at = labels = ['expert-SSL', '3.7', '3.8'] title = 'SSL_shutdown needs SSL_read() until SSL_ERROR_ZERO_RETURN' updated_at = user = 'https://github.com/njsmith' ``` bugs.python.org fields: ```python activity = actor = 'christian.heimes' assignee = 'christian.heimes' closed = False closed_date = None closer = None components = ['SSL'] creation = creator = 'njs' dependencies = [] files = [] hgrepos = [] issue_num = 30437 keywords = ['patch'] message_count = 8.0 messages = ['294216', '294245', '294299', '294301', '301472', '301491', '317028', '317407'] nosy_count = 6.0 nosy_names = ['janssen', 'christian.heimes', 'alex', 'njs', 'martin.panter', 'dstufft'] pr_nums = ['6977'] priority = 'high' resolution = None stage = 'patch review' status = 'open' superseder = None type = None url = 'https://bugs.python.org/issue30437' versions = ['Python 2.7', 'Python 3.6', 'Python 3.7', 'Python 3.8'] ```

    njsmith commented 7 years ago

    The SSL_shutdown man page says that if it returns 0, and an SSL_ERROR_SYSCALL is set, then SSL_ERROR_SYSCALL should be ignored - or at least I think that's what it's trying to say. See the RETURN VALUES section. I think this means we should only raise this error if the return value is \<0? Though I suppose we need to clear out the error queue in any case.

    I ended up changing the code that was triggering this for other reasons and now I'm not hitting it, so it's not exactly urgent for me, but FYI... I was getting SSLSyscallError exceptions from code using memory BIOs and where everything was fine. The test case had one task continually sending data into an SSLObject-based stream while the other end called SSLObject.unwrap() and then ended up continually getting SSLWantRead and reading more data -- after a few cycles of reading it got SSLSyscalError instead.

    vadmium commented 7 years ago

    Maybe bpo-10808?

    tiran commented 7 years ago

    Which OS and OpenSSL version are you on?

    njsmith commented 7 years ago

    Debian testing, x86-64, with:

    Python 3.5.3rc1 (default, Jan  3 2017, 04:40:57) 
    [GCC 6.3.0 20161229] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import ssl
    >>> ssl.OPENSSL_VERSION
    'OpenSSL 1.1.0e  16 Feb 2017'
    tiran commented 6 years ago

    If I understand the man page of SSL_shutdown correctly, than SSL_shutdown() must be called a second time when the first time returned 0. But it does not say how an application shall behave if the second call to SSL_shutdown() also returns 0.

    OpenSSL does not contain an example for bidirectional shutdown. s_client.c only does unidirectional shutdown.

    cURL just ignores the result:

    /*
     * This function is called when an SSL connection is closed.
     */
    void Curl_ossl_close(struct connectdata *conn, int sockindex)
    {
      struct ssl_connect_data *connssl = &conn->ssl[sockindex];
    
      if(connssl->handle) {
        (void)SSL_shutdown(connssl->handle);
        SSL_set_connect_state(connssl->handle);
    
        SSL_free (connssl->handle);
        connssl->handle = NULL;
      }
      if(connssl->ctx) {
        SSL_CTX_free (connssl->ctx);
        connssl->ctx = NULL;
      }
    }
    njsmith commented 6 years ago

    My reading of the man page is that if SSL_shutdown returns 0, this means that it succeeded at doing the first phase of shutdown. If there are errors then they should be ignored, because it actually succeeded.

    If you want to then complete the second phase of shutdown, of course, you have to call it again, but that's no different than any other use of SSL_shutdown.

    If two calls to SSL_shutdown both return zero, then that's definitely a bug in OpenSSL. A return value of zero means that previously the SSL_SENT_SHUTDOWN flag was not set, and now it is set, so that can only happen once per connection. But that's orthogonal to the SSL_ERROR_SYSCALL issue.

    tiran commented 6 years ago

    The issue can occur when the peer sends data while processing the close notify alert.

    The meaningless SSL_ERROR_SYSCALL in SSL_shutdown() is even more severe with OpenSSL 1.1.1 and TLS 1.3. In case the client only writes and never reads, SSL_shutdown fails to unwrap the socket. The issue is caused by the fact that the session ticket is transferred after the handshake. With a SSL_read(), the ticket is stuck on the wire and may not be consumed until OpenSSL waits for close notify TLS alert.

    In https://github.com/openssl/openssl/issues/6262#issuecomment-389987860 Kurt wrote:

    The peer is still allowed to send data after receiving the "close notify" event. If the peer did send data it need to be processed by calling SSL_read() before calling SSL_shutdown() a second time. SSL_read() will indicate the end of the peer data by returning \<= 0 and SSL_get_error() returning SSL_ERROR_ZERO_RETURN. It is recommended to call SSL_read() between SSL_shutdown() calls.

    I hacked SSL_read() into the code and the problem did no longer occur for me. My workaround is not a proper solution, though. The shutdown code is slightly complicated (to say the least).

    tiran commented 6 years ago

    The session ticket issue in TLS 1.3 handshake will be fixed by upstream patch https://github.com/openssl/openssl/pull/6340.

    We still need to drain the SSL socket to remove pending application data before the second SSL_shutdown() call, but it's no longer critical to get basic TLS 1.3 support working.

    Ned, I'm downgrading this issue from blocker to high.