Subclause 7.21.5.3 of the C Standard [ISO/IEC 9899:2011] places the following restrictions on update streams:
When a file is opened with update mode . . ., both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the
fflush
function or to a file positioning function (fseek
,fsetpos
, orrewind
), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file. Opening (or creating) a text file with update mode may instead open (or create) a binary stream in some implementations.
The following scenarios can result in undefined behavior:
- Receiving input from a stream directly following an output to that stream without an intervening call to
fflush()
,fseek()
,fsetpos()
, orrewind()
if the file is not at end-of-file - Outputting to a stream after receiving input from that stream without a call to
fseek()
,fsetpos()
, orrewind()
if the file is not at end-of-file
(See also undefined behavior 151 in Annex J of C11.) Consequently, a call to fseek()
, fflush()
, or fsetpos()
is necessary between input and output to the same stream. (See FIO07-C. Prefer fseek() to rewind().)
Noncompliant Code Example
This noncompliant code example appends data to a file and then reads from the same file:
#include <stdio.h> enum { BUFFERSIZE = 32 }; extern void initialize_data(char *data, size_t size); void func(const char *file_name) { char data[BUFFERSIZE]; char append_data[BUFFERSIZE]; FILE *file; file = fopen(file_name, "a+"); if (file == NULL) { /* Handle error */ } initialize_data(append_data, BUFFERSIZE); if (fwrite(append_data, BUFFERSIZE, 1, file) != BUFFERSIZE) { /* Handle error */ } if (fread(data, BUFFERSIZE, 1, file) != 0) { /* Handle there not being data. */ } fclose(file); }
However, because the stream is not flushed between the call to fread()
and fwrite()
, the behavior is undefined.
Compliant Solution
In this compliant solution, fseek()
is called between the output and input, eliminating the undefined behavior:
#include <stdio.h> enum { BUFFERSIZE = 32 }; extern void initialize_data(char *data, size_t size); void func(const char *file_name) { char data[BUFFERSIZE]; char append_data[BUFFERSIZE]; FILE *file; file = fopen(file_name, "a+"); if (file == NULL) { /* Handle error */ } initialize_data(append_data, BUFFERSIZE); if (fwrite(append_data, BUFFERSIZE, 1, file) != BUFFERSIZE) { /* Handle error */ } if (fseek(file, 0L, SEEK_SET) != 0) { /* Handle error */ } if (fread(data, BUFFERSIZE, 1, file) != 0) { /* Handle there not being data. */ } fclose(file); }
Risk Assessment
Alternately inputting and outputting from a stream without an intervening flush or positioning call results in undefined behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
FIO39-C | low | likely | medium | P6 | L2 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Compass/ROSE | Can detect simple violations of this rule | ||
Fortify SCA | 5.0 | Can detect violations of this rule with CERT C Rule Pack | |
9.7.1 | 84 D | Fully implemented |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
CERT C++ Secure Coding Standard | FIO39-CPP. Do not alternately input and output from a stream without an intervening flush or positioning call |
CERT C Secure Coding Standard | FIO07-C. Prefer fseek() to rewind() |
ISO/IEC TS 17961 (Draft) | Interleaving stream inputs and outputs without a flush or positioning call [ioileave] |
Bibliography
[ISO/IEC 9899:2011] | Subclause 7.21.5.3, "The fopen Function" |