diff --git a/ext/openssl/tests/stream_socket_get_crypto_status_supplemental_read.phpt b/ext/openssl/tests/stream_socket_get_crypto_status_supplemental_read.phpt new file mode 100644 index 000000000000..9044b1bbdbe4 --- /dev/null +++ b/ext/openssl/tests/stream_socket_get_crypto_status_supplemental_read.phpt @@ -0,0 +1,83 @@ +--TEST-- +stream_socket_get_crypto_status(): reports status NONE after supplemental read +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + ['local_cert' => '%s']]); + $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; + $server = stream_socket_server("tls://127.0.0.1:0", $errno, $errstr, $flags, $ctx); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server, 30); + + fwrite($conn, "hello\n"); + + phpt_wait(); + fclose($conn); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$clientCode = <<<'CODE' + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'peer_name' => '%s', + ]]); + + $client = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); + stream_set_blocking($client, false); + + $buf = ''; + $read = [$client]; + $write = $except = null; + while (stream_select($read, $write, $except, 5)) { + // Initially, read only the first char, then request more than is stored + // in the buffer, triggering a supplemental read. + $chunk = fread($client, strlen($buf) === 0 ? 1 : 10); + if ($chunk === '' || $chunk === false) { + /* A non-application record (e.g. a TLS 1.3 session ticket) may arrive first. */ + if (feof($client)) { + break; + } + } else { + $buf .= $chunk; + if (strlen($buf) >= 6) { + break; + } + } + $read = [$client]; + $write = $except = null; + } + + echo trim($buf), "\n"; + /* A successful read clears the pending status back to NONE. */ + var_dump(stream_socket_get_crypto_status($client) === STREAM_CRYPTO_STATUS_NONE); + + phpt_notify(); + fclose($client); +CODE; +$clientCode = sprintf($clientCode, $peerName); + +include 'CertificateGenerator.inc'; +$certificateGenerator = new CertificateGenerator(); +$certificateGenerator->saveNewCertAsFileWithKey($peerName, $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--CLEAN-- + +--EXPECT-- +hello +bool(true) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index a154aa4572f7..2fc335ca1d55 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2997,6 +2997,16 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); } + /* This might be a supplemental read after consuming buffered data. If + * the read returned nothing, ignore status WANT_READ. */ + if (read && + nr_bytes <= 0 && + sslsock->last_status == STREAM_CRYPTO_STATUS_WANT_READ && + stream->has_buffered_data + ) { + sslsock->last_status = STREAM_CRYPTO_STATUS_NONE; + } + /* And if we were originally supposed to be blocking, let's reset the socket to that. */ if (began_blocked) { php_openssl_set_blocking(sslsock, 1);