Howto prevent chgrp from clearing “setuid bit”?
If you want to implement your chgrp -R nobody /whatever
while retaining the setuid bit you can use these two find
commands
find /whatever ! -type l -perm -04000 -exec chgrp nobody {} + \
-exec chmod u+s {} +
find /whatever ! -type l ! -perm -04000 -exec chgrp nobody {} +
The find ... -perm 04000
option picks up files with the setuid bit set. The first command then applies the chgrp
and then a chmod
to reinstate the setuid bit that has been knocked off. The second one applies chgrp
to all files that do not have a setuid bit.
In any case, you don't want to call chgrp
or chmod
on symlinks as that would affect their targets instead, hence the ! -type l
.
Clearing SUID and SGID bits on chgrp
(or chown
) is perfectly reasonable. It is a safety measure in order to prevent security problems. For SGID (on executables, I presume) means run this program with effective group of the group owner.
If you change the group owner, then in terms of security and access control this is something entirely different, i.e. instead of running with effective group uvw
the program now runs with effective group xyz
.
Thus you have to restore the SUID or SGID bit explicitly on ownership change.
Addendum: On the claim that chgrp (or chown) should only clear SGID (or SUID, resp.)
By chown
ing or chgrp
ing you change the security setting for an executable, and this is sufficient reason to clear any privilege elevating attributes. The power of Unix comes from conceptual simplicity, and Unix security is already quite tricky. To this end removing SUID and SGID on any ownership change is simply a safety net - after all, in the history of Unix/Linux there were quite some vulnerabilities due to misguided SUID or SGID settings.
So there is no deeper reason why Unix behaves this way, it is just a conservative design decision.
The clearing of the setuid
, setgid
bit (at least on Linux) on non-directories is done by the kernel upon the chown()
system call done by chgrp
, not by chgrp
itself. So the only way is to restore it afterwards.
It also clears the security capabilities.
So, on GNU Linux:
chown_preserve_sec() (
newowner=${1?}; shift
for file do
perms=$(stat -Lc %a -- "$file") || continue
cap=$(getfattr -m '^security\.capability$' --dump -- "$file") || continue
chown -- "$newowner" "$file" || continue
[ -z "$cap" ] || printf '%s\n' "$cap" | setfattr --restore=-
chmod -- "$perms" "$file"
done
)
And run (as root
):
chown_preseve_sec :newgroup file1 file2...
to change the group while attempting to preserve the permissions.
Recursively, you could do:
# save permissions (and ACLs). Remove the "# owner" and "# group" lines
# to prevent them being restored!
perms=$(getfacl -RPn . | grep -vE '^# (owner|group): ')
# save capabilities
cap=$(getfattr -Rhm '^security\.capability$' --dump .)
chgrp -RP nobody .
# restore permissions, ACLs and capabilities
printf '%s\n' "$perms" | setfacl --restore=-
[ -z "$cap" ] || printf '%s\n' "$cap" | setfattr -h --restore=-
(that's all assuming nothing is otherwise messing up with the files at the same time).