Skip to content

Commit a11da6f

Browse files
committed
ext/standard: Fix symlink trying to resolve dangling links
1 parent 8fa692b commit a11da6f

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

ext/standard/link.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,21 @@ PHP_FUNCTION(symlink)
131131
char dirname[MAXPATHLEN];
132132
size_t len;
133133

134+
zend_stat_t v_lstat = {0};
135+
zend_stat_t v_stat = {0};
136+
134137
ZEND_PARSE_PARAMETERS_START(2, 2)
135138
Z_PARAM_PATH(topath, topath_len)
136139
Z_PARAM_PATH(frompath, frompath_len)
137140
ZEND_PARSE_PARAMETERS_END();
138141

142+
// dangling link should be treated as existing file
143+
ret = VCWD_LSTAT(frompath, &v_lstat);
144+
if (!ret && S_ISLNK(v_lstat.st_mode) && VCWD_STAT(frompath, &v_stat)) {
145+
php_error_docref(NULL, E_WARNING, "File exists in %s", frompath);
146+
RETURN_FALSE;
147+
}
148+
139149
if (!expand_filepath(frompath, source_p)) {
140150
php_error_docref(NULL, E_WARNING, "No such file or directory");
141151
RETURN_FALSE;

ext/standard/tests/gh21992t.phpt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
--TEST--
2+
GH-21992: symlink() must not resolve the final dangling link component
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY === 'Windows') {
6+
die('not for Windows');
7+
}
8+
?>
9+
--FILE--
10+
<?php
11+
$dir = __DIR__ . '/gh21992';
12+
$targetDir = $dir . '/target_dir';
13+
14+
// dangling link to nonexisting $ghost
15+
$deadLink = $dir . '/dead_link.txt';
16+
$ghost = $targetDir . '/ghost.txt';
17+
$newTarget = $dir . '/nothing';
18+
19+
// real link to $realFile
20+
$realLink = $dir . '/real_link.txt';
21+
$realFile = $targetDir . '/real.txt';
22+
$realTarget = $dir . '/something';
23+
24+
mkdir($dir);
25+
mkdir($targetDir);
26+
touch($realFile); // ensure $realFile exists
27+
28+
// verify the real file works fine
29+
var_dump(symlink($realFile, $realLink));
30+
var_dump(readlink($realLink));
31+
var_dump(symlink($realTarget, $realLink));
32+
var_dump(is_link($realLink));
33+
var_dump(readlink($realLink));
34+
var_dump(is_link($realFile));
35+
36+
// verify the ghost file works as intended
37+
var_dump(symlink($ghost, $deadLink));
38+
var_dump(readlink($deadLink));
39+
var_dump(symlink($newTarget, $deadLink));
40+
var_dump(is_link($deadLink));
41+
var_dump(readlink($deadLink));
42+
var_dump(is_link($ghost));
43+
?>
44+
--CLEAN--
45+
<?php
46+
$dir = __DIR__ . '/gh21992';
47+
$targetDir = $dir . '/target_dir';
48+
$deadLink = $dir . '/dead_link.txt';
49+
$ghost = $targetDir . '/ghost.txt';
50+
$realLink = $dir . '/real_link.txt';
51+
$realFile = $targetDir . '/real.txt';
52+
53+
@unlink($ghost);
54+
@unlink($deadLink);
55+
@unlink($realLink);
56+
@unlink($realFile);
57+
@rmdir($targetDir);
58+
@rmdir($dir);
59+
?>
60+
--EXPECTF--
61+
bool(true)
62+
string(%d) "%s/gh21992/target_dir/real.txt"
63+
64+
Warning: symlink(): File exists in %s on line %d
65+
bool(false)
66+
bool(true)
67+
string(%d) "%s/gh21992/target_dir/real.txt"
68+
bool(false)
69+
bool(true)
70+
string(%d) "%s/gh21992/target_dir/ghost.txt"
71+
72+
Warning: symlink(): File exists in %s on line %d
73+
bool(false)
74+
bool(true)
75+
string(%d) "%s/gh21992/target_dir/ghost.txt"
76+
bool(false)

0 commit comments

Comments
 (0)