The rename()
function has the following prototype.:
Code Block |
---|
int rename(const char const *oldsrc_file, const char const *newdest_file); |
If the file referenced by newdest_file
exists prior to calling rename()
, the behavior is implementation-defined. On POSIX systems, the destination file is removed. On Windows systems, the rename()
fails. This creates Consequently, issues arise when trying to write portable code , or when trying to implement alternative behavior.
...
Preserve Existing Destination File
...
Occasionally, a programmer might want to implements 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_the destination file is removed, Windows not erased or overwritten, POSIX programmers must write implement additional codesafeguards.
...
Noncompliant Code Example (
...
POSIX)
This code example is noncompliant because any existing destination file is removed by rename()
: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.
Code Block | ||||
---|---|---|---|---|
| ||||
const char char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (rename(oldsrc_file, newdest_file) != 0) { /* Handle Errorerror */ } |
Compliant Solution (
...
POSIX)
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.If the programmer's intent is not to remove an existing destination file, the POSIX access()
function can be used to check for the existence of a file [IEEE Std 1003.1:2013]. This compliant solution renames the source file only if the destination file does not exist:
Code Block | ||||
---|---|---|---|---|
| ||||
const char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (_access_s(newdest_file,0 F_OK) =!= 0) { if (remove(newrename(src_file, dest_file) != 0) { /* Handle error condition */ } } if (rename(old_file, new_file) != 0) else { /* Handle errorfile-exists condition */ } |
This code contains an unavoidable race conditions condition between the calls call to _access_s()
, remove()
and the call to rename()
and can consequently only be safely executed only when the destination file is located within a secure directory. (see FIO17See FIO15-AC. 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()
will also fail because the program lacks adequate permissions to perform the operation.
In situations where the source file is supposed not to be a directory or symbolic link, an alternative solution is to use link()
to link the source file to the destination file and then use unlink()
(or remove()
) to delete the source file. Because link()
fails if the destination file exists, the need for calling access()
is avoided. However, this solution has two race conditions related to the source file. First, before calling link()
, the program must use lstat()
to check that the source file is not a directory or symbolic link. Second, the source file could change during the time window between the link()
and the unlink()
. Consequently, this alternative solution can be safely executed only when the source file is located within a secure directory.
Compliant Solution (
...
Windows)
On POSIX systems, if the file referenced by new_file
exists prior to Windows, the rename()
function fails if a
file or directory specified by
newname
already exists or could not be created (invalid path). [MSDN]
Consequently, it is unnecessary to explicitly check for the existence of the destination file before calling rename()
, the file is automatically removed.
Code Block | ||||
---|---|---|---|---|
| ||||
const char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (rename(oldsrc_file, newdest_file) != 0) { /* Handle error condition */ } |
...
Remove Existing Destination File
...
If the desired behavior is to ensure that any the destination file referenced by new_file
is not removed, POSIX programmers must implement additional safe guards.
Non-Compliant Code Example (POSIX)
is erased by the rename()
operation, Windows programmers must write additional code.
Noncompliant Code Example (Windows)
If the intent of the programmer is to remove the file referenced by dest_file
if it exists prior to calling rename()
, this code example is noncompliant on Windows platforms because rename()
will fail:This code example is non-compliant because if new_file
exists it will be removed by rename()
.
Code Block | ||||
---|---|---|---|---|
| ||||
const char char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (rename(oldsrc_file, newdest_file) != 0) { /* Handle Errorerror */ } |
Compliant Solution (
...
Windows)
On Windows systems, it is necessary to explicitly remove the destination file before calling rename()
if the programmer wants the file to be overwritten and the rename()
operation to succeed: 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 which can check for the existence of a file explicitly \[[Open Group 04|AA. C References#Open Group 04]\]. This compliant solution only renames the file referenced by {{old_file}} if the file referenced by {{new_file}} does not exist. Wiki Markup
Code Block | ||||
---|---|---|---|---|
| ||||
const char char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (_access_s(newdest_file, F_OK0) !== 0) { if (renameremove(olddest_file, new_file) != 0) { /* Handle error condition */ } } else if (rename(src_file, dest_file) != 0) { /* Handle error condition */ } |
This code contains an unavoidable race condition conditions between the call calls to _access_s()
, remove()
, and the call to rename()
and can consequently only be safely executed only within a secure directory. (see FIO17See FIO15-AC. Ensure that file operations are performed in a secure directory).
While the likelihood of access()
returning a false negative is lower than that of fopen()
, 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)
Wiki Markup |
---|
On Windows, the [{{rename()}}|http://msdn.microsoft.com/en-us/library/zw5t957f(VS.80).aspx] function fails if: \[[MSDN|AA. C References#MSDN]\] |
File or directory specified by
newname
already exists or could not be created (invalid path)
) Another option would be to use the MoveFileEx API and pass in the MOVEFILE_REPLACE_EXISTING
flag:
Code Block | ||||
---|---|---|---|---|
| ||||
const char *src_file = /* ... */;
const char *dest_file = /* ... */;
if (!MoveFileEx(src_file, dest_file, MOVEFILE_REPLACE_EXISTING)) {
/* Handle error condition */
}
|
Although this code is not portable, it does avoid the race condition when using _access_s(), remove()
, and rename()
.
Compliant Solution (POSIX)
On POSIX systems, if the destination file exists prior to calling rename()
, the file is automatically removed:Consequently, it is unnecessary to explicitly check for the existence of the file referenced by new_file
before calling rename()
.
Code Block | ||||
---|---|---|---|---|
| ||||
const char char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (rename(oldsrc_file, newdest_file) != 0) { /* Handle error Errorcondition */ } |
Portable Behavior
A programmer that who wants to an application to behave the same on any C99 C implementation must first determine what behavior to implement.
Compliant Solution (
...
Remove Existing Destination File
...
)
This compliant solution ensures that any file referenced by new_destination file is portably removed.:
Code Block | ||||
---|---|---|---|---|
| ||||
const char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (void)remove(newdest_file) != 0) { /* Handle error condition */ }; if (rename(oldsrc_file, newdest_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 consequently only be safely executed only within a secure directory. (see FIO17See FIO15-AC. 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 if 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. This is a valid exception (EXP12-C-EX1) to EXP12-C. Do not ignore values returned by functions.
Compliant Solution (
...
Preserve Existing Destination File
...
)
This compliant solution only renames the source file referenced by old_file
only if the file referenced by new_destination file does not exist.:
Code Block | ||||
---|---|---|---|---|
| ||||
const char const *oldsrc_file = /* ... */; const char const *newdest_file = /* ... */; if (access(new!file_exists(dest_file, F_OK) != 0) { if (rename(oldsrc_file, newdest_file) != 0) { /* Handle error condition */ } } else { /* Handle errorfile-exists condition */ } |
This code contains an unavoidable race condition between the call to removefile_exists()
and the call to rename()
and can consequently only be safely executed only within a secure directory. (see FIO17See FIO15-AC. Ensure that file operations are performed in a secure directory.)
The file_exists()
function is provided by the application and is not shown here because 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 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- |
C |
Medium |
Probable |
Medium | P8 | L2 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| (customization) | Users can add a custom check for all uses of rename() . | ||||||
Helix QAC |
| C5015 | |||||||
LDRA tool suite |
| 592 S | Fully Implemented |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\] Section 7.9.4.2, "The {{rename}} function"
\[[MSDN|AA. C References#MSDN]\] [{{rename()}}|http://msdn.microsoft.com/en-us/library/zw5t957f(VS.80).aspx]
\[[Open Group 04|AA. C References#Open Group 04]\] [{{access()}}|http://www.opengroup.org/onlinepubs/009695399/functions/access.html] |
Related Guidelines
Bibliography
[IEEE Std 1003.1:2013] | XSH, System Interfaces, access |
[MSDN] | rename() |
...
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()