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

Compare with Current View Page History

« Previous Version 34 Next »

Proper understanding of the difference between text mode and binary mode is important when using functions that operate on file streams. (See FIO14-C. Understand the difference between text mode and binary mode with file streams for more information.)

Section 7.21.9.2 of the C standard [ISO/IEC 9899:2011] states the following specific behavior for fseek() when opening a binary file in binary mode:

A binary stream need not meaningfully support fseek calls with a whence value of SEEK_END.

In addition, footnote 268 of Section 7.21.3 has this to say:

Setting the file position indicator to end-of-file, as with fseek(file, 0, SEEK_END), has undefined behavior for a binary stream (because of possible trailing null characters) or for any stream with state-dependent encoding that does not assuredly end in the initial shift state.

Seeking to the end of a binary stream in binary mode with fseek() is not meaningfully supported and, as a result, not a recommended method for computing the size of a file.

Section 7.21.9.4 of the C standard [ISO/IEC 9899:2011] states the following specific behavior for ftell() when opening a text file in text mode:

For a text stream, its file position indicator contains unspecified information, usable by the fseek function for returning the file position indicator for the stream to its position at the time of the ftell call.

Consequently, the return value of ftell() for streams opened in text mode should never be used for offset calculations other than in calls to fseek().

POSIX [Open Group 2008] provides several guarantees that the problems described in the C standard do not occur on POSIX systems.

First, the fopen() page says:

The character 'b' shall have no effect, but is allowed for ISO C standard conformance.

This guarantees that binary files are treated the same as text files in POSIX.

Second, the fwrite() page says:

For each object, size calls shall be made to the fputc() function, taking the values (in order) from an array of unsigned char exactly overlaying the object. The file-position indicator for the stream (if defined) shall be advanced by the number of bytes successfully written.

This indicates that the file position indicator, and consequently file size, is directly based on the number of bytes actually written to a file.

Noncompliant Code Example (Binary File)

This code example attempts to open a binary file in binary mode and use fseek() and ftell() to obtain the file size.  This code is noncompliant on systems that do not provide the same guarantees as POSIX. On these systems, setting the file position indicator to the end of the file using fseek() is not guaranteed to work for a binary stream, and consequently, the amount of memory allocated may be incorrect, leading to a potential vulnerability.

FILE *fp;
long file_size;
char *buffer;

fp = fopen("foo.bin", "rb");
if (fp == NULL) {
  /* Handle Error */
}

if (fseek(fp, 0 , SEEK_END) != 0) {
  /* Handle Error */
}

file_size = ftell(fp);
if (file_size == -1) {
  /* Handle Error */
}

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* Handle Error */
}

/* ... */ 

Compliant Solution (POSIX)

The above code is, however, perfectly compliant when run on a regular file in POSIX.

FILE *fp;
long file_size;
char *buffer;
struct stat st;
 
fp = fopen("foo.bin", "rb");
if (fp == NULL) {
  /* Handle Error */
}
 
if ((fstat(fileno(fp), &st) != 0) || (!S_ISREG(st.st_mode))) {
  /* Handle Error */
}
if (fseek(fp, 0 , SEEK_END) != 0) {
  /* Handle Error */
}
 
file_size = ftell(fp);
if (file_size == -1) {
  /* Handle Error */
}
 
buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* Handle Error */
}

/* ... */ 

If the code needs to handle large files, it is preferable to use fseeko() and ftello() since in some compilation environments they can handle larger file offsets than fseek() and ftell().  If they are used, the file_size variable should have type off_t in order to avoid the possibility of overflow when assigning the return value of ftello() to it.

Compliant Solution (POSIX fstat())

This compliant solution uses the POSIX fstat() function, rather than fseek() and ftell(), to obtain the size of the binary file.

FILE *fp;
off_t file_size;
char *buffer;
struct stat stbuf;
int fd;

fd = open("foo.bin", O_RDONLY);
if (fd == -1) {
  /* Handle Error */
}

fp = fdopen(fd, "rb");
if (fp == NULL) {
  /* Handle Error */
}

if (fstat(fd, &stbuf) == -1) {
  /* Handle Error */
}

file_size = stbuf.st_size;

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* Handle Error */
}

/* ... */ 

Compliant Solution (Windows _fstat())

Windows provides an _fstat() function, which behaves similarly to POSIX fstat().

FILE *fp;
long file_size;
char *buffer;
struct _stat stbuf;
int fd;

fd = open("foo.bin", O_RDONLY);
if (fd == -1) {
  /* Handle Error */
}

fp = fdopen(fd, "rb");
if (fp == NULL) {
  /* Handle Error */
}

if (_fstat(fd, &stbuf) == -1) {
  /* Handle Error */
}

file_size = stbuf.st_size;

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* Handle Error */
}

/* ... */ 

Noncompliant Code Example (Text File)

This noncompliant code example attempts to open a text file in text mode and use fseek() and ftell() to obtain the file size.

FILE *fp;
long file_size;
char *buffer;

fp = fopen("foo.txt", "r");
if (fp == NULL) {
  /* Handle Error */
}

if (fseek(fp, 0 , SEEK_END) != 0) {
  /* Handle Error */
}

file_size = ftell(fp);
if (file_size == -1) {
  /* Handle Error */
}

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* Handle Error */
}

/* ... */ 

However, the file position indicator returned by ftell() with a file opened in text mode is only useful in calls to fseek(). As such, the value of file_size may not necessarily be a meaningful measure of the number of characters in the file, and consequently, the amount of memory allocated may be incorrect, leading to a potential vulnerability.

The value returned by ftell may not reflect the physical byte offset for streams opened in text mode, because text mode causes carriage return-linefeed translation. Use ftell with fseek to return to file locations correctly.

Again, this indicates that the return value of ftell() for streams opened in text mode is useful only in calls to fseek() and should not be used for any other purpose.

Compliant Solution (POSIX)

This compliant solution uses fstat() instead to the size of the text file.

FILE *fp;
long file_size;
char *buffer;
struct stat stbuf;
int fd;

fd = open("foo.txt", O_RDONLY);
if (fd == -1) {
  /* Handle Error */
}

fp = fdopen(fd, "r");
if (fp == NULL) {
  /* Handle Error */
}

if (fstat(fd, &stbuf) == -1) {
  /* Handle Error */
}
file_size = stbuf.st_size;

buffer = (char*)malloc(file_size);
if (buffer == NULL) {
  /* Handle Error */
}

/* ... */ 

Risk Assessment

Understanding the difference between text mode and binary mode with file streams is critical when working with functions that operate on them. Setting the file position indicator to end-of-file with fseek() has undefined behavior for a binary stream. In addition, the return value of ftell() for streams opened in text mode is useful only in calls to fseek(), not for determining file sizes or for any other use. As such, fstat(), or other platform-equivalent functions, should be used to determine the size of a file.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

FIO19-C

low

unlikely

medium

P2

L3

Related Guidelines

ISO/IEC 9899:2011 Section 7.21.9.2, "The fseek function", Section 7.21.3, "Files," and Section 7.21.9.4, "The ftell function"

Bibliography

[MSDN] "ftell"

[Open Group 2008]


  • No labels