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

Compare with Current View Page History

« Previous Version 4 Next »

Functions that perform unbounded copies often rely on external input to be a reasonable size. Such assumptions may prove to be false, causing a buffer overflow to occur. For this reason, care must be taken when using functions that may perform unbounded copies.

Noncompliant Code Example

This example uses the getchar() function to read in a character at a time from stdin, instead of reading the entire line at once. The stdin stream is read until end-of-file is encountered or a new-line character is read. Any new-line character is discarded, and a null character is written immediately after the last character read into the array. Similar to the previous example, there are no guarantees that this code will not result in a buffer overflow.

char buf[BUFSIZ], *p;
int ch;
p = buf;
while ( ((ch = getchar()) != '\n')
       && !feof(stdin)
       && !ferror(stdin))
{
  *p++ = ch;
}
*p++ = 0;

Compliant Solution

In this compliant solution, characters are no longer copied to buf once index = BUFSIZ, leaving room to null terminate the string. The loop continues to read through to the end of the line until the end of the file is encountered or an error occurs.

unsigned char buf[BUFSIZ];
int ch;
int index = 0;
int chars_read = 0;
while ( ( (ch = getchar()) != '\n')
        && !feof(stdin)
        && !ferror(stderr) )
{
  if (index < BUFSIZ-1) {
    buf[index++] = (unsigned char)ch;
  }
  chars_read++;
} /* end while */
buf[index] = '\0';  /* terminate NTBS */
if (feof(stdin)) {
  /* handle EOF */
}
if (ferror(stdin)) {
  /* handle error */
}
if (chars_read > index) {
  /* handle truncation */
}

If at the end of the loop feof(stdin) != 0, the loop has read through to the end of the file without encountering a new-line character. If at the end of the loop ferror(stdin) != 0, a read error occurred before the loop encountered a new-line character. If at the end of the loop chars_read > index, the input string has been truncated. Rule FIO34-CPP. Use int to capture the return value of character IO functions is also applied in this solution.

Reading a character at a time provides more flexibility in controlling behavior without additional performance overhead.

The following test for the while loop is normally sufficient.

while ( ( (ch = getchar()) != '\n') && ch != EOF ) {

See FIO35-CPP. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char) for the case where feof() and ferror() must be used instead.

Noncompliant Code Example ( gets() )

The gets() function is inherently unsafe, and should never be used as it provides no way to control how much data is read into a buffer from stdin. These two lines of code assume that gets() will not read more than BUFSIZ - 1 characters from stdin. This is an invalid assumption and the resulting operation can cause a buffer overflow.

According to Section 7.19.7.7 of C99 [[ISO/IEC 9899:1999]], the gets() function reads characters from the stdin into a destination array until end-of-file is encountered or a new-line character is read. Any new-line character is discarded, and a null character is written immediately after the last character read into the array.

char buf[BUFSIZ];
if (gets(buf) == NULL) {
  /* Handle Error */
}

The gets() function is obsolescent, and is deprecated.

Compliant Solution ( fgets() )

The fgets() function reads, at most, one less than a specified number of characters from a stream into an array. This example is compliant because the number of bytes copied from stdin to buf cannot exceed the allocated memory.

char buf[BUFSIZ];
int ch;
char *p;

if (fgets(buf, sizeof(buf), stdin)) {
  /* fgets succeeds, scan for newline character */
  p = strchr(buf, '\n');
  if (p) {
    *p = '\0';
  }
  else {
    /* newline not found, flush stdin to end of line */
    while (((ch = getchar()) != '\n')
          && !feof(stdin)
          && !ferror(stdin)
    );
  }
}
else {
  /* fgets failed, handle error */
}

The fgets() function, however, is not a strict replacement for the gets() function because fgets() retains the new-line character (if read) but may also return a partial line. It is possible to use fgets() to safely process input lines too long to store in the destination array, but this is not recommended for performance reasons. Consider using one of the following compliant solutions when replacing gets().

Compliant Solution ( get_s() )

The gets_s() function reads at most one less than the number of characters specified from the stream pointed to by stdin into an array.

According to TR 24731 [[ISO/IEC TR 24731-2006]]:
<blockquote><p>No additional characters are read after a new-line character (which is discarded) or after end-of-file. The discarded new-line character does not count towards number of characters read. A null character is written immediately after the last character read into the array.</p></blockquote>If end-of-file is encountered and no characters have been read into the destination array, or if a read error occurs during the operation, then the first character in the destination array is set to the null character and the other elements of the array take unspecified values.

char buf[BUFSIZ];

if (gets_s(buf, sizeof(buf)) == NULL) {
  /* handle error */
}

Noncompliant Code Example ( scanf() )

The scanf() function is used to read and format input from stdin. Improper use of scanf() may result in an unbounded copy. In the code below, the call to scanf() does not limit the amount of data read into buf. If more than 9 characters are read, then a buffer overflow occurs.

enum { CHARS_TO_READ = 9 };

char buf[CHARS_TO_READ + 1];
scanf("%s", buf);

Compliant Solution ( scanf() )

The number of characters read by scanf() can be bounded by using the format specifier supplied to scanf().

#define STRING(n) STRING_AGAIN(n)
#define STRING_AGAIN(n) #n

#define CHARS_TO_READ 9

char buf[CHARS_TO_READ + 1];
scanf("%"STRING(CHARS_TO_READ)"s", buf);

Non-Compliant Code Example (operator<<())

Since the input is unbounded, the following code could lead to a buffer overflow

char buf[12];
cin >> buf;

To solve this problem, one can be tempted to use the width method of the ios_base class, but there still is a trap.

char buf_one[12];
char buf_two[12];
cin.width(12);
cin >> buf_one;
cin >> buf_two;

In this example, the first read won't overflow, but the second still could, because as the C++ standard states : "operator>> extracts characters and stores them into successive locations of an array [...] operator>> then calls width(0)." Which means that width should be called every time you use the >> operator with a bounded array.

Arguably better code

While the following doesn't suffer of the same problem as the previous, it still has some :

char buf_one[12];
char buf_two[12];
cin.width(12);
cin >> buf_one;
cin.width(12);
cin >> buf_two;

because, as the C++ standard states, "If width() is greater than zero, n is width() [...] n-1 characters are stored [...] Operator>> then stores a null byte (charT()) in the next position, which may be the first position if no characters were extracted." The input could therefore be truncated, leading to information lost, and to a possible vulnerability.
In this particular example, if the user enters a string longer than 11 (11 characters + the NULL terminating character automatically appended by the >> operator equals 12 characters), the 12th and all subsequent characters will be lost.

To avoid this truncation problem, it would be better to use an instance of the string class to store the input, as it is dynamically resized to fit the input.

string input;
cin >> input;
const char *array = input.c_str();

The only problem with this code is that it will be necessary to copy the characters in another array if they are to be modified.

Risk Assessment

Copying data from an unbounded source to a buffer of fixed size may result in a buffer overflow.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

STR35-CPP

high

likely

medium

P18

L1

Automated Detection

The LDRA tool suite Version 7.6.0 can detect violations of this rule.

Compass/ROSE can detect some violations of this rule.

Klocwork Version 8.0.4.16 can detect violations of this rule with the NNTS.TAINTED, SV.STRBO.GETS, and SV.USAGERULES.UNBOUNDED_STRING_COPY checkers.

Related Vulnerabilities

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

Other Languages

This rule appears in the C Secure Coding Standard as STR35-C. Do not copy data from an unbounded source to a fixed-length array.

References

[[Drepper 06]] Section 2.1.1, "Respecting Memory Bounds"
[[ISO/IEC 14882-2003]] Sections 3.6.1 Main function, and 18.7 Other runtime support
[[ISO/IEC 9899:1999]] Section 7.19, "Input/output <stdio.h>"
[[ISO/IEC TR 24731-2006]] Section 6.5.4.1, "The gets_s function"
[[Lai 06]]
[[MITRE 07]] CWE ID 120, "Unbounded Transfer ('Classic Buffer Overflow')"
[[NIST 06]] SAMATE Reference Dataset Test Case ID 000-000-088
[[Seacord 05a]] Chapter 2, "Strings"


STR34-CPP. Cast characters to unsigned types before converting to larger integer sizes      07. Characters and Strings (STR)      STR36-CPP. Do not specify the bound of a character array initialized with a string literal

  • No labels