Skip to content

Commit bb4a2e8

Browse files
manuelmnikic
authored andcommitted
Fix #76972: FTP data truncation due to forceful ssl socket shutdown
Do a correct bidirectional shutdown instead
1 parent abfda3d commit bb4a2e8

File tree

2 files changed

+71
-7
lines changed

2 files changed

+71
-7
lines changed

NEWS

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ PHP NEWS
66
. Fixed bug #76946 (Cyclic reference in generator not detected). (Nikita)
77

88
- FCGI:
9-
. Fixed #76948 (Failed shutdown/reboot or end session in Windows). (Anatol)
9+
. Fixed bug #76948 (Failed shutdown/reboot or end session in Windows).
10+
(Anatol)
11+
12+
- FTP:
13+
. Fixed bug #76972 (Data truncation due to forceful ssl socket shutdown).
14+
(Manuel Mausz)
1015

1116
11 Oct 2018, PHP 7.1.23
1217

ext/ftp/ftp.c

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767

6868
#ifdef HAVE_FTP_SSL
6969
#include <openssl/ssl.h>
70+
#include <openssl/err.h>
7071
#endif
7172

7273
#include "ftp.h"
@@ -111,6 +112,11 @@ static databuf_t* data_close(ftpbuf_t *ftp, databuf_t *data);
111112
/* generic file lister */
112113
static char** ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path);
113114

115+
#ifdef HAVE_FTP_SSL
116+
/* shuts down a TLS/SSL connection */
117+
static void ftp_ssl_shutdown(ftpbuf_t *ftp, php_socket_t fd, SSL *ssl_handle);
118+
#endif
119+
114120
/* IP and port conversion box */
115121
union ipbox {
116122
struct in_addr ia[2];
@@ -184,8 +190,7 @@ ftp_close(ftpbuf_t *ftp)
184190
if (ftp->fd != -1) {
185191
#ifdef HAVE_FTP_SSL
186192
if (ftp->ssl_active) {
187-
SSL_shutdown(ftp->ssl_handle);
188-
SSL_free(ftp->ssl_handle);
193+
ftp_ssl_shutdown(ftp, ftp->fd, ftp->ssl_handle);
189194
}
190195
#endif
191196
closesocket(ftp->fd);
@@ -1743,6 +1748,62 @@ data_accept(databuf_t *data, ftpbuf_t *ftp)
17431748
}
17441749
/* }}} */
17451750

1751+
/* {{{ ftp_ssl_shutdown
1752+
*/
1753+
#ifdef HAVE_FTP_SSL
1754+
static void ftp_ssl_shutdown(ftpbuf_t *ftp, php_socket_t fd, SSL *ssl_handle) {
1755+
/* In TLS 1.3 it's common to receive session tickets after the handshake has completed. We need to train
1756+
the socket (read the tickets until EOF/close_notify alert) before closing the socket. Otherwise the
1757+
server might get an ECONNRESET which might lead to data truncation on server side.
1758+
*/
1759+
char buf[256]; /* We will use this for the OpenSSL error buffer, so it has
1760+
to be at least 256 bytes long.*/
1761+
int done = 1, err, nread;
1762+
unsigned long sslerror;
1763+
1764+
err = SSL_shutdown(ssl_handle);
1765+
if (err < 0) {
1766+
php_error_docref(NULL, E_WARNING, "SSL_shutdown failed");
1767+
}
1768+
else if (err == 0) {
1769+
/* The shutdown is not yet finished. Call SSL_read() to do a bidirectional shutdown. */
1770+
done = 0;
1771+
}
1772+
1773+
while (!done) {
1774+
if (data_available(ftp, fd)) {
1775+
ERR_clear_error();
1776+
nread = SSL_read(ssl_handle, buf, sizeof(buf));
1777+
err = SSL_get_error(ssl_handle, nread);
1778+
switch (err) {
1779+
case SSL_ERROR_NONE: /* this is not an error */
1780+
case SSL_ERROR_ZERO_RETURN: /* no more data */
1781+
/* This is the expected response. There was no data but only
1782+
the close notify alert */
1783+
done = 1;
1784+
break;
1785+
case SSL_ERROR_WANT_READ:
1786+
/* there's data pending, re-invoke SSL_read() */
1787+
break;
1788+
case SSL_ERROR_WANT_WRITE:
1789+
/* SSL wants a write. Really odd. Let's bail out. */
1790+
done = 1;
1791+
break;
1792+
default:
1793+
if ((sslerror = ERR_get_error())) {
1794+
ERR_error_string_n(sslerror, buf, sizeof(buf));
1795+
}
1796+
php_error_docref(NULL, E_WARNING, "SSL_read on shutdown: %s (%d)", (sslerror ? buf : strerror(errno)), errno);
1797+
done = 1;
1798+
break;
1799+
}
1800+
}
1801+
}
1802+
(void)SSL_free(ssl_handle);
1803+
}
1804+
#endif
1805+
/* }}} */
1806+
17461807
/* {{{ data_close
17471808
*/
17481809
databuf_t*
@@ -1758,8 +1819,7 @@ data_close(ftpbuf_t *ftp, databuf_t *data)
17581819
#ifdef HAVE_FTP_SSL
17591820
if (data->ssl_active) {
17601821
/* don't free the data context, it's the same as the control */
1761-
SSL_shutdown(data->ssl_handle);
1762-
SSL_free(data->ssl_handle);
1822+
ftp_ssl_shutdown(ftp, data->listener, data->ssl_handle);
17631823
data->ssl_active = 0;
17641824
}
17651825
#endif
@@ -1769,8 +1829,7 @@ data_close(ftpbuf_t *ftp, databuf_t *data)
17691829
#ifdef HAVE_FTP_SSL
17701830
if (data->ssl_active) {
17711831
/* don't free the data context, it's the same as the control */
1772-
SSL_shutdown(data->ssl_handle);
1773-
SSL_free(data->ssl_handle);
1832+
ftp_ssl_shutdown(ftp, data->fd, data->ssl_handle);
17741833
data->ssl_active = 0;
17751834
}
17761835
#endif

0 commit comments

Comments
 (0)