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 chowning or chgrping 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).