[英]address sanitizer sometimes misses heap-use-after-free
Keeping a pointer on an element of a vector which is resized and dereferencing it afterwards is undefined behavior.将指针保留在调整大小的向量元素上并在之后取消引用它是未定义的行为。
When testing this bad practice on the following program with a std::vector<int>
(with #if 0
), the address sanitizer correctly reports a heap-use-after-free error.在使用
std::vector<int>
(使用#if 0
)在以下程序上测试这种不良做法时,地址清理程序会正确报告 heap-use-after-free 错误。
$ ./prog
capa: 8
v[0]: 0x603000000010 <1000>
p: 0x603000000010 <1000>
capa: 16
v[0]: 0x6060000000e0 <1000>
=================================================================
==23068==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000010
But when trying the same experiment with std::vector<std::string>
(with #if 1
), the address sanitizer does not report anything, which leads to using a destroyed string (probably moved-from during the resize) through the pointer!但是,当使用
std::vector<std::string>
(使用#if 1
)尝试相同的实验时,地址清理程序不会报告任何内容,这会导致通过指针!
$ ./prog
capa: 8
v[0]: 0x611000000040 <1000>
p: 0x611000000040 <1000>
capa: 16
v[0]: 0x615000000080 <1000>
p: 0x611000000040 <>
My question: why does not the address sanitizer report the error in this second case?我的问题:为什么地址清理程序在第二种情况下不报告错误?
edit: valgrind reports the error.编辑:valgrind 报告错误。
I tested the following program on GNU/Linux x86_64 (Archlinux) with g++ 9.2.0 and clang++ 9.0.0.我使用 g++ 9.2.0 和 clang++ 9.0.0 在 GNU/Linux x86_64 (Archlinux) 上测试了以下程序。
/**
g++ -std=c++17 -o prog prog.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
#include <vector>
#if 1
# include <string>
inline auto make_elem(int n) { return std::to_string(n); }
#else
inline auto make_elem(int n) { return n; }
#endif
using elem_t = decltype(make_elem(0));
inline
void
fill(std::vector<elem_t> &v,
int sz)
{
v.resize(std::size_t(sz));
for(auto i=0; i<sz; ++i)
{
v[i]=make_elem(1000+i);
}
}
inline
void
show(const std::vector<elem_t> &v,
const elem_t *p)
{
std::cout << "capa: " << v.capacity() << '\n';
std::cout << "v[0]: " << &v[0] << " <" << v[0] << ">\n";
std::cout << "p: " << p << " <" << *p << ">\n"; // <-- possible invalid pointer here
}
int
main()
{
constexpr auto sz=8;
auto v=std::vector<elem_t>{};
fill(v, sz);
const auto *p=data(v);
show(v, p);
fill(v, 2*sz);
show(v, p);
return 0;
}
I've also filed upstream bug about this.我还为此提交了上游错误。
I've commented on github issue, but the short answer is that due to the way libstdc++.so.6
splits certain common template instantiations, such as我对 github 问题发表了评论,但简短的回答是由于
libstdc++.so.6
拆分某些常见模板实例的方式,例如
basic_ostream<...>::operator<<(basic_ostream<...>&, const std::string &);
and instantiates them only once inside libstdc++.so.6
, and because libstdc++.so.6
itself is not asan-instrumented, all that instrumented code can see is that you are passing a dangling pointer into an external function.并且只在
libstdc++.so.6
内实例化它们一次,并且由于libstdc++.so.6
本身不是作为仪表化的,所有仪表化代码可以看到的是您正在将一个悬空指针传递给外部 function。 It doesn't know what the external function will do with this pointer, and so can't report the error.不知道外部的function会用这个指针做什么,所以不能报错。
The problem does not reproduce with clang++... -stdlib=libc++
(dangling access is properly reported).使用 clang++无法重现该问题
clang++... -stdlib=libc++
(已正确报告悬空访问)。
By default Asan detects overflows in buffers allocated with malloc
.默认情况下,Asan 检测使用
malloc
分配的缓冲区中的溢出。 Vector's methods ( push_back
, clear
, resize
, etc.) may or may not call malloc
/ free
depending on element size, current capacity, etc. so Asan will often miss vector (or any other STL container) overflows. Vector 的方法(
push_back
、 clear
、 resize
等)可能会或可能不会调用malloc
/ free
,具体取决于元素大小、当前容量等,因此 Asan 经常会错过 vector(或任何其他 STL 容器)溢出。
Recent versions of Asan got better STL support (at least for std::vector
and std::string
) and can detect accesses outside of container's "logical" bounds.最近版本的 Asan 获得了更好的 STL 支持(至少对于
std::vector
和std::string
)并且可以检测容器“逻辑”边界之外的访问。 These checks are enabled by default in Clang but not in GCC (you need to compile with -D_GLIBCXX_SANITIZE_STD_ALLOCATOR=1
there).这些检查在 Clang 中默认启用,但在 GCC 中未启用(您需要在此处使用
-D_GLIBCXX_SANITIZE_STD_ALLOCATOR=1
进行编译)。 Enabling this feature may introduce false positives so GCC's approach is IMHO safer.启用此功能可能会引入误报,因此 GCC 的方法恕我直言更安全。
As a side note, it's risky to test Asan's functionality with simple programs like this one.作为旁注,使用像这样的简单程序测试 Asan 的功能是有风险的。 Modern compilers will often often detect and optimize out obviously UB code at early phases, sometimes even at
-O0
(hopefully with a warning so bug won't go unnoticed) before Asan instrumentation is added so Asan has no chance of detecting the bug.现代编译器通常会在早期阶段经常检测和优化明显的 UB 代码,有时甚至在
-O0
时(希望有警告,因此错误不会被 go 忽视)在添加 Asan 工具之前,因此 Asan 没有机会检测到错误。 You can prevent unwanted "optimizations" with __attribute__((noinline,noclone))
, inline asm, separate compilation, etc.您可以使用
__attribute__((noinline,noclone))
、内联汇编、单独编译等来防止不需要的“优化”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.