From ad6c43c72bbc2741179e3bba54db73cc7594535c Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Thu, 18 Jun 2026 23:05:52 +0200 Subject: [PATCH 1/3] Windows: Skip installing main/poll/* headers (#22317) In Autotools these aren't installed and neither are there any public headers. --- win32/build/config.w32 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/build/config.w32 b/win32/build/config.w32 index e099167c076a..a0f26033306f 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -312,7 +312,7 @@ ADD_SOURCES("win32", "dllmain.c readdir.c \ ADD_FLAG("CFLAGS_BD_WIN32", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); -PHP_INSTALL_HEADERS("", "Zend/ TSRM/ main/ main/poll/ main/streams/ win32/"); +PHP_INSTALL_HEADERS("", "Zend/ TSRM/ main/ main/streams/ win32/"); PHP_INSTALL_HEADERS("Zend/Optimizer", "zend_call_graph.h zend_cfg.h zend_dfg.h zend_dump.h zend_func_info.h zend_inference.h zend_optimizer.h zend_ssa.h zend_worklist.h"); STDOUT.WriteBlankLines(1); From ec84d9663e5087c98ce6cfbc86eb15637627e6fe Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 18 Jun 2026 23:50:04 +0200 Subject: [PATCH 2/3] Use ZEND_CONTAINER_OF() in poll API --- ext/standard/io_poll.c | 6 ++---- main/php_poll.h | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c index 4bb7f6a80688..c36306a0b49b 100644 --- a/ext/standard/io_poll.c +++ b/ext/standard/io_poll.c @@ -68,11 +68,9 @@ typedef struct php_stream_poll_handle_data { } php_stream_poll_handle_data; /* Accessor macros */ -#define PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(_obj) \ - ((php_io_poll_context_object *) ((char *) (_obj) - offsetof(php_io_poll_context_object, std))) +#define PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(_obj) ZEND_CONTAINER_OF(_obj, php_io_poll_context_object, std) -#define PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(_obj) \ - ((php_io_poll_watcher_object *) ((char *) (_obj) - offsetof(php_io_poll_watcher_object, std))) +#define PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(_obj) ZEND_CONTAINER_OF(_obj, php_io_poll_watcher_object, std) #define PHP_POLL_WATCHER_OBJ_FROM_ZV(_zv) PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv)) #define PHP_POLL_CONTEXT_OBJ_FROM_ZV(_zv) PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv)) diff --git a/main/php_poll.h b/main/php_poll.h index 254f885a6d3b..c73052712940 100644 --- a/main/php_poll.h +++ b/main/php_poll.h @@ -158,8 +158,7 @@ struct php_poll_handle_object { zend_object std; }; -#define PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(obj) \ - ((php_poll_handle_object *) ((char *) (obj) - offsetof(php_poll_handle_object, std))) +#define PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(obj) ZEND_CONTAINER_OF(obj, php_poll_handle_object, std) #define PHP_POLL_HANDLE_OBJ_FROM_ZV(zv) PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(Z_OBJ_P(zv)) From 35fde02e424faaddba2ebf27245e16697acaea4d Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 15 Jun 2026 20:34:20 -0400 Subject: [PATCH 3/3] ext/ftp: fix out-of-bounds read in ftp_get() ASCII CRLF translation In ASCII mode ftp_get() peeks at the byte after each '\r' to fold CRLF into LF. When that '\r' is the last byte of a full FTP_BUFSIZE read, the lookahead reads one byte past the buffer; a server placing '\r' at offset 4095 of a 4096-byte read hits it (ASAN: heap-buffer-overflow read of size 1). Bound the lookahead to the received data, as ftp_readline() does. ftp_nb_continue_read() carries the trailing '\r' across reads and isn't affected. Closes GH-22328 --- ext/ftp/ftp.c | 2 +- .../tests/ftp_get_ascii_crlf_boundary.phpt | 25 +++++++++++++++++++ ext/ftp/tests/server.inc | 7 ++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 ext/ftp/tests/ftp_get_ascii_crlf_boundary.phpt diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 36a0d1697eca..ca5e05ead81b 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -946,7 +946,7 @@ ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t pat #else while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) { php_stream_write(outstream, ptr, (s - ptr)); - if (*(s + 1) == '\n') { + if (s + 1 < e && *(s + 1) == '\n') { s++; php_stream_putc(outstream, '\n'); } diff --git a/ext/ftp/tests/ftp_get_ascii_crlf_boundary.phpt b/ext/ftp/tests/ftp_get_ascii_crlf_boundary.phpt new file mode 100644 index 000000000000..4c3a70b647c6 --- /dev/null +++ b/ext/ftp/tests/ftp_get_ascii_crlf_boundary.phpt @@ -0,0 +1,25 @@ +--TEST-- +ftp_get() ASCII mode: CRLF straddling the FTP_BUFSIZE read boundary +--EXTENSIONS-- +ftp +pcntl +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/ftp/tests/server.inc b/ext/ftp/tests/server.inc index c2c8449a0686..4c9d2a754bff 100644 --- a/ext/ftp/tests/server.inc +++ b/ext/ftp/tests/server.inc @@ -391,6 +391,13 @@ if ($pid) { // Just a side channel for getting the received file size. fputs($s, "425 Can't open data connection (".$GLOBALS['rest_pos'].").\r\n"); break; + case "crlf_boundary": + // A CRLF whose CR lands on the final byte of the first + // FTP_BUFSIZE (4096) read, so the LF arrives in the next read. + fputs($s, "150 File status okay; about to open data connection.\r\n"); + fputs($fs, str_repeat("A", 4095) . "\r\n" . str_repeat("B", 10)); + fputs($s, "226 Closing data Connection.\r\n"); + break; default: fputs($s, "550 {$matches[1]}: No such file or directory \r\n");