CertC-POS35ΒΆ

Avoid race conditions while checking for the existence of a symbolic link

Required inputs: IR, StaticSemanticAnalysis

Many common operating systems, such as Windows and UNIX, support symbolic (soft) links. Symbolic links can be created in UNIX using the ln -s command or in Windows by using directory junctions in NTFS or the Linkd.exe (Win 2K resource kit) or "junction" freeware.

If not properly performed, checking for the existence of symbolic links can lead to race conditions.

This rule is a specific instance of rule  FIO45-C. Avoid TOCTOU race conditions while accessing files.

The POSIX lstat() function collects information about a symbolic link rather than its target. This noncompliant code example uses the lstat() function to collect information about the file, checks the st_mode field to determine if the file is a symbolic link, and then opens the file if it is not a symbolic link:

char *filename = /* file name */;
char *userbuf = /* user data */;
unsigned int userlen = /* length of userbuf string */;

struct stat lstat_info;
int fd;
/* ... */
if (lstat(filename, &lstat_info) == -1) {
  /* Handle error */
}

if (!S_ISLNK(lstat_info.st_mode)) {
   fd = open(filename, O_RDWR);
   if (fd == -1) {
       /* Handle error */
   }
}
if (write(fd, userbuf, userlen) < userlen) {
  /* Handle error */
}

This code contains a time-of-check, time-of-use (TOCTOU) race condition between the call to lstat() and the subsequent call to open() because both functions operate on a file name that can be manipulated asynchronously to the execution of the program. (See FIO01-C. Be careful using functions that use file names for identification.)

This compliant solution eliminates the race condition by using O_NOFOLLOW to cause open() to fail if passed a symbolic link, avoiding the TOCTOU by not having a separate "check" and "use":

char *filename = /* file name */;
char *userbuf = /* user data */;
unsigned int userlen = /* length of userbuf string */;

int fd = open(filename, O_RDWR|O_NOFOLLOW);
if (fd == -1) {
  /* Handle error */
}
if (write(fd, userbuf, userlen) < userlen) {
  /* Handle error */
}

This compliant solution eliminates the race condition by

  1. Calling lstat() on the file name.
  2. Calling open() to open the file.
  3. Calling fstat() on the file descriptor returned by open().
  4. Comparing the file information returned by the calls to lstat() and fstat() to ensure that the files are the same.
char *filename = /* file name */;
char *userbuf = /* user data */;
unsigned int userlen = /* length of userbuf string */;

struct stat lstat_info;
struct stat fstat_info;
int fd;
/* ... */
if (lstat(filename, &lstat_info) == -1) {
  /* handle error */
}

fd = open(filename, O_RDWR);
if (fd == -1) {
  /* handle error */
}

if (fstat(fd, &fstat_info) == -1) {
  /* handle error */
}

if (lstat_info.st_mode == fstat_info.st_mode &&
    lstat_info.st_ino == fstat_info.st_ino  &&
    lstat_info.st_dev == fstat_info.st_dev) {
  if (write(fd, userbuf, userlen) < userlen) {
    /* Handle Error */
  }
}

This code eliminates the TOCTOU condition because fstat() is applied to file descriptors, not file names, so the file passed to fstat() must be identical to the file that was opened. The lstat() function does not follow symbolic links, but open() does. Comparing modes using the st_mode field is sufficient to check for a symbolic link.

Comparing i-nodes, using the st_ino fields, and devices, using the st_dev fields, ensures that the file passed to lstat() is the same as the file passed to fstat(). (See FIO05-C. Identify files using multiple file attributes.)

TOCTOU race condition vulnerabilities can be exploited to gain elevated privileges.

Rule Severity Likelihood Remediation Cost Priority Level
POS35-C high likely medium P18 L1
Taxonomy Taxonomy item Relationship
CWE 2.11 CWE-363, Race condition enabling link following 2017-07-07: CERT: Exact
[ Dowd 2006] Chapter 9, "UNIX 1: Privileges and Files"
[ ISO/IEC 9899:2011] Section 7.21, "Input/output <stdio.h>"
[ Open Group 2004] lstat()
fstat()
open()
[ Seacord 2013] Chapter 8, "File I/O"
Excerpt from SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition) and SEI CERT C Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/posix-pos/pos35-c], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

Possible Messages

Key

Text

Severity

Disabled

no_fstat_after_lstat_open

A fstat() should follow an open() that is preceded by a lstat() call.

None

False

Options