When setuid root no longer means setuid root (Forced Privileges)
By DarrenMoffat on Jan 04, 2011
Starting with Solaris 10 (though similar functionality had been available for more than 15 years in Trusted Solaris variants) it was no longer necessary to be "root" to perform privileged operations via the Solaris kernel. Instead individual fine grained privileges are checked by the kernel (and in some cases the X server as well). A number of core setuid root binaries on Solaris were updated to drop all privileges they knew they wouldn't need, but they were still setuid root and thus ran as root for a very short period of time, others that weren't explicitly modified continued to run as root for their lifetime.
The setuid bit on a file system binary (or script) has traditionally indicated to the Solaris kernel that on exec(2) the uid of the process should be switched to that of the owner. The most common use of this is to make programs run with "root" privileges.
Trusted Solaris 8 and older had the concept of forced privileges on binaries. This was achieved by storing with the binary the list of privileges it should always be run with, this required a modified version of the UFS file system and as a result customised backup tools as well (the pax/tar/cpio/ufsdump provided with Solaris knew about these (and other) UFS extensions but not all 3rd party backup software did. This functionality wasn't carried forward into Solaris 10, which in addition to UFS also has ZFS (which is now the default and only choice for the root file system in Solaris 11 Express).
Solaris 11 Express includes a new implementation of the forced privileges mechanism. Unlike the version from Trusted Solaris 8 and earlier it is file system agnostic and doesn't store additional information on disk with the binary.
When the kernel is processing an exec(2) it now treats setuid to root differently (setuid to any other uid or setgid is as in Solaris 10). The kernel now looks for an entry in the Forced Privilege RBAC profile in exec_attr(4) to determine which privileges the program should run with. Instead of having it start with uid root and all privileges, which was the previous behaviour, it will now run with the current uid and just those additional privileges the Forced Privilege RBAC execution profile assigned to that pathname.
We can easily see this difference in behaviour by showing how this works for /usr/sbin/ping.
First lets look with truss at what it looked like prior to the introduction of Forced Privilege:
# truss -f -texec -p 20549 3735: execve("/usr/sbin/ping", 0xFED50640, 0x080477EC) argc = 2 3735: \*\*\* SUID: ruid/euid/suid = 101 / 0 / 0 \*\*\*
As we can see the real uid is 101, the effective and saved are 0 (root). Now lets compare that to the new behaviour:
# truss -f -texec -p 20549 3590: execve("/usr/sbin/ping", 0xFED50638, 0x080477EC) argc = 2 3590: \*\*\* FPRIV: P/E: net_icmpaccess,sys_ip_config \*\*\*
This time the ruid/euid/suid doesn't change (all three stay at 101 in this example) instead we set the permitted (P) and effective (E) privilege sets to be the set obtained for /usr/sbin/ping in the "Forced Privileges" RBAC profile (net_icmpaccess,sys_ip_config in this case).
This means that at no time was ping running with uid 0 nor did it ever have all privileges, so we no longer require the code for ping to do its own privilege management.
If there is't an entry in the "Forced Privileges" RBAC profile for the program being exec'd then the traditional behaviour of setting euid to 0 remains.
As a result Solaris 11 Express currently has 15 fewer "true" setuid binaries - note that the list of binaries in the "Forced Privileges" RBAC profile may change over time including in software updates.
So setuid root no longer means setuid root in all cases.