From a73b69be2e69e0c4e926940a57e363850e6e84c8 Mon Sep 17 00:00:00 2001 From: Alejandro Pacheco Date: Thu, 15 Oct 2026 04:37:24 +0200 Subject: [PATCH 1/2] Fix double free in ext/ftp data_accept() on accept failure When the server never opens the active-mode data connection, my_accept() fails and data_accept() frees the databuf_t. ftp_getdata() has already stored it in ftp->data, so the caller's bail path then runs data_close(), which frees it a second time. This affects ftp_get(), ftp_put(), ftp_append(), ftp_nb_get(), ftp_nb_put() and the listing functions. data_accept() does not own the buffer: leave it in ftp->data and let data_close() release it, as the SSL failure paths in data_accept() already do. --- ext/ftp/ftp.c | 2 +- .../tests/ftp_get_data_accept_timeout.phpt | 39 +++++++++++++++++++ ext/ftp/tests/server.inc | 5 +++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 ext/ftp/tests/ftp_get_data_accept_timeout.phpt diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 31ec7dc7343a..fd0f295500b8 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -1874,7 +1874,7 @@ data_accept(databuf_t *data, ftpbuf_t *ftp) data->listener = -1; if (data->fd == -1) { - efree(data); + /* data is still owned by ftp->data, the caller's data_close() frees it */ return NULL; } diff --git a/ext/ftp/tests/ftp_get_data_accept_timeout.phpt b/ext/ftp/tests/ftp_get_data_accept_timeout.phpt new file mode 100644 index 000000000000..5ce4b071fabe --- /dev/null +++ b/ext/ftp/tests/ftp_get_data_accept_timeout.phpt @@ -0,0 +1,39 @@ +--TEST-- +ftp_get()/ftp_nb_get() fail cleanly when the active data connection is never opened +--EXTENSIONS-- +ftp +pcntl +--FILE-- + +--CLEAN-- + +--EXPECTF-- +bool(true) +bool(true) + +Warning: ftp_get(): Connection timed out in %s on line %d + +Warning: ftp_get(): File status okay; about to open data connection. in %s on line %d +bool(false) + +Warning: ftp_nb_get(): Connection timed out in %s on line %d + +Warning: ftp_nb_get(): File status okay; about to open data connection. in %s on line %d +bool(true) diff --git a/ext/ftp/tests/server.inc b/ext/ftp/tests/server.inc index add8c3ae70ac..2d9697e4d35d 100644 --- a/ext/ftp/tests/server.inc +++ b/ext/ftp/tests/server.inc @@ -328,6 +328,11 @@ if ($pid) { break; } }elseif (preg_match('/^RETR ([\/]*[\w\h]+)/', $buf, $matches)) { + if ($matches[1] === "no_data_connection") { + // Never open the active data connection. + fputs($s, "150 File status okay; about to open data connection.\r\n"); + continue; + } if(!empty($pasv)){ ; } From 54e736c74f8af792f97f834c26ceca52b78e6a63 Mon Sep 17 00:00:00 2001 From: Alejandro Pacheco Date: Thu, 15 Oct 2026 06:08:35 +0200 Subject: [PATCH 2/2] Match the ETIMEDOUT message on every libc in ftp_get_data_accept_timeout.phpt musl and FreeBSD report "Operation timed out", glibc "Connection timed out". --- ext/ftp/tests/ftp_get_data_accept_timeout.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/ftp/tests/ftp_get_data_accept_timeout.phpt b/ext/ftp/tests/ftp_get_data_accept_timeout.phpt index 5ce4b071fabe..bf0ece9a3fa2 100644 --- a/ext/ftp/tests/ftp_get_data_accept_timeout.phpt +++ b/ext/ftp/tests/ftp_get_data_accept_timeout.phpt @@ -28,12 +28,12 @@ ftp_close($ftp); bool(true) bool(true) -Warning: ftp_get(): Connection timed out in %s on line %d +Warning: ftp_get(): %s timed out in %s on line %d Warning: ftp_get(): File status okay; about to open data connection. in %s on line %d bool(false) -Warning: ftp_nb_get(): Connection timed out in %s on line %d +Warning: ftp_nb_get(): %s timed out in %s on line %d Warning: ftp_nb_get(): File status okay; about to open data connection. in %s on line %d bool(true)