A call to the std::basic_filebuf<T>::open()
function must be matched with a call to std::basic_filebuf<T>::close()
before the lifetime of the last pointer that stores the return value of the call has ended or before normal program termination, whichever occurs first.
Note that std::basic_ifstream<T>
, std::basic_ofstream<T>
, and std::basic_fstream<T>
all maintain an internal reference to a std::basic_filebuf<T>
object on which open()
and close()
are called as needed. Properly managing an object of one of these types (by not leaking the object) is sufficient to ensure compliance with this rule. Often, the best solution is to use the stream object by value semantics instead of via dynamic memory allocation, ensuring compliance with MEM51-CPP. Properly deallocate dynamically allocated resources. However, that is still insufficient for situations in which destructors are not automatically called.
Page properties | ||
---|---|---|
| ||
We may want an overarching rule that covers any situation where destructors are not automatically called, since there can be all sorts of nasty things that happen in those situations. |
Noncompliant Code Example
In this noncompliant code example, a std::fstream
object file
is constructed. The constructor for std::fstream
calls std::basic_filebuf<T>::open()
, and the default std::terminate_handler
called by std::terminate()
is std::abort()
, which does not call destructors. Consequently, the underlying std::basic_filebuf<T>
object maintained by the object is not properly closed.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <exception>
#include <fstream>
#include <string>
void f(const std::string &fileName) {
std::fstream file(fileName);
if (!file.is_open()) {
// Handle error
return;
}
// ...
std::terminate();
} |
This noncompliant code example and the subsequent compliant solutions are assumed to eventually call std::terminate()
in accordance with the ERR50-CPP-EX1 exception described in ERR50-CPP. Do not abruptly terminate the program. Indicating the nature of the problem to the operator is elided for brevity
Failing to close files when they are no longer needed may allow attackers to exhaust, and possibly manipulate, system resources. This phenomenon is typically referred to as file descriptor leakage, although file pointers may also be used as an attack vector. To prevent file descriptor leaks, files should be closed when they are no longer needed.
Be careful not to close the standard streams (especially stdout
): doing so will send an EOF
to any application on the other side of a pipe, possibly causing it to take actions that shouldn't have occurred until the first application terminates.
Noncompliant Code Example
Wiki Markup |
---|
In this noncompliant code example derived from a [vulnerability|BB. Definitions#vulnerability] in OpenBSD's {{chpass}} program \[[NAI 98|AA. C++ References#NAI 98]\], a file containing sensitive data is opened for reading. The program then retrieves the registered editor from the {{EDITOR}} environment variable and executes it using the {{system()}} command. If, the {{system()}} command is implemented in a way that spawns a child process, then the child process inherits the file descriptors opened by its parent. As a result, the child process, which in this example is the program specified by the {{EDITOR}} environment variable, will be able to access the contents of the potentially sensitive file called {{file_name}}. |
Code Block | ||
---|---|---|
| ||
FILE* f;
const char *editor;
char *file_name;
/* Initialize file_name */
f = fopen(file_name, "r");
if (f == NULL) {
/* Handle fopen() error */
}
/* ... */
editor = getenv("EDITOR");
if (editor == NULL) {
/* Handle getenv() error */
}
if (system(editor) == -1) {
/* Handle error */
}
|
On UNIX-based systems, child processes are typically spawned using a form of fork()
and exec()
, and the child process always receives copies of its parent's file descriptors. Under Microsoft Windows, the CreateProcess()
function is typically used to start a child process. In Windows, file-handle inheritance is determined on a per-file bases. Additionally, the CreateProcess()
function itself provides a mechanism to limit file-handle inheritance. As a result, the child process spawned by CreateProcess()
may not receive copies of the parent process's open file handles.
Compliant Solution
In this compliant solution, file_name
is closed before launching the editor std::fstream::close()
is called before std::terminate()
is called, ensuring that the file resources are properly closed.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <exception> #include <fstream> #include <string> void f(const std::string &fileName) { std::fstream file(fileName); if (!file.is_open()) { // Handle error return; } // ... file.close(); if (file.fail() FILE* f; const char *editor; char *file_name; /* Initialize file_name */ f = fopen(file_name, "r"); if (f == NULL) { /* Handle fopen() error */ } /* ... */ fclose(f); f = NULL; editor = getenv("EDITOR"); if (editor == NULL) { /*/ Handle getenv()error error */ } /* Sanitize environment before calling system()! */ if (system(editor) == -1) { /* Handle Error */ } |
Several security issues remain in this example. Compliance with recommendations, such as STR02-CPP. Sanitize data passed to complex subsystems and FIO02-CPP. Canonicalize path names originating from untrusted sources is necessary to prevent exploitation. However, these recommendations do not address the specific issue of file descriptor leakage addressed here.
Compliant Solution (POSIX)
std::terminate();
}
|
Compliant Solution
In this compliant solution, the stream is implicitly closed through RAII before std::terminate()
is called, ensuring that the file resources are properly closedSometimes it is not practical for a program to close all active file descriptors before issuing a system call such as system()
or exec()
. An alternative on POSIX systems is to use the FD_CLOEXEC
flag, or O_CLOEXEC
when available, to set the close-on-exec flag for the file descriptor.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <exception> #include <fstream> #include <string> void f(const std::string &fileName int flags; char *editor; char *file_name; /* Initialize file_name */ int fd = open(file_name, O_RDONLY); if (fd == -1) { /*{ Handle error */ } flags = fcntl(fd, F_GETFD std::fstream file(fileName); if (flags == -1) { /* Handle error */ } if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1!file.is_open()) { /* Handle error */ } /* ... */ editor = getenv("EDITOR"); if (editor == NULL) { /* Handle getenv() error */ } if (system(editor) == -1) { /* Handle error */ } |
Wiki Markup |
---|
Some systems (such as those with Linux kernel versions greater than or equal to 2.6.23) have an {{O_CLOEXEC}} flag that provides the close-on-exec function directly in {{open()}}. This flag is required by POSIX.1-2008 \[[Austin Group 08|AA. C++ References#Austin Group 08]\]. In multithreaded programs, this flag should be used if possible because it prevents a timing hole between {{open()}} and {{fcntl()}} when using {{FD_CLOEXEC}}, during which another thread can create a child process while the file descriptor does not have close-on-exec set. |
Code Block | ||
---|---|---|
| ||
char *editor; char *file_name; /* Initialize file_name */ int fd = open(file_name, O_RDONLY | O_CLOEXEC); if (fd == -1) { /* Handle error */ } /* ... */ editor = getenv("EDITOR"); if (editor == NULL) { /* Handle getenv() error */ } if (system(editor) == -1) { /* Handle error */ } return; } } // file is closed properly here when it is destroyed std::terminate(); } |
Risk Assessment
Failing to properly close files may allow unintended access to, or exhaustion of, system resourcesan attacker to exhaust system resources and can increase the risk that data written into in-memory file buffers will not be flushed in the event of abnormal program termination.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
FIO51-CPP |
Medium |
Unlikely |
Medium | P4 | L3 |
Automated Detection
Tool |
---|
The LDRA tool suite Version 7.6.0 can detect violations of this recommendation.
Fortify SCA Version 5.0 with CERT C Rule Pack can detect violations of this recommendation.
Klocwork Version 8.0.4.16 can detect violations of this rule with the RH.LEAK checker.
...
Version | Checker | Description | |||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| ALLOC.LEAK | Leak | ||||||
Helix QAC |
| DF4786, DF4787, DF4788 | |||||||
Klocwork |
| RH.LEAK | |||||||
Parasoft C/C++test |
| CERT_CPP-FIO51-a | Ensure resources are freed | ||||||
Parasoft Insure++ | Runtime detection | ||||||||
Polyspace Bug Finder |
| CERT C++: FIO51-CPP | Checks for resource leak (rule partially covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the the CERT website.
...
Related Guidelines
This rule appears in the C Secure Coding Standard as supplements FIO42-C. Ensure Close files are properly closed when they are no longer needed.
References
Wiki Markup |
---|
\[[Austin Group 08|AA. C++ References#Austin Group 08]\]
\[[Dowd 06|AA. C++ References#Dowd 06]\] Chapter 10, "UNIX Processes" (File Descriptor Leaks 582-587)
\[[MITRE 07|AA. C++ References#MITRE 07]\] [CWE ID 404|http://cwe.mitre.org/data/definitions/404.html], "Improper Resource Shutdown or Release," and [CWE ID 403|http://cwe.mitre.org/data/definitions/403.html], "UNIX File Descriptor Leak"
\[[MSDN|AA. C++ References#MSDN]\] [Inheritance|http://msdn.microsoft.com/en-us/library/ms683463.aspx] (Windows)
\[[NAI 98|AA. C++ References#NAI 98]\] |
Bibliography
[ISO/IEC 14882-2014] | Subclause 27.9.1, "File Streams" |
...
FIO41-CPP. Do not call getc() or putc() with stream arguments that have side effects 09. Input Output (FIO) FIO43-CPP. Do not create temporary files in shared directories