Issue
I cannot manage to set the SGID bit from PHP.
I have this directory:
4 drwxrwsr-x 12 www-data mygroup 4096 Oct 7 16:05 mydir
Note the SGID bit. So, if I simply create a directory into it from the shell with mkdir test
, I get
4 drwxr-sr-x 2 myuser mygroup 4096 Oct 7 16:22 test
Note that the SGID bit is inherited. But I would want it group writable (which my umask 22
does not allow) so I can simply chmod 02775 test
and I am perfectly happy:
4 drwxrwsr-x 2 myuser mygroup 4096 Oct 7 16:22 test
Now I would like to do the same from a PHP script. Naturally, I would expect this to work:
mkdir("/mydir/test2");
chmod("/mydir/test2", 02775);
But it does not and I get this instead (the SGID is reset):
4 drwxrwxr-x 2 www-data mygroup 4096 Oct 7 16:30 test2
Here are a couple of other useful experiments:
mkdir("/mydir/test3");
mkdir("/mydir/test4");
passthru("chmod 02775 '/mydir/test4'");
mkdir("/mydir/test5");
passthru("chmod g+w '/mydir/test5'");
The results are
4 drwxr-sr-x 2 www-data mygroup 4096 Oct 7 16:39 test3
4 drwxrwxr-x 2 www-data mygroup 4096 Oct 7 16:39 test4
4 drwxrwxr-x 2 www-data mygroup 4096 Oct 7 17:06 test5
Interestingly, mkdir()
alone has preserved the SGID, but chmod()
resets it, even through passthru()
.
I know that the PHP manual says for chmod
that the command expects only three octal digits, but I read also this stackoverflow question and it looks like the manual contains obsolete information and others can affect the SGID. Besides, it should not affect the passthru()
versions, should it? The same stackoverflow question mentions something about chmod()
needing to be "the last to be called" after chown()
and chgrp()
, but I am not using any of those.
What am I doing wrong?
Solution
In the end, the only way I could find to obtain the permissions I need is to profit of the correct behaviour of mkdir()
and avoid calling chmod()
which appears to reset the SGID bit no matter what. The only way I can think of doing this is to change the umask with umask()
:
$myumask = umask(2);
mkdir("/mydir/test6");
umask($myumask);
This appears to work fine:
4 drwxrwsr-x 2 www-data mygroup 4096 Oct 9 14:22 test6
This leaves me with the issue raised in the note of https://www.php.net/manual/en/function.umask.php: that all threads of a multithreaded webserver share the same umask, obviously leading to undesired and unpredictable behaviour. Luckily, in my case, I could ascertain that all directory creations are done in a monothreaded context (essentially, on the first test run of a script) and thus I feel safe. Hence, this is a useful workaround but not a general solution.
Answered By - Paolo Answer Checked By - Mary Flores (WPSolving Volunteer)