Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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. Oftentimes, the best solution is to use the stream object by value semantics instead of via dynamic memory allocation, ensuring compliance with MEM31-CPP. Properly deallocate dynamically allocated resources. However, that is still insufficient for situations where destructors are not automatically called.

Page properties
hiddentrue

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

Standard FILE objects and their underlying representation (file descriptors on POSIX ® platforms or handles elsewhere) are a finite resource that must be carefully managed. The maximum number of files that an implementation guarantees may be open simultaneously is bounded by the FOPEN_MAX macro defined in <stdio.h>. The value of the macro is guaranteed to be at least 8. Thus, portable programs must either avoid keeping more than FOPEN_MAX files at the same time or be prepared for functions such as fopen() to fail due to resource exhaustion.

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. In addition, keeping files open longer than necessary increases the risk that data written into in-memory file buffers will not be flushed in the event of abnormal program termination, for example as a result of an uncaught exception (see also ERR30-CPP. Try to recover gracefully from unexpected errors). To prevent file descriptor leaks and to guarantee that any buffered data will be flushed into permanent storage, files must be closed when they are no longer needed.

...

.

Noncompliant Code Example

In this noncompliant code example derived from a vulnerability in OpenBSD's chpass program [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, a std::fstream object f 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. Thus, the underlying std::basic_filebuf<T> object maintained by the object is not properly closed, and the program has no way of determining if an error occurs while flushing or closing the file.

Code Block
bgColor#FFcccc
langcpp
FILE*#include f;<exception>
const char *editor;
char *file_name;

/* Initialize file_name */

f = fopen(file_name, "r");
if (f == NULL#include <fstream>

void f(const std::string &N) {
  /* 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.

Code Block
bgColor#ccccff
langcpp
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 */
}
/* 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::fstream f(N);
  if (!f.is_open()) {
    // Handle error
    return;
  }
  // ...
  std::terminate();
}

Compliant Solution

In this compliant solution, std::fstream::close() is called prior to calling std::terminate(), 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
bgColor#ccccff
langcpp
int#include flags;<exception>
char#include *editor;
char *file_name;

/* Initialize file_name */

int fd = open(file_name, O_RDONLY);
if (fd == -1<fstream>

void f(const std::string &N) {
  /* Handle error */
}

flags = fcntl(fd, F_GETFDstd::fstream f(N);
  if (flags == -1(!f.is_open()) {
    //* Handle error */
}

if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
  /* Handle error */
}

/* return;
  }
  // ... */

editor = getenv("EDITOR"f.close();
if (editor == NULL) {
  /* Handle getenv() error */
}
if (systemf.fail(editor) == -1) {
  /* Handle error */
}

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]. 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
bgColor#ccccff
langcpp
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 */ }
  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

FIO42-CPP

mediumMedium

unlikelyUnlikely

mediumMedium

P4

L3

Automated Detection

Coverity Code Advisor version 7.5 can detect violations of this rule.

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.

...

Tool

Version

Checker

Description

    

Related Vulnerabilities

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

Other Languages

...

Related Guidelines

...

...

 

Bibliography

[

...

ISO/IEC 14882-2014]27.9.1, "File Streams"