You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 41 Next »

The rename() function has the following prototype:

int rename(char const *old_file, char const *new_file);

If the file referenced by new_file exists prior to calling rename(), the behavior is implementation-defined. On POSIX systems, the file is removed. On Windows systems, the rename() fails.

This creates issues when trying to write portable code, or when trying to implement alternative behavior.

New File Removed

Occasionally, a programmer might want to implement a behavior other than the implementation-defined behavior for rename() on that platform. If the desired behavior is to ensure that any file referenced by new_file is removed, Windows programmers must write additional code.

Non-Compliant Code Example (Windows)

If the intent of the programmer is to remove the file referenced by new_file if it exists prior to calling rename(), this code example is non-compliant on Windows platforms because rename() will fail.

char const *old_file = /* ... */;
char const *new_file = /* ... */;
if (rename(old_file, new_file) != 0) {
  /* Handle Error */
}

Compliant Solution (Windows)

On Windows systems, it is necessary to explicitly remove the file referenced by new_file before calling rename(), if you want the file to be overwritten and the rename() operation to succeed.

char const *old_file = /* ... */;
char const *new_file = /* ... */;

if (_access_s(new_file,0) == 0) {
  if (remove(new_file) != 0) {
    /* Handle error condition */
  }
}

if (rename(old_file, new_file) != 0) {
  /* Handle error condition */
}

This code contains unavoidable race conditions between the calls to _access_s(), remove() and rename() and can consequently only be safely executed within a secure directory (see FIO17-A. Ensure that file operations are performed in a secure directory).

Compliant Solution (POSIX)

On POSIX systems, if the file referenced by new_file exists prior to calling rename(), the file is automatically removed.

char const *old_file = /* ... */;
char const *new_file = /* ... */;
if (rename(old_file, new_file) != 0) {
  /* Handle error condition */
}

New File Not Removed

If the desired behavior is to ensure that any file referenced by new_file is not removed, POSIX programmers must implement additional safe guards.

Non-Compliant Code Example (POSIX)

This code example is non-compliant because if new_file exists it is removed by rename().

char const *old_file = /* ... */;
char const *new_file = /* ... */;
if (rename(old_file, new_file) != 0) {
  /* Handle Error */
}

Compliant Solution (POSIX)

If the intent of the programmer is to not remove the file referenced by new_file if it exists prior to calling rename(), the POSIX access() function can be used to check for the existence of a file explicitly [[Open Group 04]]. This compliant solution only renames the file referenced by old_file if the file referenced by new_file does not exist.

char const *old_file = /* ... */;
char const *new_file = /* ... */;

if (access(new_file, F_OK) != 0) {
  if (rename(old_file, new_file) != 0) {
    /* Handle error condition */
  }
} 
else {
  /* Handle file-exists condition */
}

This code contains an unavoidable race condition between the call to access() and the call to rename() and can consequently only be safely executed within a secure directory (see FIO17-A. Ensure that file operations are performed in a secure directory).

On file systems where the program does not have sufficient permissions in the directory to view the file, access() may return -1 even when the file exists. In such cases, rename() would also fail because the program lacks adequate permissions to perform the operation.

Compliant Solution (Windows)

On Windows, the rename() function fails if [[MSDN]]:

File or directory specified by newname already exists or could not be created (invalid path)

Consequently, it is unnecessary to explicitly check for the existence of the file referenced by new_file before calling rename().

char const *old_file = /* ... */;
char const *new_file = /* ... */;
if (rename(old_file, new_file) != 0) {
  /* Handle Error */
}

Portable Behavior

A programmer that wants an application to behave the same on any C99 implementation must first determine what behavior to implement.

Compliant Solution (New File Removed)

This compliant solution ensures that any file referenced by new_file is portably removed.

char const *old_file = /* ... */;
char const *new_file = /* ... */;

(void)remove(new_file);

if (rename(old_file, new_file) != 0) {
  /* Handle error condition */
}

This code contains an unavoidable race condition between the call to remove() and the call to rename() and consequently can only be safely executed within a secure directory (see FIO17-A. Ensure that file operations are performed in a secure directory).

The return value of remove() is deliberately not checked, because it is expected to fail in the case where the file does not exist. If the file exists but cannot be removed, the rename() call will also fail and the error will be detected at that point.

Compliant Solution (New File Not Removed)

This compliant solution only renames the file referenced by old_file if the file referenced by new_file does not exist.

char const *old_file = /* ... */;
char const *new_file = /* ... */;

if (!file_exists(new_file)) {
  if (rename(old_file, new_file) != 0) {
    /* Handle error condition */
  }
} 
else {
  /* Handle file-exists condition */
}

This code contains an unavoidable race condition between the call to file_exists() and the call to rename() and can consequently only be safely executed within a secure directory (see FIO17-A. Ensure that file operations are performed in a secure directory).

The file_exists() function is provided by the application, and is not shown here as it must be implemented differently on different platforms. (On POSIX systems it would use access(), on Windows _access_s(), and on other platforms whatever function is available to test file existence.)

Risk Assessment

Calling rename() has implementation-defined behavior when the new file name refers to an existing file. Incorrect use of rename() can result in a file being unexpectedly overwritten or other unexpected behavior.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

FIO10-A

medium

probable

medium

P8

L2

Related Vulnerabilities

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

References

[[ISO/IEC 9899:1999]] Section 7.9.4.2, "The rename function"
[[MSDN]] rename()
[[Open Group 04]] access()


FIO09-A. Be careful with binary data when transferring data across systems      09. Input Output (FIO)       FIO11-A. Take care when specifying the mode parameter of fopen()

  • No labels