From 3e44f49d96b13468cf1a0e8b83be9ea1dab6c9cb Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:58:19 +0200 Subject: [PATCH 1/2] Fix GH-13204: glob() fails if square bracket is in current directory The problem is not limited to square brackets, but to every meta character. The solution is to override the glob functions for handling paths with the VCWD ones in PHP. If that is not available, use the old but limited workaround. --- ext/standard/dir.c | 52 ++++++++++++++++--- ext/standard/tests/file/gh13204.phpt | 12 +++++ .../tests/file/gh13204[brackets]/empty.txt | 0 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 ext/standard/tests/file/gh13204.phpt create mode 100644 ext/standard/tests/file/gh13204[brackets]/empty.txt diff --git a/ext/standard/dir.c b/ext/standard/dir.c index 7c1f8efe68875..faf116189b328 100644 --- a/ext/standard/dir.c +++ b/ext/standard/dir.c @@ -400,13 +400,34 @@ PHP_FUNCTION(getcwd) /* }}} */ /* {{{ Find pathnames matching a pattern */ +#if defined(ZTS) && defined(PHP_GLOB_ALTDIRFUNC) +static void *php_glob_opendir_wrapper(const char *path) +{ + return VCWD_OPENDIR(path); +} + +static void php_glob_closedir_wrapper(void *dir) +{ + (void) closedir(dir); +} + +static int php_glob_lstat_wrapper(const char *buf, zend_stat_t *sb) +{ + return VCWD_LSTAT(buf, sb); +} + +static int php_glob_stat_wrapper(const char *buf, zend_stat_t *sb) +{ + return VCWD_STAT(buf, sb); +} +#endif + PHP_FUNCTION(glob) { size_t cwd_skip = 0; -#ifdef ZTS +#if defined(ZTS) && !defined(PHP_GLOB_ALTDIRFUNC) char cwd[MAXPATHLEN]; char work_pattern[MAXPATHLEN]; - char *result; #endif char *pattern = NULL; size_t pattern_len; @@ -433,9 +454,28 @@ PHP_FUNCTION(glob) RETURN_FALSE; } + memset(&globbuf, 0, sizeof(globbuf)); + + int passed_glob_flags = flags & PHP_GLOB_FLAGMASK; + #ifdef ZTS if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) { - result = VCWD_GETCWD(cwd, MAXPATHLEN); + /* System glob uses the current work directory which is not thread safe. + * The first fix is to override the functions used to open/read/... paths + * with the VCWD ones used in PHP. + * If that functionality is unavailable for whatever reason, fall back + * to prepending the current working directory to the passed path. + * However, that comes with limitations regarding meta characters + * that is not solvable in general (GH-13204). */ +#ifdef PHP_GLOB_ALTDIRFUNC + globbuf.gl_opendir = php_glob_opendir_wrapper; + globbuf.gl_readdir = (struct dirent *(*)(void *)) readdir; + globbuf.gl_closedir = php_glob_closedir_wrapper; + globbuf.gl_lstat = php_glob_lstat_wrapper; + globbuf.gl_stat = php_glob_stat_wrapper; + passed_glob_flags |= PHP_GLOB_ALTDIRFUNC; +#else + char *result = VCWD_GETCWD(cwd, MAXPATHLEN); if (!result) { cwd[0] = '\0'; } @@ -448,13 +488,11 @@ PHP_FUNCTION(glob) snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern); pattern = work_pattern; +#endif } #endif - - memset(&globbuf, 0, sizeof(globbuf)); - globbuf.gl_offs = 0; - if (0 != (ret = php_glob(pattern, flags & PHP_GLOB_FLAGMASK, NULL, &globbuf))) { + if (0 != (ret = php_glob(pattern, passed_glob_flags, NULL, &globbuf))) { #ifdef PHP_GLOB_NOMATCH if (PHP_GLOB_NOMATCH == ret) { /* Some glob implementation simply return no data if no matches diff --git a/ext/standard/tests/file/gh13204.phpt b/ext/standard/tests/file/gh13204.phpt new file mode 100644 index 0000000000000..1af66539195a4 --- /dev/null +++ b/ext/standard/tests/file/gh13204.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-13204 (glob() fails if square bracket is in current directory) +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + string(11) "./empty.txt" +} diff --git a/ext/standard/tests/file/gh13204[brackets]/empty.txt b/ext/standard/tests/file/gh13204[brackets]/empty.txt new file mode 100644 index 0000000000000..e69de29bb2d1d From bacfeedf90d9d87c9a65b95e649af712e4eaa643 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 3 Mar 2026 20:24:12 +0100 Subject: [PATCH 2/2] review+NEWS --- NEWS | 2 ++ ext/standard/dir.c | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index cf36b1bad7c7f..75332b89a1ef2 100644 --- a/NEWS +++ b/NEWS @@ -123,6 +123,8 @@ PHP NEWS defaulted to 0. (Jorg Sowa) . Fixed bug GH-21058 (error_log() crashes with message_type 3 and null destination). (David Carlier) + . Fixed bug GH-13204 (glob() fails if square bracket is in current directory). + (ndossche) - Streams: . Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream diff --git a/ext/standard/dir.c b/ext/standard/dir.c index faf116189b328..730ef6154907e 100644 --- a/ext/standard/dir.c +++ b/ext/standard/dir.c @@ -467,28 +467,28 @@ PHP_FUNCTION(glob) * to prepending the current working directory to the passed path. * However, that comes with limitations regarding meta characters * that is not solvable in general (GH-13204). */ -#ifdef PHP_GLOB_ALTDIRFUNC +# ifdef PHP_GLOB_ALTDIRFUNC globbuf.gl_opendir = php_glob_opendir_wrapper; globbuf.gl_readdir = (struct dirent *(*)(void *)) readdir; globbuf.gl_closedir = php_glob_closedir_wrapper; globbuf.gl_lstat = php_glob_lstat_wrapper; globbuf.gl_stat = php_glob_stat_wrapper; passed_glob_flags |= PHP_GLOB_ALTDIRFUNC; -#else +# else char *result = VCWD_GETCWD(cwd, MAXPATHLEN); if (!result) { cwd[0] = '\0'; } -#ifdef PHP_WIN32 +# ifdef PHP_WIN32 if (IS_SLASH(*pattern)) { cwd[2] = '\0'; } -#endif +# endif cwd_skip = strlen(cwd)+1; snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern); pattern = work_pattern; -#endif +# endif } #endif