CertC-FIO39

Do not alternately input and output from a stream without an intervening flush or positioning call

Required inputs: IR, StaticSemanticAnalysis

The C Standard, 7.21.5.3, paragraph 7 [ 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, or rewind), 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. (See undefined behavior 151.)

  •  Receiving input from a stream directly following an output to that stream without an intervening call to fflush(), fseek(), fsetpos(), or rewind() 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(), or rewind() if the file is not at end-of-file
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, 1, BUFFERSIZE, file) != BUFFERSIZE) {
    /* Handle error */
  }
  if (fread(data, 1, BUFFERSIZE, file) < BUFFERSIZE) {
    /* Handle there not being data */
  }

  if (fclose(file) == EOF) {
    /* Handle error */
  }
}

Because there is no intervening flush or positioning call between the calls 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 */
  }

  if (fclose(file) == EOF) {
    /* Handle error */
  }
}
Risk Assessment

Alternately inputting and outputting from a stream without an intervening flush or positioning call is undefined behavior.

Rule Severity Likelihood Remediation Cost Priority Level
FIO39-C Low Likely Medium P6 L2
Related Guidelines
Taxonomy Taxonomy item Relationship
CERT C FIO50-CPP. Do not alternately input and output from a file stream without an intervening positioning call Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TS 17961:2013 Interleaving stream inputs and outputs without a flush or positioning call [ioileave] Prior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11 CWE-664 2017-07-10: CERT: Rule subset of CWE
Bibliography
[ ISO/IEC 9899:2011] 7.21.5.3, "The fopen Function"
Excerpt from SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition) and SEI CERT C Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/input-output-fio/fio39-c], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

Possible Messages

Key

Text

Severity

Disabled

alternating_input_output

Alternating input and output without intervening flush or positioning.

None

False

possibly_alternating_input_output

Possibly alternating input and output without intervening flush or positioning. References a stream from outside the routine.

None

False

Options

consider_std_fstream

consider_std_fstream : bool = False

Whether streams based on std::fstream should be considered.
 

consider_stlib_file_stream

consider_stlib_file_stream : bool = True

Whether streams based on stdio.h FILE streams should be considered.
 

reading_functions

reading_functions : set[bauhaus.analysis.config.QualifiedName] = {'fread', 'std::basic_istream::get', 'std::basic_istream::read', 'std::getline'}

Reading functions whose calls should not be followed by writing function calls without an intervening call to a resetting function call. operator>> is always considered as a reading function.
 

report_non_local_streams

report_non_local_streams : bool = False

If set to true, (possible) violations are reported for streams that are not local to routines and could thus be modified elsewhere.
 

resetting_functions

resetting_functions

Type: set[bauhaus.analysis.config.QualifiedName]

Default: {'fflush', 'fseek', 'fsetpos', 'rewind', 'std::basic_istream::seekg', 'std::basic_istream::tellg', 'std::basic_ostream::seekp', 'std::basic_ostream::tellp', 'std::basic_streambuf::pubseekoff', 'std::basic_streambuf::pubseekpos'}

Possible functions that reset the stream or file for subsequent reading or writing. Any reassignment to a stream or file counts as resetting.
 

writing_functions

writing_functions : set[bauhaus.analysis.config.QualifiedName] = {'fwrite', 'std::basic_ostream::put', 'std::basic_ostream::write'}

Writing functions whose calls should not be followed by reading function calls without an intervening call to a resetting function call. operator<< is always considered as a writing function.