php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #74357 lchown fails to change ownership of symlink with ZTS
Submitted: 2017-04-01 04:07 UTC Modified: 2025-11-30 14:35 UTC
From: msaladna at apisnetworks dot com Assigned: bukka (profile)
Status: Verified Package: Filesystem function related
PHP Version: 7.4.23 OS: CentOS 7.3
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: msaladna at apisnetworks dot com
New email:
PHP Version: OS:

 

 [2017-04-01 04:07 UTC] msaladna at apisnetworks dot com
Description:
------------
Compiling PHP with ZTS affects performance of lchown and lchgrp. 

PHP CLI compiled without ZTS:

./configure --disable-all

ZTS is above + '--enable-maintainer-zts'

Test script:
---------------
<?php
   echo "TS: ", PHP_ZTS, "\n";
   is_link("foo") && unlink("foo");
   symlink("/tmp", "foo");
   lchown("foo", 99);
   var_dump(lstat("foo")['uid']);
?>

Expected result:
----------------
TS: 1
int(99)

Actual result:
--------------
TS: 1
int(0)

Patches

Pull Requests

Pull requests:

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-09-09 12:20 UTC] cmb@php.net
-Status: Open +Status: Feedback -Assigned To: +Assigned To: cmb
 [2021-09-09 12:20 UTC] cmb@php.net
What is the output of the following script:

<?php
   echo "TS: ", PHP_ZTS, "\n";
   is_link("foo") && unlink("foo");
   symlink("/tmp", "foo");
   lchown("foo", 99);
   clearstatcache(true);
   var_dump(lstat("foo")['uid']);
?>

And please check with any of the actively supported PHP
versions[1]?

[1] <https://www.php.net/supported-versions.php>
 [2021-09-09 16:26 UTC] msaladna at apisnetworks dot com
-Status: Feedback +Status: Assigned -PHP Version: 7.1.3 +PHP Version: 7.4.23
 [2021-09-09 16:26 UTC] msaladna at apisnetworks dot com
Problem still persists. Sample code amended for clarity:

<?php
   echo "TS: ", PHP_ZTS, " ", PHP_VERSION, "\n";
   echo posix_getuid(), ":", posix_geteuid(), "\n";
   is_link("foo") && unlink("foo");
   symlink("/tmp", "foo");
   lchown("foo", 99);
   clearstatcache(true);
   var_dump(lstat("foo")['uid']);
?>

This is on a CentOS 7/PHP 7.4 machine.

TS: 1 7.4.23
0:0
int(0)

And on a Rocky Linux (CentOS 8)/PHP 8 machine:

TS: 1 8.0.6
0:0
int(0)
 [2021-09-10 14:40 UTC] cmb@php.net
-Status: Assigned +Status: Verified -Assigned To: cmb +Assigned To:
 [2021-09-10 14:40 UTC] cmb@php.net
I can confirm the reported behavior (PHP-7.4).  It is apparently
not related to the realpath cache, since disabling the cache in
the first place doesn't make a difference.
 [2024-07-13 09:12 UTC] ewverb654 at gmail dot com
This information is really helpful for who really needs this. I hope you will many more write post like this. (https://github.com)(https://www.mgh-patientgateway.com)
 [2025-11-30 14:35 UTC] bukka@php.net
-Assigned To: +Assigned To: bukka
 [2025-11-30 14:35 UTC] bukka@php.net
So this is really a bug in ZTS. The reason is that virtual_chown calls:

if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {

The problem is that this calls tsrm_realpath_r which resolves the simlink in 

		if (save && S_ISLNK(st.st_mode)) {
			if (++(*ll) > LINK_MAX || (j = (size_t)php_sys_readlink(tmp, path, MAXPATHLEN)) == (size_t)-1) {
				/* too many links or broken symlinks */
				free_alloca(tmp, use_heap);
				return (size_t)-1;
			}
			path[j] = 0;
			if (IS_ABSOLUTE_PATH(path, j)) {
				j = tsrm_realpath_r(path, 1, j, ll, t, use_realpath, is_dir, &directory);
				if (j == (size_t)-1) {
					free_alloca(tmp, use_heap);
					return (size_t)-1;
				}
			} else {
				if (i + j >= MAXPATHLEN-1) {
					free_alloca(tmp, use_heap);
					return (size_t)-1; /* buffer overflow */
				}
				memmove(path+i, path, j+1);
				memcpy(path, tmp, i-1);
				path[i-1] = DEFAULT_SLASH;
				j = tsrm_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir, &directory);
				if (j == (size_t)-1) {
					free_alloca(tmp, use_heap);
					return (size_t)-1;
				}
			}
			if (link_is_dir) {
				*link_is_dir = directory;
			}
		}


Currently it seems that it does not get resolved only for CWD_EXPAND but not 100% sure if it's safe. Anyway I created https://github.com/php/php-src/pull/20626 so it will be checked there.
 [2025-11-30 14:35 UTC] bukka@php.net
The following pull request has been associated:

Patch Name: Fix bug #74357: lchown fails to change ownership of symlink with ZTS
On GitHub:  https://github.com/php/php-src/pull/20626
Patch:      https://github.com/php/php-src/pull/20626.patch
 
PHP Copyright © 2001-2025 The PHP Group
All rights reserved.
Last updated: Mon Dec 15 00:00:01 2025 UTC