Files can often be identified by other attributes in addition to the file name, for example, by comparing file ownership or creation time. Information about a file that has been created and closed can be stored and then used to validate the identity of the file when it is reopened.

Comparing multiple attributes of the file increases the likelihood that the reopened file is the same file that had been previously operated on.

Non-Compliant Code Example (device / i-node)

This example code opens and closes a file multiple times. It relies only on the file name to identify the file.

FILE *fd = fopen(file_name, "w");
if (fd) {
  /* write to file */
  fclose(fd);
}
else {
  /* handle error condition */
}

/* reopen file_name */
fd = fopen(file_name, "r");
if (fd) {
  /* read from file */
  fclose(fd);
}
else {
  /* handle error condition */
}

There is no guarantee that the file opened for reading is the same file that was opened for writing. An attacker could substitute a malicious file between the first fclose() and the second fopen().

Compliant Solution (POSIX) (device / i-node)

In this compliant solution, the file is opened using the open() function. If the file is successfully opened, the fstat() function is used to read information about the file into the stat structure. This information is compared with existing information about the file (stored in the dev and ino variables) to improve identification.

struct stat st;
dev_t dev; /* device */
ino_t ino; /* file serial number */

int fd = open(file_name, O_WRONLY);
if ((fd != -1) && (fstat(fd, &st) != -1)) {
  ino = st.st_ino;
  dev = st.st_dev;
  /* write to file */
  close(fd);
}
else {
  /* handle error condition */
}

/* reopen previously written file */
fd = open(file_name, O_RDONLY);
if ((fd != -1) &&
    (fstat(fd, &st) != -1) &&
    (st.st_ino == ino) &&
    (st.st_dev == dev)
   ) {
  /* read from file */
  close(fd);
}
else {
  /* handle error condition */
}

This enables the program to recognize if an attacker has switched files on the program in between the first close() and the second open(). The program does not recognize if the file has been modified in-place, however.

Alternatively, the same solution could be implemented using the C99 fopen() function to open the file and the POSIX fileno() function to convert the FILE object pointer to a file descriptor.

The structure members st_mode, st_ino, st_dev, st_uid, st_gid, st_atime, st_ctime, and st_mtime should all have meaningful values for all file types on POSIX-compliant systems. The st_ino field contains the file serial number. The st_dev field identifies the device containing the file. The st_ino and st_dev, taken together, uniquely identify the file. The st_dev value is not necessarily consistent across reboots or system crashes, however, so you may not be able to use this field for file identification if there is a possibility of a system crash or reboot before you attempt to reopen a file.

It is necessary to call the {{fstat()}} function on an already opened file, rather than calling {{stat()}} on a file name followed by {{open()}} to ensure the file for which the information is being collected is the same file that is opened.  See \[[FIO01-A. Be careful using functions that use file names for identification]\] for more information on avoiding race conditions resulting from the use of file names for identification.

Non-Compliant Code Example (owner)

This example code runs in a setuid-root program, opening a file for reading. Although it expects the user to own the file, it relies only on the file name to identify the file.

FILE *fd = fopen(file_name, "r");
if (fd) {
  /* read user's file */
  fclose(fd);
}
else {
  /* handle error condition */
}

There is no guarantee that the file opened is the correct file. In fact, the file opened may not even be owned by the user invoking the program. An attacker could use this program to access data they would normally not be privileged to read.

Compliant Solution (POSIX) (owner)

In this compliant solution, the file is opened using the open() function. If the file is successfully opened, the fstat() function is used to read information about the file into the stat structure. This information is compared with existing information about the user (obtained by the getuid(2) and getgid(2) functions.)

struct stat st;
uid_t uid = getuid();
gid_t gid = getgid();

/* open file for reading */
fd = open(file_name, O_RDONLY);
if ((fd != -1) &&
    (fstat(fd, &st) != -1) &&
    (st.st_uid == uid) &&
    (st.st_gid == gid)
   ) {
  /* read from file */
  close(fd);
}
else {
  /* handle error condition */
}

This enables the program to recognize if an attacker is passing data they don't control, by ensuring that the file's owner and group matchers the real userid and group id.

Alternatively, the same solution could be implemented using the C99 fopen() function to open the file and the POSIX fileno() function to convert the FILE object pointer to a file descriptor.

Risk Assessment

Many file-related vulnerabilities are exploited to cause a program to access an unintended file. Proper identification of a file is necessary to prevent exploitation.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

FIO05-A

2 (medium)

2 (probable)

2 (medium)

P8

L2

Related Vulnerabilities

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

References

\[[Drepper 06|AA. C References#Drepper 06]\] Section 2.2.1 "Identification When Opening"
\[[ISO/IEC 9899-1999|AA. C References#ISO/IEC 9899-1999]\] Section 7.19.3, "Files," and Section 7.19.4, "Operations on Files"
\[[ISO/IEC PDTR 24772|AA. C References#ISO/IEC PDTR 24772]\] "EWR Path Traversal"
\[[MITRE 07|AA. C References#MITRE 07]\] [CWE ID 37|http://cwe.mitre.org/data/definitions/37.html], "Path Issue - Slash Absolute Path"; [CWE ID 38|http://cwe.mitre.org/data/definitions/38.html], "Path Issue - Backslash Absolute Path"; [CWE ID 39|http://cwe.mitre.org/data/definitions/39.html], "Path Issue - Drive Letter or Windows Volume"; [CWE ID 62|http://cwe.mitre.org/data/definitions/62.html], "UNIX Hard Link"; [CWE ID 64|http://cwe.mitre.org/data/definitions/64.html], "Windows Shortcut Following (.LNK)"; [CWE ID 65|http://cwe.mitre.org/data/definitions/65.html], "Windows Hard Link"
\[[Open Group 04|AA. C References#Open Group 04]\] "The open function," "The fstat function"
\[[Seacord 05|AA. C References#Seacord 05]\] Chapter 7, "File I/O"


FIO04-A. Detect and handle input and output errors      09. Input Output (FIO)       FIO06-A. Create files with appropriate access permissions