The POSIX {{ Wiki Markup setuid()
}} function has complex semantics and platform-specific behavior \[ [Open Group 04|AA. C References#Open Group 04]\]Group 2004].
If the process has appropriate privileges,
setuid()
shall set the real user ID, effective user ID, and the saved set-user-ID of the calling process touid
.If the process does not have appropriate privileges, but
uid
is equal to the real user ID or the saved set-user-ID,setuid()
shall set the effective user ID touid
; the real user ID and saved set-user-ID shall remain unchanged.
The meaning of "appropriate privileges" varies from platform to platform. For example, on Solaris, appropriate privileges for setuid()
means that the PRIV_PROC_SETID
privilege is in the effective privilege set of the process. On BSD, it means that the effective user ID (EUID) is zero (that is, the process is running as root) or that uid=geteuid()
. On Linux, it means that the process has CAP_SETUID
capability and that setuid(geteuid())
will fail if the effective user ID (EUID) the EUID is not equal to 0, the real user ID (RUID), or the saved set-user - ID (SSUID).
Because of this complex behavior, there may be cases where the desired privilege drops are unsuccessfulsometimes may fail. For example, the range of Linux Kernel versions (2.2.0-20–2.2.15) is vulnerable to an insufficient privilege attack wherein setuid(getuid())
did not drop privileges as expected when the capability bits were set to zero. As a precautionary measure, subtle behavior and error conditions for the targeted implementation must be carefully noted.
...
This noncompliant code example compiles cleanly on most POSIX systems, but no explicit checks have been checks are made to ensure that privilege relinquishment has succeeded. This may be dangerous depending on the sequence of the preceding privilege changes.
Code Block | ||||
---|---|---|---|---|
| ||||
/* Code intended to run with elevated privileges */ /* Temporarily drop privileges */ if (seteuid(getuid()) != 0) { /* Handle error */ } /* Code intended to run with lower privileges */ if (need_more_privileges) { /* Restore privileges */ if (seteuid(0) != 0) { /* Handle error */ } /* Code intended to run with elevated privileges */ } /* ... */ /* Permanently drop privileges */ if (setuid(getuid()) != 0) { /* Handle error */ } /* * Code intended to run with lower privileges, * but if privilege relinquishment failed, * attacker can regain elevated privileges! */ |
If the program is run as a setuid root program, over time, the state of the UID
s over time might be as followsUIDs might look like the following:
Description | Code | EUID | RUID | SSUID |
---|
Program startup |
0 |
User | 0 |
Temporary drop |
|
User |
User | 0 |
Restore |
| 0 |
User | 0 |
Permanent drop |
|
User |
User |
User |
Restore (attacker) |
|
User |
User |
User |
If the program fails to restore privileges, it will be unable to permanently drop them later:
Description | Code | EUID | RUID | SSUID |
---|---|---|---|---|
program startup |
0 |
User | 0 |
Temporary drop |
|
User |
User | 0 |
Restore |
|
User |
User | 0 |
Permanent drop |
|
User |
User | 0 |
Restore (attacker) |
| 0 | 0 | 0 |
Compliant Solution
...
This compliant solution was implemented in sendmail, a popular mail transfer agent, to determine if superuser privileges were successfully dropped \[ [Wheeler 03|AA. C References#Wheeler 03]\2003]. If the {{setuid()
}} call succeeds after (supposedly) dropping privileges permanently, then the privileges were not dropped as intended.
Code Block | ||||
---|---|---|---|---|
| ||||
/* Code intended to run with elevated privileges */ /* Temporarily drop privileges */ if (seteuid(getuid()) != 0) { /* Handle error */ } /* Code intended to run with lower privileges */ if (need_more_privileges) { /* Restore Privileges */ if (seteuid(0) != 0) { /* Handle error */ } /* Code intended to run with elevated privileges */ } /* ... */ /* Permanently drop privileges */ if (setuid(getuid()) != 0) { /* Handle error */ } if (setuid(0) != -1) { /* Privileges can be restored, handle error */ } /* * Code intended to run with lower privileges; * attacker cannot regain elevated privileges */ |
Compliant Solution
A better solution is to ensure that proper privileges exist before attempting to carry out to perform a permanent drop.:
Code Block | ||||
---|---|---|---|---|
| ||||
/* Store the privileged ID for later verification */ uid_t privid = geteuid(); /* Code intended to run with elevated privileges */ /* Temporarily drop privileges */ if (seteuid(getuid()) != 0) { /* Handle error */ } /* Code intended to run with lower privileges */ if (need_more_privileges) { /* Restore Privileges */ if (seteuid(privid) != 0) { /* Handle error */ } /* Code intended to run with elevated privileges */ } /* ... */ /* Restore privileges if needed */ if (geteuid() != privid) { if (seteuid(privid) != 0) { /* Handle error */ } } /* Permanently drop privileges */ if (setuid(getuid()) != 0) { /* Handle error */ } if (setuid(0) != -1) { /* Privileges can be restored, handle error */ } /* * Code intended to run with lower privileges; * attacker cannot regain elevated privileges */ |
...
Supplementary Group
...
IDs
A process may have a number of supplementary group IDs, in addition to its effective group ID, and the supplementary groups can allow privileged access to filesAny user, including root, may belong to multiple groups; these are handled by supplemental group privileges. The getgroups()
function returns an array of that contains the supplementary group IDs , and can also contain the effective group ID. The setgroups()
function can set the supplementary group IDs and can set the array to an arbitrary array, but usually only if the user has root privileges. While also set the effective group ID on some systems. Using setgroups()
usually requires privileges. Although POSIX defines the getgroups()
function, it does not define setgroups()
.
Under normal circumstances, setuid()
and related calls do not alter the supplemental supplementary group privilegesIDs. However, a setuid-root program can grant itself supplemental group ids alter its supplementary group IDs and then relinquish root privileges, in which icase case, it maintains the supplemental supplementary group ids, IDs but lacks the privilege necessary to relinquish them. Consequently, it is recommended that a program immediately relinquish supplemental supplementary group privileges immediately IDs before relinquishing root privileges.
POS37POS36-C. Ensure that privilege relinquishment is successfulObserve correct revocation order while relinquishing privileges discusses how to drop supplemental supplementary group privilegesIDs. To ensure that supplemental supplementary group privileges IDs are indeed relinquished, you can use the following eql_sups
function:
Code Block | ||||
---|---|---|---|---|
| ||||
/* Returns nonzero if the two group lists are equivalent (taking into
account that the lists may differ wrt the egid */
int eql_sups(const int cursups_size, const gid_t* const cursups_list,
const int targetsups_size, const gid_t* const targetsups_list) {
int i;
int j;
const int n = targetsups_size;
const int diff = cursups_size - targetsups_size;
const gid_t egid = getegid();
if (diff > 1 || diff < 0 ) {
return 0;
}
for (i=0, j=0; i < n; i++, j++) {
if (cursups_list[j] != targetsups_list[i]) {
if (cursups_list[j] == egid) {
i--; /* skipping j */
} else {
return 0;
}
}
}
/* If reached here, we're sure i==targetsups_size. Now, either
j==cursups_size (skipped the egid or it wasn't there), or we didn't
get to the egid yet because it's the last entry in cursups */
return j == cursups_size ||
(j+1 == cursups_size && cursups_list[j] == egid);
}
|
...
Many systems have nonportable privilege capabilities that, if unchecked, can yield privilege escalation vulnerabitilites.
...
vulnerabilities. The following section describes one such capability.
File System Access Privileges (Linux)
...
Processes on Linux have two additional values called {{fsuid
}} and {{fsgid
}}. These values indicate the privileges used when accessing files on the file system. These values They normally shadow the effective user ID and effective group ID, but the {{setfsuid()
}} and {{setfsgid()
}} functions allow them to be changed. Since Because changes to the {{euid
}} and {{egid
}} normally also apply to {{fsuid
}} and {{fsgid
}}, a program relinquishing root privileges need not be concerned with setting {{fsuid
}} or {{fsgid
}} to safe values. However, there has been at least one kernel bug that violated this invariant (\[[Chen 02|AA. C References#Chen 02]\] and \[[Tsafrir 08|AA. C References#Tsafrir 08]\Chen 2002] and [Tsafrir 2008]). Consequently, a prudent program will check that {{fsuid}} and {{fsgid}} have harmless values after relinquishing checks that fsuid
and fsgid
have harmless values after relinquishing privileges.
Risk Assessment
If privilege relinquishment conditions are left unchecked, any flaw in the program may lead to unintended system compromise corresponding to the more privileged user or group account.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
POS37-C | high | probable | low | P18 | L1 |
Automated Detection
Tool |
---|
Klocwork Version 8.0.4.16 can detect violations of this rule with the SV.FIU.PERMISSIONS and SV.USAGERULES.PERMISSIONS checkers.
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[Chen 02|AA. C References#Chen 02]\] "Setuid Demystified"
\[[Dowd 06|AA. C References#Dowd 06]\] Chapter 9, "Unix I: Privileges and Files"
\[[ISO/IEC PDTR 24772|AA. C References#ISO/IEC PDTR 24772]\] "XYO Privilege Sandbox Issues"
\[[MITRE 07|AA. C References#MITRE 07]\] [CWE ID 250|http://cwe.mitre.org/data/definitions/250.html], "Execution with Unnecessary Privileges," [CWE ID 273|http://cwe.mitre.org/data/definitions/273.html], "Failure to Check Whether Privileges Were Dropped Successfully"
\[[Open Group 04|AA. C References#Open Group 04]\] [{{setuid()}}|http://www.opengroup.org/onlinepubs/009695399/functions/setuid.html], [{{getuid()}}|http://www.opengroup.org/onlinepubs/009695399/functions/getuid.html], [{{seteuid()}}|http://www.opengroup.org/onlinepubs/009695399/functions/seteuid.html]
\[[Tsafrir 08|AA. C References#Tsafrir 08]\] "The Murky Issue of Changing Process Identity: Revising 'Setuid Demystified'"
\[[Wheeler 03|AA. C References#Wheeler 03]\] [Section 7.4, "Minimize Privileges"|http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/minimize-privileges.html] |
Version | Checker | Description | |||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| user_defined | Soundly supported | ||||||
Axivion Bauhaus Suite |
| CertC-POS37 | |||||||
Helix QAC |
| DF4876, DF4877, DF4878 | |||||||
Klocwork |
| SV.USAGERULES.PERMISSIONS | |||||||
Parasoft C/C++test |
| CERT_C-POS37-a | Ensure that privilege relinquishment is successful | ||||||
| CERT C: Rule POS37-C | Checks for priviledge drop not verified (rule fully covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
ISO/IEC TR 24772 | Privilege Sandbox Issues [XYO] | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-273, Failure to check whether privileges were dropped successfully | 2017-07-07: CERT: Exact |
Bibliography
[Chen 2002] | "Setuid Demystified" |
[Dowd 2006] | Chapter 9, "Unix I: Privileges and Files" |
[Open Group 2004] | setuid() getuid() seteuid() |
[Tsafrir 2008] | "The Murky Issue of Changing Process Identity: Revising 'Setuid Demystified'" |
[Wheeler 2003] | Section 7.4, "Minimize Privileges" |
...