Many common operating systems such as Windows and UNIX support file links including hard links, symbolic (soft) links, and virtual drives. Hard links can be created in UNIX with the ln
command, or in Windows operating systems by calling the CreateHardLink()
function. 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. Virtual drives can also be created in Windows using the subst
command.
File links can create security issues for programs that fail to consider the possibility that the file being opened may actually be a link to a different file. This is especially dangerous when the vulnerable program is running with elevated privileges.
Frequently, there is no need to check for the existence of symbolic links as this problem can be solved using other techniques. When opening an existing file, for example, the simplest solution is often to drop privileges to the privileges of the user. This solution permits the use of links while preventing access to files for which the user of the application is not privileged.
When creating new files, it may be possible to use functions which only create a new file where a file does not already exist. This prevents the application from overwriting an existing file during file creation (see [[FIO03-A. Do not make assumptions about fopen() and file creation]]).
In rare cases, it is necessary to check for the existence of symbolic or hard links to ensure that a program is reading from an intended file and not a different file in another directory. In these cases, avoid creating a race condition when checking for the existence of symbolic links.
Non-Compliant Code Example
This non-compliant code example opens the file specified by the string "/home/rcs/.conf"
for read/write exclusive access, and then writes user supplied data to the file.
if ((fd = open("/home/rcs/.conf", O_EXCL|O_RDWR, 0600)) == -1) { /* handle error */ } write(fd, userbuf, userlen);
If the process is running with elevated privileges, an attacker can exploit this code, for example, by creating a link from .conf
to the /etc/passwd
authentication file. The attacker can then overwrite data stored in the password file to create a new root account with no password. As a result, this attack can be used to gain root privileges on a vulnerable system.
Non-Compliant Code Example
The only function available on POSIX systems to collect information about a symbolic link rather than its target is the lstat()
function. This non-compliant code example uses the lstat()
function to collection information about the file, and then checks the st_mode
field to determine if the file is a symbolic link.
struct stat lstat_info; int fd; if (lstat("some_file", &lstat_info) == -1) { /* handle error */ } if (!S_ISLNK(lstat_info.st_mode)) { if ((fd = open("some_file", O_RDWR, 0600)) == -1) { /* handle error */ } } write(fd, userbuf, userlen);
Compliant Solution (Linux 2.1.26+, FreeBSD, Solaris 10, POSIX.1-2008)
Some systems provide the O_NOFOLLOW flag to help mitigate this problem. The flag will be required by the forthcoming POSIX.1-2008 standard, and so will become more portable over time. If the flag is set and the supplied pathname
is a symbolic link, then the open will fail.
int fd; if ((fd = open("some_file", O_RDWR | O_NOFOLLOW, 0600)) == -1) { /* handle error */ } write(fd, userbuf, userlen);
Compliant Solution
This compliant solution properly checks for the existence of a link and eliminates the race condition.
struct stat lstat_info, fstat_info; int fd; if (lstat("some_file", &lstat_info) == -1) { /* handle error */ } if ((fd = open("some_file", O_RDWR, 0600)) == -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) { write(fd, userbuf, userlen); }
This 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-A. Identify files using multiple file attributes]]).
Risk Assessment
Failing to check for the existence of links can result in a critical system file being overwritten, leading to a data integrity violation.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
POS01-A |
2 (medium) |
3 (likely) |
1 (high) |
P6 |
L2 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[ISO/IEC 9899-1999]] Section 7.19.7.2, "The fgets function"
[[Seacord 05]] Chapter 7, "File I/O"
POS00-A. Avoid race conditions with multiple threads 50. POSIX (POS) POS02-A. Be careful using errno with multiple threads