原文链接:

STR50-CPP. Guarantee that storage for strings has sufficient space for character data and the null terminator


将数据拷贝到不够大的缓存中会导致缓冲区溢出 (buffer overflow). 缓冲区移除经常发生在字符串操作上 [Seacord 2013]. 为避免这类错误, 无论时通过截断限制拷贝, 或者, 最好是确保目标地址能有足够大小来保存被拷贝的数据. C 风格的字符串需要一个空字符来标识字符串的结尾, 但是 C++ std::basic_string 模板不需要该空字符.

不合规代码示例

因为输入是无界的, 所以下述代码可能导致缓冲区溢出.

1
2
3
4
5
6
#include <iostream>

void f() {
char buf[12];
std::cin >> buf;
}

不合规代码示例

为解决这个问题, 可能会使用 std::ios_base::width() 方法, 但是仍然是个陷阱, 如下不合规代码示例所示.

1
2
3
4
5
6
7
8
9
#include <iostream>

void f() {
char bufOne[12];
char bufTwo[12];
std::cin.width(12);
std::cin >> bufOne;
std::cin >> bufTwo;
}

在这个例子中, 第一次读取不会溢出, 但是填充 bufOne 字符串会被截断. 进而, 第二次读取时仍然溢出 bufTwo . C++ Standard, [istream.extractors], paragraphs 7–9 [ISO/IEC 14882-2014], 表述了 operator>>(basic_istream &, charT *) 的行为, 部分表述如下:

operator>> 会在下个位置存储一个空字节 (charT()) – 如果没有字符输入, 这可能是第一个位置. operator>> 继而调用 width(0) .

operator>> then stores a null byte (charT()) in the next position, which may be the first position if no characters were extracted. operator>> then calls width(0).

因此, 在每次调用 operator>> 输入有界数组前优先调用 width() 是必要的. 然而, 这并没有解决输入被切断的问题 – 可能导致信息丢失或者潜在 漏洞, vulnerability.

合规示例

确保数据不被截断并避免缓冲区溢出的最佳方案是用 std::string 来代替有界数组, 类似这个合规方案.

1
2
3
4
5
6
7
8
#include <iostream>
#include <string>

void f() {
std::string input;
std::string stringOne, stringTwo;
std::cin >> stringOne >> stringTwo;
}

不合规代码示例

在这个不合规代码示例中, 使用未格式化的输入函数从给定文件中读取一个包含 32 个字符的未格式化的字符数组. 然而, read() 函数未保证该字符串以 null 结尾, 所以如果该字符数组不包含一个空终止符, 后续 std::string 构造函数的调用导致 未定义行为, undefined behavior .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <fstream>
#include <string>

void f(std::istream &in) {
char buffer[32];
try {
in.read(buffer, sizeof(buffer));
} catch (std::ios_base::failure &e) {
// Handle error
}

std::string str(buffer);
// ...
}

合规方案

这个合规方案假设从文件输入最多 32 个字符. 该方案基于从输入流读取的字符数量构造了一个 std::string 对象, 而不是插入一个空终止符. 如果输入的大小是不确定的, 最好根据实际需求使用 std::basic_istream<T>::readsome() 或者格式化输入函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <fstream>
#include <string>

void f(std::istream &in) {
char buffer[32];
try {
in.read(buffer, sizeof(buffer));
} catch (std::ios_base::failure &e) {
// Handle error
}
std::string str(buffer, in.gcount());
// ...
}

Risk Assessment

Copying string data to a buffer that is too small to hold that data results in a buffer overflow. Attackers can exploit this condition to execute arbitrary code with the permissions of the vulnerable process.

Rule Severity Likelihood Remediation Cost Priority Level
STR50-CPP High Likely Medium P18 L1

Automated Detection

Tool Version Checker Description
CodeSonar 6.1p0 *MISC.MEM.NTERM***LANG.MEM.BO LANG.MEM.TO ** No space for null terminatorBuffer overrun Type overrun
Helix QAC 2021.2 C++2835, C++2836, C++2839, C++5216
Klocwork 2021.1 NNTS.MIGHT NNTS.TAINTED
LDRA tool suite 9.7.1 489 S, 66 X, 70 X, 71 X** ** Partially implemented
Parasoft C/C++test 2021.1 CERT_CPP-STR50-b CERT_CPP-STR50-c CERT_CPP-STR50-e CERT_CPP-STR50-f CERT_CPP-STR50-g Avoid overflow due to reading a not zero terminated string Avoid overflow when writing to a buffer Prevent buffer overflows from tainted data Avoid buffer write overflow from tainted data Do not use the ‘char’ buffer to store input from ‘std::cin’
Polyspace Bug Finder R2021b CERT C++: STR50-CPP Checks for:Use of dangerous standard functionMissing null in string arrayBuffer overflow from incorrect string format specifierDestination buffer overflow in string manipulationRule partially covered.
SonarQube C/C++ Plugin 4.10 S3519

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

SEI CERT C Coding Standard STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator

Bibliography

[ISO/IEC 14882-2014] Subclause 27.7.2.2.3, “basic_istream::operator>>“ Subclause 27.7.2.3, “Unformatted Input Functions”
[Seacord 2013] Chapter 2, “Strings”

img img img