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.
Code Block | ||
---|---|---|
| ||
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.
Code Block | ||
---|---|---|
| ||
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.
Code Block |
---|
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.
...
Code Block | ||
---|---|---|
| ||
char buf[BUFSIZ]; if (gets_s(buf, sizeof(buf)) == NULL) { /* handle error */ } |
getchar()
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.
Code Block | ||
---|---|---|
| ||
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.
Code Block | ||
---|---|---|
| ||
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.
Code Block |
---|
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.
scanf()
Noncompliant Code Example
( scanf()
)
The 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.
Code Block | ||
---|---|---|
| ||
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()
.
Code Block | ||
---|---|---|
| ||
#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<<()
)
Inputting more than 11 characters into the following the C++ program results in an out-of-bounds write:
Code Block | ||
---|---|---|
| ||
#include <iostream>
int main(void) {
using namespace std;
char buf[12];
cin >> buf;
cout << "echo: " << buf << endl;
}
|
Compliant Solution (operator<<()
)
The extraction operation can be limited to a specified number of characters if ios_base::width
is set to a value > 0. In this compliant solution, the width
field is set to the size of the buffer. In this particular example, this limits the number of characters read from stdin
to 11 characters. The call to the extraction operator appends a null-termination character to the character array.
Code Block | ||
---|---|---|
| ||
#include <iostream>
int main(void) {
using namespace std;
char buf[12];
cin.width(12);
cin >> buf;
cout << "echo: " << buf << endl;
}
|
After a call to the extraction operation, the value of the width
field is reset to 0.
Risk Assessment
Copying data from an unbounded source to a buffer of fixed size may result in a buffer overflow.
...
Wiki Markup |
---|
\[[Drepper 06|AA. C++ References#Drepper 06]\] Section 2.1.1, "Respecting Memory Bounds"
\[[ISO/IEC 14882-2003|AA. C++ References#ISO/IEC 14882-2003]\] Sections 3.6.1 Main function, and 18.7 Other runtime support
\[[ISO/IEC 9899:1999|AA. C++ References#ISO/IEC 9899-1999]\] Section 7.19, "Input/output <{{stdio.h}}>"
\[[ISO/IEC TR 24731-2006|AA. C++ References#ISO/IEC TR 24731-2006]\] Section 6.5.4.1, "The {{gets_s}} function"
\[[Lai 06|AA. C++ References#Lai 06]\]
\[[MITRE 07|AA. C++ References#MITRE 07]\] [CWE ID 120|http://cwe.mitre.org/data/definitions/120.html], "Unbounded Transfer ('Classic Buffer Overflow')"
\[[NIST 06|AA. C++ References#NIST 06]\] SAMATE Reference Dataset Test Case ID 000-000-088
\[[Seacord 05a|AA. C++ References#Seacord 05]\] Chapter 2, "Strings" |
...