原文链接:

STR51-CPP. Do not attempt to create a std::string from a null pointer


std::basic_string 类型使用 traits 设计模式来处理各种字符串类型的实现细节, 形成一系列具有通用底层实现的类 string 类. 确切地说, std::basic_string 通过配对使用 std::char_traits 来创建 std::string, std::wstring, std::u16string, 和 std::u32string 类. std::char_traits 类被显式特例化来对 std::basic_string 类型提供基于策略的实现细节 (policy-based implementation details) . std::char_traits::length() 函数是这类实现细节之一 – 常用于判定以空字符终止的字符串的字符数目. 根据 C++ Standard, [char.traits.require], Table 62 [ISO/IEC 14882-2014], 将 null 指针传入该函数是 未定义行为, undefined behavior , 因为这将导致解引用空指针.

下述 std::basic_string 成员函数都会调用 std::char_traits::length():

  • basic_string::basic_string(const charT *, const Allocator &)
  • basic_string &basic_string::append(const charT *)
  • basic_string &basic_string::assign(const charT *)
  • basic_string &basic_string::insert(size_type, const charT *)
  • basic_string &basic_string::replace(size_type, size_type, const charT *)
  • basic_string &basic_string::replace(const_iterator, const_iterator, const charT *)
  • size_type basic_string::find(const charT *, size_type)
  • size_type basic_string::rfind(const charT *, size_type)
  • size_type basic_string::find_first_of(const charT *, size_type)
  • size_type basic_string::find_last_of(const charT *, size_type)
  • size_type basic_string::find_first_not_of(const charT *, size_type)
  • size_type basic_string::find_last_not_of(const charT *, size_type)
  • int basic_string::compare(const charT *)
  • int basic_string::compare(size_type, size_type, const charT *)
  • basic_string &basic_string::operator=(const charT *)
  • basic_string &basic_string::operator+=(const charT *)

下述 std::basic_string 非成员函数会调用 std::char_traits::length():

  • basic_string operator+(const charT *, const basic_string&)
  • basic_string operator+(const charT *, basic_string &&)
  • basic_string operator+(const basic_string &, const charT *)
  • basic_string operator+(basic_string &&, const charT *)
  • bool operator==(const charT *, const basic_string &)
  • bool operator==(const basic_string &, const charT *)
  • bool operator!=(const charT *, const basic_string &)
  • bool operator!=(const basic_string &, const charT *)
  • bool operator<(const charT *, const basic_string &)
  • bool operator<(const basic_string &, const charT *)
  • bool operator>(const charT *, const basic_string &)
  • bool operator>(const basic_string &, const charT *)
  • bool operator<=(const charT *, const basic_string &)
  • bool operator<=(const basic_string &, const charT *)
  • bool operator>=(const charT *, const basic_string &)
  • bool operator>=(const basic_string &, const charT *)

空指针作为 const charT * 的参数时, 不要调用任一上述的函数. 这条规则是 EXP34-C. Do not dereference null pointers 的一个具体例子.

实现细节

某些厂商的标准库, 像 libstdc++, 当在上述函数调用中使用空指针时, 会抛出 std::logic_error , 尽管不在调用 std::char_traits::length() 时. 然而, std::logic_error 并不是 C++ Standard 所必需具备的, 某些厂商 (例如, libc++Microsoft Visual Studio STL) 并没有实现该行为. 考虑移植性, 你不应该依赖这个行为.

不合规代码示例

在这个不合规代码示例中, 创建了一个 std::string 对象来保存 std::getenv() 调用返回的结果. 然而, 因为 std::getenv() 在失败时返回空指针, 所以当环境变量不存在 (或者发生其他错误) 时, 这段代码会导致 未定义行为, undefined behavior .

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

void f() {
std::string tmp(std::getenv("TMP"));
if (!tmp.empty()) {
// ...
}
}

合规方案

在这个合规方案中, 在构造 std::string 对象之前, 检查了 std::getenv() 调用返回的结果是否为空.

1
2
3
4
5
6
7
8
9
10
#include <cstdlib>
#include <string>

void f() {
const char *tmpPtrVal = std::getenv("TMP");
std::string tmp(tmpPtrVal ? tmpPtrVal : "");
if (!tmp.empty()) {
// ...
}
}

风险预估

解引用空指针是 未定义行为, undefined behavior, 典型地是 程序异常终止, abnormal program termination. 在某些情景中, 解引用空指针会导致任意代码执行 [Jack 2007, van Sprundel 2006]. 标示的严重性是对于某些更严重的情况而言的; 在那些无法 利用, exploit 解引用空指针来执行任意代码的平台上, 实际的严重性较低.

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

Automated Detection

Tool Version Checker Description
Astrée 20.10 **assert_failure **
Helix QAC 2021.2 C++4770, C++4771, C++4772, C++4773, C++4774
Klocwork 2021.1 NPD.CHECK.CALL.MIGHT NPD.CHECK.CALL.MUST NPD.CHECK.MIGHTNPD.CHECK.MUSTNPD.CONST.CALL NPD.CONST.DEREF NPD.FUNC.CALL.MIGHT NPD.FUNC.CALL.MUST NPD.FUNC.MIGHT **NPD.FUNC.MUSTNPD.GEN.CALL.MIGHT NPD.GEN.CALL.MUST NPD.GEN.MIGHT NPD.GEN.MUSTRNPD.CALL RNPD.DEREF **
Parasoft C/C++test 2021.1 CERT_CPP-STR51-a Avoid null pointer dereferencing

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

SEI CERT C Coding Standard EXP34-C. Do not dereference null pointers

Bibliography

[ISO/IEC 9899:2011] Subclause 7.20.3, “Memory Management Functions”
[ISO/IEC 14882-2014] Subclause 21.2.1, “Character Trait Requirements”
[Jack 2007]
[van Sprundel 2006]

img img img