From a11da6fc15ab7a3c2c6c7e8b451ae024fff0402d Mon Sep 17 00:00:00 2001 From: zaaarf Date: Tue, 12 May 2026 13:55:21 +0200 Subject: [PATCH 1/5] ext/standard: Fix symlink trying to resolve dangling links --- ext/standard/link.c | 10 +++++ ext/standard/tests/gh21992t.phpt | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 ext/standard/tests/gh21992t.phpt diff --git a/ext/standard/link.c b/ext/standard/link.c index df2831b87846..8e94f016bea0 100644 --- a/ext/standard/link.c +++ b/ext/standard/link.c @@ -131,11 +131,21 @@ PHP_FUNCTION(symlink) char dirname[MAXPATHLEN]; size_t len; + zend_stat_t v_lstat = {0}; + zend_stat_t v_stat = {0}; + ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_PATH(topath, topath_len) Z_PARAM_PATH(frompath, frompath_len) ZEND_PARSE_PARAMETERS_END(); + // dangling link should be treated as existing file + ret = VCWD_LSTAT(frompath, &v_lstat); + if (!ret && S_ISLNK(v_lstat.st_mode) && VCWD_STAT(frompath, &v_stat)) { + php_error_docref(NULL, E_WARNING, "File exists in %s", frompath); + RETURN_FALSE; + } + if (!expand_filepath(frompath, source_p)) { php_error_docref(NULL, E_WARNING, "No such file or directory"); RETURN_FALSE; diff --git a/ext/standard/tests/gh21992t.phpt b/ext/standard/tests/gh21992t.phpt new file mode 100644 index 000000000000..1fa08336b531 --- /dev/null +++ b/ext/standard/tests/gh21992t.phpt @@ -0,0 +1,76 @@ +--TEST-- +GH-21992: symlink() must not resolve the final dangling link component +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECTF-- +bool(true) +string(%d) "%s/gh21992/target_dir/real.txt" + +Warning: symlink(): File exists in %s on line %d +bool(false) +bool(true) +string(%d) "%s/gh21992/target_dir/real.txt" +bool(false) +bool(true) +string(%d) "%s/gh21992/target_dir/ghost.txt" + +Warning: symlink(): File exists in %s on line %d +bool(false) +bool(true) +string(%d) "%s/gh21992/target_dir/ghost.txt" +bool(false) From 7bd502d93b4a05c41f7819d6c71eed9ce94dbf65 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 13 May 2026 02:21:24 +0200 Subject: [PATCH 2/5] try to fix test --- ext/standard/tests/gh21992t.phpt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/standard/tests/gh21992t.phpt b/ext/standard/tests/gh21992t.phpt index 1fa08336b531..6a883decea72 100644 --- a/ext/standard/tests/gh21992t.phpt +++ b/ext/standard/tests/gh21992t.phpt @@ -3,7 +3,8 @@ GH-21992: symlink() must not resolve the final dangling link component --SKIPIF-- --FILE-- From 5b7b6d84132813b4a4cb01610fad2bec626ef8fb Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 13 May 2026 02:32:25 +0200 Subject: [PATCH 3/5] actually fix windows test --- ext/standard/tests/gh21992t.phpt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/standard/tests/gh21992t.phpt b/ext/standard/tests/gh21992t.phpt index 6a883decea72..9c65bc16f8b8 100644 --- a/ext/standard/tests/gh21992t.phpt +++ b/ext/standard/tests/gh21992t.phpt @@ -60,18 +60,18 @@ $realFile = $targetDir . '/real.txt'; ?> --EXPECTF-- bool(true) -string(%d) "%s/gh21992/target_dir/real.txt" +string(%d) "%s%egh21992%etarget_dir%ereal.txt" Warning: symlink(): File exists in %s on line %d bool(false) bool(true) -string(%d) "%s/gh21992/target_dir/real.txt" +string(%d) "%s%egh21992%etarget_dir%ereal.txt" bool(false) bool(true) -string(%d) "%s/gh21992/target_dir/ghost.txt" +string(%d) "%s%egh21992%etarget_dir%eghost.txt" Warning: symlink(): File exists in %s on line %d bool(false) bool(true) -string(%d) "%s/gh21992/target_dir/ghost.txt" +string(%d) "%s%egh21992%etarget_dir%eghost.txt" bool(false) From cd6ddb8f396a1078451102a082914177b26b3c8c Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 13 May 2026 03:04:59 +0200 Subject: [PATCH 4/5] better warning message --- ext/standard/link.c | 2 +- ext/standard/tests/gh21992t.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/link.c b/ext/standard/link.c index 8e94f016bea0..948cf618c02e 100644 --- a/ext/standard/link.c +++ b/ext/standard/link.c @@ -142,7 +142,7 @@ PHP_FUNCTION(symlink) // dangling link should be treated as existing file ret = VCWD_LSTAT(frompath, &v_lstat); if (!ret && S_ISLNK(v_lstat.st_mode) && VCWD_STAT(frompath, &v_stat)) { - php_error_docref(NULL, E_WARNING, "File exists in %s", frompath); + php_error_docref(NULL, E_WARNING, "Dangling symlink at %s", frompath); RETURN_FALSE; } diff --git a/ext/standard/tests/gh21992t.phpt b/ext/standard/tests/gh21992t.phpt index 9c65bc16f8b8..15e6fedab46d 100644 --- a/ext/standard/tests/gh21992t.phpt +++ b/ext/standard/tests/gh21992t.phpt @@ -70,7 +70,7 @@ bool(false) bool(true) string(%d) "%s%egh21992%etarget_dir%eghost.txt" -Warning: symlink(): File exists in %s on line %d +Warning: symlink(): Dangling symlink at %s on line %d bool(false) bool(true) string(%d) "%s%egh21992%etarget_dir%eghost.txt" From b8b1b39c80c491df8db4fbf28ca4b274f84efb7f Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 13 May 2026 03:06:15 +0200 Subject: [PATCH 5/5] fix test for windows --- ext/standard/tests/gh21992t.phpt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/standard/tests/gh21992t.phpt b/ext/standard/tests/gh21992t.phpt index 15e6fedab46d..60af0d2bcb1d 100644 --- a/ext/standard/tests/gh21992t.phpt +++ b/ext/standard/tests/gh21992t.phpt @@ -3,8 +3,7 @@ GH-21992: symlink() must not resolve the final dangling link component --SKIPIF-- --FILE--