Skip to content

feof hangs indefinitely #10495

Closed
Closed

Description

@bytestream

Description

I am using horde/imap-client to connect to an IMAPS server. If I disconnect the local network interface after a couple of iterations of https://github.com/bytestream/Imap_Client/blob/ac98de92168777fb8bf0bacf3129fc178523defe/lib/Horde/Imap/Client/Socket/Connection/Socket.php#L186 when it's downloading a 30MB email body then feof hangs indefintely. stream_set_timeout(30); and default_socket_timeout of 60 seem to have no effect...

The following is an example to illustrate the problem, and should not be used as a test case:

<?php

$_stream = @stream_socket_client(
  "ssl://server.com:993",
  $error_number,
  $error_string,
  30,
  STREAM_CLIENT_CONNECT,
  stream_context_create([
    'ssl' => [
      'verify_peer' => false,
      'verify_peer_name' => false,
      'cafile' => '/etc/ssl/certs/ca-certificates.crt',
      'allow_self_signed' => true,
    ],
  ])
);

stream_set_timeout($_stream, 30);
stream_set_read_buffer($_stream, 0);
stream_set_write_buffer($_stream, 0);

while (!feof($_stream)) {
  $in = fread($_stream, 8192);
}

If I dump stream metadata, the result never changes:

array(8) {
  ["crypto"]=>
  array(4) {
    ["protocol"]=>
    string(7) "TLSv1.2"
    ["cipher_name"]=>
    string(27) "ECDHE-RSA-AES128-GCM-SHA256"
    ["cipher_bits"]=>
    int(128)
    ["cipher_version"]=>
    string(7) "TLSv1.2"
  }
  ["timed_out"]=>
  bool(false)
  ["blocked"]=>
  bool(true)
  ["eof"]=>
  bool(false)
  ["stream_type"]=>
  string(14) "tcp_socket/ssl"
  ["mode"]=>
  string(2) "r+"
  ["unread_bytes"]=>
  int(0)
  ["seekable"]=>
  bool(false)
}

When using slightly modified code of:

while ($literal_len > 0) {
  sleep(3);
  if (! is_resource($this->_stream) || feof($this->_stream)) {

strace at the point where the process hangs shows:

16:57:11.921521 clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=3, tv_nsec=0}, 0x7fffb50a22b0) = 0
16:57:14.930968 poll([{fd=11, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=11, revents=POLLIN}])
16:57:14.931033 read(11, "\27\3\3\20\30", 5) = 5
 | 00000  17 03 03 10 18                                    .....            |
16:57:14.931068 read(11, "\26c\306", 4120) = 3
 | 00000  16 63 c6                                          .c.              |
16:57:14.931094 read(11, 

File descriptor 11 is the socket to the IMAPS server.

PHP Version

PHP 8.1.11

Operating System

No response

Activity

  1. bukka commented on May 6, 2023

    @bukka
    Member

    I just tried to recreate this but not successful so might need more details and ideally server code that can be used for recreating. I tried with this:

    Server:

    <?php
    
    $context = stream_context_create(['ssl' => ['local_cert' => __DIR__ . '/cert.pem']]);
    
    $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
    $fp = stream_socket_server("ssl://127.0.0.1:9993", $errornum, $errorstr, $flags, $context);
    $conn = stream_socket_accept($fp);
    
    for ($i = 0; $i < 32; $i++) {
    	$written = fwrite($conn, str_repeat('a', 1000 * 1000));
    	echo "written $written bytes\n";
    	sleep(1);
    }

    Client:

    <?php
    
    $_stream = @stream_socket_client(
      "ssl://127.0.0.1:9993",
      $error_number,
      $error_string,
      30,
      STREAM_CLIENT_CONNECT,
      stream_context_create([
        'ssl' => [
          'verify_peer' => false,
          'verify_peer_name' => false,
          'allow_self_signed' => true,
        ],
      ])
    );
    
    stream_set_timeout($_stream, 30);
    stream_set_read_buffer($_stream, 0);
    stream_set_write_buffer($_stream, 0);
    
    do {
      $in = fread($_stream, 8048);
      echo "read " . strlen($in) . " bytes\n";
      sleep(1);
    } while (!feof($_stream));

    And tried to use this testing cert:

    -----BEGIN CERTIFICATE-----
    MIIFizCCA3OgAwIBAgIBGTANBgkqhkiG9w0BAQUFADCByzEpMCcGA1UEAxMgQ2F0
    YXBocmFjdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxCzAJBgNVBAYTAlBUMQ8wDQYD
    VQQHEwZMaXNib2ExETAPBgNVBAgTCFBvcnR1Z2FsMSkwJwYDVQQKEyBDYXRhcGhy
    YWN0IENlcnRpZmljYXRlIEF1dGhvcml0eTEcMBoGA1UECxMTQ2VydGlmaWNhdGUg
    U2lnbmluZzEkMCIGCSqGSIb3DQEJARYVQ2F0YXBocmFjdEBuZXRjYWJvLnB0MB4X
    DTExMDYwNzIzNTIwM1oXDTE4MTIwMTAwMTExOVowWjEXMBUGA1UEAxMOYnVnNTQ5
    OTIubG9jYWwxCzAJBgNVBAYTAlBUMQ8wDQYDVQQHEwZMaXNib2ExDzANBgNVBAgT
    Bkxpc2JvYTEQMA4GA1UEChMHcGhwLm5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
    gYkCgYEAtUAVQKTgpUPgtFOJ3w3kDJETS45tWeT96kUg1NeYLKW+jNbFhxPoPJv7
    XhfemCaqh2tbq1cdYW906Wp1L+eNQvdTYA2IQG4EQBUlmfyIakOIMsN/RizVkF09
    vlNQwTpaMpqTv7wB8vvwbxb9jbC2ZhQUBEg6PIn18dSstbM9FZ0CAwEAAaOCAWww
    ggFoMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFCysG9r7vXtfHa38AUZeCM6tgH9c
    MIH4BgNVHSMEgfAwge2AFCP7zb9jhhalHyQiLShslQuSqYHBoYHRpIHOMIHLMSkw
    JwYDVQQDEyBDYXRhcGhyYWN0IENlcnRpZmljYXRlIEF1dGhvcml0eTELMAkGA1UE
    BhMCUFQxDzANBgNVBAcTBkxpc2JvYTERMA8GA1UECBMIUG9ydHVnYWwxKTAnBgNV
    BAoTIENhdGFwaHJhY3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRwwGgYDVQQLExND
    ZXJ0aWZpY2F0ZSBTaWduaW5nMSQwIgYJKoZIhvcNAQkBFhVDYXRhcGhyYWN0QG5l
    dGNhYm8ucHSCAQEwCwYDVR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglg
    hkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4ICAQAT
    M7Id7nBSvaDXuStLunfeV0WPAh3DkKWCxw9YK0MjK7E/K5xEiYaWWbz9zuHEcKrN
    MuflSdYVPXTqvD6mHLFNptOgzG6YMOO+rAAEYB5HZ/PYTO6UWAdSLlS96DpA4SS3
    Qwmrc0eXe1p4U8noEN+N3+rAbetjOuvnLG/cpoQGcA8Mws84B/elzjRne5C8N1rF
    Tvdb3bqIqvP1thuPfyh/uIKSQb5ZusHvj7ZBkEs+zQLBRnCcDK4ETXFM0TcKSPar
    d11tve/91BqqemwlA+ntVrVTgi/pnw4wuWxa3GOVmeEeWgtv3063wZ3lGv/72PCh
    gSjxoCoVLaLPTbC/iG2a5+ca2HcF0TjfJqYNCgosgRGlm5IunvuIv+g5jLcZcDSO
    hMw+HzyF8GlDF166YRRb9nUL6AtBisdEw6uQW1vQFRRQS4SGMoArSBw2EBqd7Kvf
    ruCMcrkudC8vbWQHMETEvhAXdAjgsIxLeGCPh0/8mtES1Lnr0TWIrM9evPJkKACj
    f6CyIASkIDZKFf5JwuUh02qvuNLr/QRELfI1NnA1aTYMQQWWOVCBffu4ce+NPdtl
    Uh1vRwWAWI0Zjszw3kUk2vHLbSXeD3bU7gP3IFa1X8XsXBW2SH+BfpNWHUilHj1I
    bX+zqjfaRWDJuZqB9y6iTCu8DfBtbMiTUGcI/Rs9wQ==
    -----END CERTIFICATE-----
    -----BEGIN RSA PRIVATE KEY-----
    MIICXgIBAAKBgQC1QBVApOClQ+C0U4nfDeQMkRNLjm1Z5P3qRSDU15gspb6M1sWH
    E+g8m/teF96YJqqHa1urVx1hb3TpanUv541C91NgDYhAbgRAFSWZ/IhqQ4gyw39G
    LNWQXT2+U1DBOloympO/vAHy+/BvFv2NsLZmFBQESDo8ifXx1Ky1sz0VnQIDAQAB
    AoGBALUEnHUkdgv4P7o5WJACAomedqPWSlYmgoVvpvuLmrq0ihuFAGAIvL+TlTgD
    JNfWfiejTDlSVtCSDTR1kzZVztitfXDxRkWEjGtFjMhk/DJkql3w10SUtcqCiWqw
    /XknyPHZ7A+w7Fu5KRO2LoSIze2ZLKvCfP/M/pLR2fTKGTHtAkEA2NreT1GUnvzj
    u1lb2J0nTZbSQHvEkfpEej9akl0Bc5UkskenEsiXE3cJYA1TbEGSqYCmt23x3Rd2
    FYxm6MwV6wJBANX34ZuUOllsS0FJPbkEAps3M4s59daQSFiEkQc5XjPgVB0xVV7s
    OEBlGkM3eqcCUOMnMI8L9wfBk49sELZCeJcCQQC/y/TL2q/EXo9c6I/faj+U1Exp
    VA5rvhpKtTX6NeBOxh6Kv+z5JAja4nGcTqz2FpkM6giKO+erUFDUhjWOuNK5AkEA
    xkmHnCRLxp8jRodXWeQrfigz7ixydLsVMGL5+9XgRPb5PGyBjwwePR70raH2Wls9
    FqU0zPvrnBZ6Zwlgm2cSVQJAPLYA51Z9piajbTuggpioQ5qbUEDkJjmYHbm8eJnK
    h5NW/EtCk4SBxAc+8ElPrvJjtZyOPWfm4vZF5sDKtC3Fkg==
    -----END RSA PRIVATE KEY-----
    

    I tried different option with terminating the server but no luck. If you could modify it in some reliable way or provide exact steps how to recreate it, that would be great!

  2. self-assigned this
    on May 6, 2023
  3. github-actions commented on May 21, 2023

    @github-actions

    No feedback was provided. The issue is being suspended because we assume that you are no longer experiencing the problem. If this is not the case and you are able to provide the information that was requested earlier, please do so. Thank you.

  4. driskell commented on May 21, 2023

    @driskell

    Just wanted to note that I experienced this symptom with TLS 1.3 and eventually came across https://bugs.php.net/bug.php?id=79501

    Workaround for me was to force 1.2 but of course not a permanent solution but noting here in case people come across this thread before the above one.

  5. bytestream commented on May 21, 2023

    @bytestream
    Author

    Thanks @bukka for the client and server to help reproduce - that's very helpful.

    Thanks @driskell for linking that issue, it does sound like what I've described... and @bukka latest solution on the issue is pretty much what I ended up implementing to work around it.

  6. bukka commented on May 21, 2023

    @bukka
    Member

    @bytestream Just to clarify I have not been able to reproduce it with the script above and that's why I was asking about more feedback. In terms of solution, are you talking about using non blocking socket instead? If so it's just a workaround as non blocking is using different paths. It would be still appreciated to be able to recreate for blocking socket. Are you able to reliably recreate the problem (even with all dependencies - does not need to be necessarily extracted script if it's too hard to craete)? Does it happen with horde client. And what's the imap server?

    From what I see in metadata in this bug report above, then it says TLSv1.2 protocol so seems like potentially somethings else. Although that issue can happen for 1.2 but it is a less likely to be visible (it might happen during renegotiation for example).

  7. reopened this on May 21, 2023
  8. bytestream commented on May 22, 2023

    @bytestream
    Author

    Are you able to reliably recreate the problem (even with all dependencies - does not need to be necessarily extracted script if it's too hard to craete)?

    Steps to replicate:

    1. git clone https://github.com/bytestream/php-src-10495
    2. cd php-src-10495/
    3. Open test.php and configure username [email protected], password, and hostpsec of mail.php.net
    4. Send an email to [email protected] containing random.img as an attachment.
      Make sure that it's the only unread email in your INBOX... the script will download and mark all unread emails as read...
    5. Run test.php and disconnect the network (kill the network manager, pull the ethernet, disconnect the wifi) when it's Downloading emails...
    $ php8.1 --version
    PHP 8.1.18 (cli) (built: Apr 14 2023 04:39:24) (NTS)
    Copyright (c) The PHP Group
    Zend Engine v4.1.18, Copyright (c) Zend Technologies
        with Zend OPcache v8.1.18, Copyright (c), by Zend Technologies
        with Xdebug v3.2.0, Copyright (c) 2002-2022, by Derick Rethans
    
    $ php8.1 test.php

    The output will stop and it just hangs forever.

    bytestream/Imap_Client#10 is the workaround I added to temporarily use a non blocking socket.

  9. bukka commented on Jun 9, 2023

    @bukka
    Member

    @bytestream Thanks for the replication steps. I just tried it.

    Firstly php.net emails are just aliases and the one that I use just points to gmail. I have just gmail accounts actually so tried to set it up with gmail but unfortunately it fails on

    C: 2 AUTHENTICATE PLAIN [INITIAL CLIENT RESPONSE (username: ***@gmail.com)]
    S: 2 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)
    

    I use the same creds as for login as documented in https://support.google.com/a/answer/9003945?hl=en#zippy=%2Cstep-turn-on-imap-in-gmail and IMAP is of course enable so no idea why it doesn't work. I spent a bit of time trying to find a solution but no luck so far - I'm not really an expert on IMAP tbh and don't want to waste that much my dev time on this. Are you able to make it work with any gmail account or potentially another provider where I can get free inbox?

  10. bytestream commented on Jun 9, 2023

    @bytestream
    Author

    @bukka I think you would need to use either an app password, or oauth for Gmail / Microsoft. They recently started enforcing oauth which is a bit more complex to setup.

    I could give you a mailbox on one of my servers? Then at least we're on the same page from a replication perspective. Let me know if you're ok with that and I'll email you the details.

  11. bukka commented on Jun 9, 2023

    @bukka
    Member

    @bytestream That would be great! Please email me the details and I will give it a try.

  12. bukka commented on Dec 21, 2023

    @bukka
    Member

    So I have finally done some debugging of this and it is caused pretty much the same part of code as https://bugs.php.net/bug.php?id=79501 (specifically it happens on SSL_peek that can block forever). I think we will need to make this check non blocking as poll is not enough here. The problem is that if the whole record is not read, it will block which is what I think is happening here. The Redis issue is slightly different as it might also block on write but effectively the same fix should fix both issues.

    I have started looking to a standard test for this which is a bit tricky and having some issues with getting proxy layer working in the way I want so will need to do some debugging there. I would like to also produce test for write part (key update) so might need something more sophisticated potentially. I will be looking more into it in the coming months - it's my top TLS priority so should hopefully get fixed at some point.

  13. bukka commented on Feb 24, 2024

    @bukka
    Member

    PR for the fix create: #13487

  14. added 2 commits that reference this issue on Mar 23, 2024
    d4a2a73
    c1bd9a9
  15. added a commit that references this issue on Mar 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions