You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 25 Next »

The POSIX setuid() function has complex semantics and platform-specific behavior [[Open Group 04]]

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 phrase "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 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 unsuccessful. For example, the range of Linux Kernel versions (2.2.0-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.

Non-Compliant Code Example

This non-compliant code example compiles cleanly on most POSIX systems, however no explicit checks have been made to ensure that privilege relinquishment has succeeded. This may be dangerous depending on the sequence of the preceding privilege changes.

/*  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 could regain elevated priveleges! */

If the program is run with as a setuid root program, the state of the UID's over time might be:

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 03]]. If the setuid() call succeeds after (supposedly) dropping privileges permanently , privileges were not dropped as intended.

/*  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 can not regain elevated privileges */

Compliant Solution

A better solution is to ensure that proper privileges exist before attempting to carry out a permanent drop.

/* 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 can not regain elevated 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

Related Vulnerabilities

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

References

[[CWE - 273]] Failure to Check Whether Privileges Were Dropped Successfully
[[Dowd 06]] Chapter 9, "UNIX I: Privileges and Files"
[[Open Group 04]] setuid(), getuid(), seteuid()
[[Wheeler 03]] Section 7.4, "Minimize Privileges"


      50. POSIX (POS)       CERT C Secure Coding Standard

  • No labels