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.
Noncompliant Code Example
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.)
Compliant Solution (POSIX.1-2008 or newer)
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 */
}
Compliant Solution (POSIX.1-2001 or older)
This compliant solution eliminates the race condition by
- Calling
lstat()on the file name. - Calling
open()to open the file. - Calling
fstat()on the file descriptor returned byopen(). - Comparing the file information returned by the calls to
lstat()andfstat()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.)
Risk Assessment
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 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CWE 2.11 | CWE-363, Race condition enabling link following | 2017-07-07: CERT: Exact |
Bibliography
| [ 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" |
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
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
This rule has no individual options.