Skip to end of metadata
Go to start of metadata

Using the vfork function introduces many portability and security issues. There are many cases in which undefined and implementation-specific behavior can occur, leading to a denial-of-service vulnerability.

According to the vfork man page,

The vfork() function has the same effect as fork(), except that the behavior is undefined if the process created by vfork() either modifies any data other than a variable of type pid_t used to store the return value from vfork(), or returns from the function in which vfork() was called, or calls any other function before successfully calling _exit() or one of the exec family of functions.

Furthermore, older versions of Linux are vulnerable to a race condition, occurring when a privileged process calls vfork(), and then the child process lowers its privileges and calls execve(). The child process is executed with the unprivileged user's UID before it calls execve().

Because of the implementation of the vfork() function, the parent process is suspended while the child process executes. If a user sends a signal to the child process, delaying its execution, the parent process (which is privileged) is also blocked. This means that an unprivileged process can cause a privileged process to halt, which is a privilege inversion resulting in a denial of service.

This code example shows how difficult it is to use vfork() without triggering undefined behavior. The lowering of privileges in this case requires a call to setuid(), the behavior of which is undefined because it occurs between the vfork() and the execve().

pid_t pid = vfork();
if (pid == 0) /* child */ {
  setuid(unprivileged_user);  /* undefined behavior */
  /*
   * Window of vulnerability to privilege inversion on
   * older versions of Linux
   */
  if (execve(filename, NULL, NULL) == -1) {
    /* Handle error */
  }

  /*
   * In normal operations, execve() might fail; if it does,
   * vfork() behavior is undefined.
   */
  _exit(1);  /* in case execve() fails */
}

Use fork() instead of vfork() in all circumstances.

Noncompliant Code Example

This noncompliant code example calls vfork() and then execve(). As previously discussed, a vfork()/execve() pair contains an inherent race window on some implementations.

char *filename = /* something */;

pid_t pid = vfork();
 if (pid == 0 )  /* child */ {
   if (execve(filename, NULL, NULL) == -1) {
     /* Handle error */
   }
   _exit(1);  /* in case execve() fails */
}

Compliant Solution

This compliant solution replaces the call to vfork() with a call to fork(), which does not contain a race condition, and eliminates the denial-of-service vulnerability:

char *filename = /* something */;

pid_t pid = fork();
if (pid == 0) /* child */ {
  if (execve(filename, NULL, NULL) == -1) {
    /* Handle error */
  }
  _exit(1);  /* in case execve() fails */
}

Risk Assessment

Using the vfork function can result in a denial-of-service vulnerability.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

POS33-C

low

probable

low

P6

L2

Automated Detection

Tool

Version

Checker

Description

Axivion Bauhaus Suite

6.9.0

CertC-POS33
CodeSonar
5.2p0
BADFUNC.VFORKUse of vfork
Coverity
2017.07

DONTCALL

Implemented
LDRA tool suite
9.7.1

44 S

Fully implemented

Compass/ROSE




Parasoft C/C++test
10.4.2

CERT_C-POS33-a

Avoid using the 'vfork()' function

Polyspace Bug Finder

R2019b

CERT C: Rule POS33-CChecks for use of obsolete standard function (rule fully covered)


PRQA QA-C
9.7
5023Fully implemented
SonarQube C/C++ Plugin
3.11
ObsoletePosixFunction

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

CWE 2.11CWE-242, Use of inherently dangerous function2017-07-05: CERT: Rule subset of CWE
MITRE CWECWE-676Prior to 2018-01-12:CERT:Relationship Unspecified
CWE 3.1CWE-676, Use of Potentially Dangerous Function2018-10-18:CERT:None

CERT-CWE Mapping Notes

Key here for mapping notes

CWE-242 and POS33-C

CWE-242 = Union( POS33-C, list) where list =

  • Use of dangerous functions besides vfork

CWE-676 and POS33-C

INTERSECTION(CWE-676, POS33-C) = 

Bibliography



3 Comments

  1. Should have a "POSIX" label.

    vfork was an invention of BSD, with the claim that fork was too expensive for simple spawn (fork/exec), since it copied the whole program only to overlay it with a new one, and the BSD developers claimed that the obvious solution on the VAX, use "copy-on-write" tags in the page table, didn't work right.  (However, AT&T UNIX didn't have a problem with that.)  Modern systems have figured out how to fork efficiently.

    Plan 9 from Bell Labs has a more general rfork facility which provides considerable control over how much of the environment is shared and how much is copied.

    1. This recommendation is already in the POSIX section, so the POSIX label is implicit.

  2. The problem that remains with fork (even with COW) is that the page tables need to be cloned and this can result in 100's of MB's of memory being required to duplicate a process with a large VM layout. Therefore fork continues to suffer for large processes from page table copying latency and duplicate page table memory load. You can avoid all of this with vfork, but you have to be very careful how you use it.