File names on many operating systems, including Windows and UNIX, may be used to access special files, which are actually devices. Reserved MS-DOS device names include AUX
, CON
, PRN
, COM1
, and LPT1
. Device files on UNIX systems are used to apply access rights and to direct operations on the files to the appropriate device drivers.
Performing operations on device files that are intended for ordinary character or binary files can result in crashes and denial-of-service attacks. For example, when Windows attempts to interpret the device name as a file resource, it performs an invalid resource access that usually results in a crash \[[Howard 2002|AA. Bibliography#Howard 02]\]. |
Device files in UNIX can be a security risk when an attacker can access them in an unauthorized way. For instance, if attackers can read or write to the {{/dev/kmem}} device, they may be able to alter their priority, UID, or other attributes of their process or simply crash the system. Similarly, access to disk devices, tape devices, network devices, and terminals being used by other processes all can lead to problems \[[Garfinkel 1996|AA. Bibliography#Garfinkel 96]\]. |
On Linux, it is possible to lock certain applications by attempting to open devices rather than files. Consider the following example:
/dev/mouse /dev/console /dev/tty0 /dev/zero |
A Web browser that failed to check for these devices would allow an attacker to create a Web site with image tags such as <IMG src="file:///dev/mouse">
that would lock the user's mouse.
In this noncompliant code example, the user can specify a locked device or a FIFO file name, causing the program to hang on the call to fopen()
.
char *file_name; FILE *file; /* initialize file_name */ if (!fgets(file_name, sizeof(file_name), stdin)) { /* Handle error */ } if ((file = fopen(file_name, "wb")) == NULL) { /* Handle error */ } /* operate on file */ fclose(file); |
POSIX defines the {{O_NONBLOCK}} flag to {{open()}}, which ensures that delayed operations on a file do not hang the program \[[Open Group 2004|AA. Bibliography#Open Group 04]\]. |
When opening a FIFO with
O_RDONLY
orO_WRONLY
set:
- If
O_NONBLOCK
is set, anopen()
for reading-only shall return without delay. Anopen()
for writing-only shall return an error if no process currently has the file open for reading.- If
O_NONBLOCK
is clear, anopen()
for reading-only shall block the calling thread until a thread opens the file for writing. Anopen()
for writing-only shall block the calling thread until a thread opens the file for reading.When opening a block special or character special file that supports non-blocking opens:
- If
O_NONBLOCK
is set, theopen()
function shall return without blocking for the device to be ready or available. Subsequent behavior of the device is device-specific.- If
O_NONBLOCK
is clear, theopen()
function shall block the calling thread until the device is ready or available before returning.Otherwise, the behavior of
O_NONBLOCK
is unspecified.
Once the file is open, programmers can use the POSIX lstat()
and fstat()
functions to obtain information about a named file and the S_ISREG()
macro to determine if the file is a regular file. (See recommendation FIO05-C. Identify files using multiple file attributes.)
Because the behavior of O_NONBLOCK
on subsequent calls to read()
or write()
is unspecified, it is advisable to disable the flag after it has been determined that the file in question is not a special device.
When available (Linux 2.1.126+, FreeBSD, Solaris 10, POSIX.1-2008), the O_NOFOLLOW
should also be used. (See recommendation POS01-C. Check for the existence of links when dealing with files.) When O_NOFOLLOW
is not available, symbolic link checks should use the method from rule POS35-C. Avoid race conditions while checking for the existence of a symbolic link.
#ifdef O_NOFOLLOW #define OPEN_FLAGS O_NOFOLLOW | O_NONBLOCK #else #define OPEN_FLAGS O_NONBLOCK #endif /* ... */ struct stat orig_st; struct stat open_st; int fd; int flags; char *file_name; /* initialize file_name */ if (!fgets(file_name, sizeof(file_name), stdin)) { /* Handle error */ } if ((lstat(file_name, &orig_st) != 0) || (!S_ISREG(orig_st.st_mode))) { /* Handle error */ } /* A TOCTOU race condition exists here, see below */ fd = open(file_name, OPEN_FLAGS | O_WRONLY); if (fd == -1) { /* Handle error */ } if (fstat(fd, &open_st) != 0) { /* Handle error */ } if ((orig_st.st_mode != open_st.st_mode) || (orig_st.st_ino != open_st.st_ino) || (orig_st.st_dev != open_st.st_dev)) { /* file was tampered with */ } /* Optional: drop the O_NONBLOCK now that we are sure * this is a good file */ if ((flags = fcntl(fd, F_GETFL)) == -1) { /* Handle error */ } if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) != 0) { /* Handle error */ } /* Operate on file */ close(fd); |
This code does contains an intractable TOCTOU race condition under which an attacker can alter the file referenced by file_name
following the call to lstat()
but before the call to open()
. The switch will be discovered after the file is opened, but opening the file cannot be prevented in the case where this action itself causes undesired behavior.
Essentially, an attacker can switch out a file for one of the file types shown in the table with the specified effect.
Table - File types and effect
Type |
Note on effect |
---|---|
another regular file |
The |
FIFO |
Either |
symbolic link |
|
special device |
Usually the |
This TOCTOU race condition can be prevented if the effected files are maintained in a secure directory. (See recommendation FIO15-C. Ensure that file operations are performed in a secure directory.)
The GetFileType()
function can be used to determine if the file is a disk file.
HANDLE hFile = CreateFile( pFullPathName, 0, 0, NULL, OPEN_EXISTING, 0, NULL ); if (hFile == INVALID_HANDLE_VALUE) { /* Handle error */ } else { if (GetFileType(hFile) != FILE_TYPE_DISK) { /* Handle error */ } /* operate on file */ } |
Allowing operations to be performed on devices that are only appropriate for files can result in denial-of-service attacks or more serious exploits depending on the platform.
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
FIO32-C |
medium |
unlikely |
medium |
P4 |
L3 |
Tool |
Version |
Checker |
Description |
||
---|---|---|---|---|---|
|
|
|
|
||
|
|
|
|
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
CERT C++ Secure Coding Standard: FIO32-CPP. Do not perform operations on devices that are only appropriate for files
The CERT Oracle Secure Coding Standard for Java: FIO00-J. Do not operate on files in shared directories
MITRE CWE: CWE-67, "Failure to Handle Windows Device Names"
ISO/IEC 9899:1999 Section 7.19.4, "Operations on Files"
\[[Garfinkel 1996|AA. Bibliography#Garfinkel 96]\] Section 5.6, "Device Files" \[[Howard 2002|AA. Bibliography#Howard 02]\] Chapter 11, "Canonical Representation Issues" \[[Open Group 2004|AA. Bibliography#Open Group 04]\] [{{open()}}|http://www.opengroup.org/onlinepubs/009695399/functions/open.html] |
FIO31-C. Do not open a file that is already open 09. Input Output (FIO) FIO33-C. Detect and handle input output errors resulting in undefined behavior