Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The POSIX setuid() function has complex semantics and platform-specific behavior [Open 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 to uid.

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 to uid; 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 EUID is not equal to 0, the real user ID (RUID), or the saved set-user ID (SSUID).

Because of this complex behavior, desired privilege drops sometimes may failThere may be cases where the desired privilege drops are unsuccessful. For example, the range of Linux Kernel versions (2.2.0-20–2.2.15) was 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.

...

Noncompliant Code Example

The following This noncompliant code snippet example compiles cleanly on most POSIX based systems, however but no explicit checks have been checks are made to vouchsafe ensure that privilege relinquishment will be carried out successfullyhas succeeded. This may be dangerous depending on the sequence of the preceding privilege changes.

Code Block
bgColor#ffcccc
langc


/*  Code intended to run with elevated privileges */

/* Temporarily drop privileges */

setuidif (seteuid(getuid()); != 0) {
  /* Handle error */
}

/*  Code intended to run with lower privileges  */

if (need_more_privileges) {

Compliant Solution

  /* 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 UIDs might look like the following:

Description

Code

EUID

RUID

SSUID

Program startup


0

User

0

Temporary drop

seteuid(getuid())

User

User

0

Restore

seteuid(0)

0

User

0

Permanent drop

setuid(getuid())

User

User

User

Restore (attacker)

setuid(0) (fails)

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

seteuid(getuid())

User

User

0

Restore

seteuid(0)

User

User

0

Permanent drop

setuid(getuid())

User

User

0

Restore (attacker)

setuid(0)

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 2003]. If the setuid() call succeeds after (supposedly) dropping privileges permanently, then the privileges were not dropped as Wiki MarkupThis compliant solution was implemented in sendmail, a popular mail transfer agent \[[Wheeler 03|AA. C References#Wheeler 03]\]. It checks whether superuser privileges were dropped successfully. Note that if the {{setuid()}} call succeeds after the {{setuid(getuid())}} operation, privileges were not dropped as was originally intended.

Code Block
bgColor#ccccff
langc


/*  Code intended to run with elevated privileges   */


setuid/* Temporarily drop privileges */
if (seteuid(getuid());

if  (setuid != 0) {
  /* Handle error */
}

/* Code intended to run with lower privileges */

if (need_more_privileges) {
  /* Restore Privileges */
  if (seteuid(0) =!= -10) {
    /* Handle error */
  }

  /* Code intended to run with elevated privileges */
}

/* ... */

/* Permanently drop privileges */
if (setuid returns -1 on error (getuid()) != 0) {
  /* Handle error */
}

if (setuid(0) != -1) {
   /* Setuid failedPrivileges 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 perform a permanent drop:The function shown below correctly follows the principle of least privilege, however, due to inconsistencies and implementation defined behavior of certain functions (such as setuid()) across various Operating Systems, the final result may be unexpected. Here, when privileges are given up temporarily for the final time, the effective User ID of the process is set to the real user ID. Unexpectedly, the call to setuid(realuid) that follows, does not affect the saved set-user-ID since effective UID is no longer 0 (Except on FreeBSD and NetBSD). If a seteuid(0) gets executed maliciously after this statement, root privileges would be recovered from the saved set-user-ID.

Code Block
bgColor#ffcccc#ccccff
langc
/* Store the privileged ID for later verification */


void doSomething(void)
{
  uid_t realuidprivid = getuidgeteuid();

/* Code intended to run with elevated privileges   */

/* Temporarily seteuid(realuid);      /* Give up privileges temporarily  */
  
  seteuid(0);            /* Regain superuser privileges  */

  /* Carry out the privileged task */ 

  seteuid(realuid);  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) {
    /* Give up privileges temporarily Handle error */
  }
}

/* Permanently drop privileges */
if (setuid(getuid()) != 0) {
  /* Handle error setuid(realuid);       /* Failed attempt at giving up privileges permanently */
}

Compliant Solution

The following code shows how the effective UID should be obtained and compared against 0 (superuser's EUID) to make sure privileges can be successfully dropped permanently. This constitutes a more portable and safe solution.

...

bgColor#ccccff

...

*/
}

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 files. The getgroups() function returns an array 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 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 supplementary group IDs. However, a setuid-root program can alter its supplementary group IDs and then relinquish root privileges, in which case, it maintains the supplementary group IDs but lacks the privilege necessary to relinquish them. Consequently, it is recommended that a program immediately relinquish supplementary group IDs before relinquishing root privileges.

POS36-C. Observe correct revocation order while relinquishing privileges discusses how to drop supplementary group IDs. To ensure that supplementary group IDs are indeed relinquished, you can use the following eql_sups function:

Code Block
bgColor#ccccff
langc
/* 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);
}

System-Specific Capabilities

Many systems have nonportable privilege capabilities that, if unchecked, can yield privilege escalation 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. They normally shadow the effective user ID and effective group ID, but the setfsuid() and setfsgid() functions allow them to be changed. 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 2002] and [Tsafrir 2008]). Consequently, a prudent program 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

Version

Checker

Description

Astrée
Include Page
Astrée_V
Astrée_V

user_defined

Soundly supported
Axivion Bauhaus Suite

Include Page
Axivion Bauhaus Suite_V
Axivion Bauhaus Suite_V

CertC-POS37
Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

DF4876, DF4877, DF4878


Klocwork
Include Page
Klocwork_V
Klocwork_V

SV.USAGERULES.PERMISSIONS


Parasoft C/C++test

Include Page
Parasoft_V
Parasoft_V

CERT_C-POS37-a
Ensure that privilege relinquishment is successful

Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C: Rule POS37-CChecks for priviledge drop not verified (rule fully covered)

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

References

Wiki Markup
\[[CWE - 273|AA. C References#CWE - 273]\] [Failure to Check Whether Privileges Were Dropped Successfully | http://cwe.mitre.org/data/definitions/273.html]

Wiki Markup
\[[Wheeler 03|AA. C References#Wheeler 03]\] [Section 7.4, "Minimize Privileges"|http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/minimize-privileges.html]

Wiki Markup
\[[Dowd 06|AA. C References#Dowd 06]\] Chapter 9, "Unix I: Privileges and Files"

Related Guidelines

Key here (explains table format and definitions)

Taxonomy

Taxonomy item

Relationship

ISO/IEC TR 24772Privilege Sandbox Issues [XYO]Prior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11CWE-273, Failure to check whether privileges were dropped successfully2017-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"


...

Image Added Image Added Image AddedImage Removed      50. POSIX (POS)       CERT C Secure Coding Standard