我的问题
我有一个单身汉,他的内存被一个未知的腐败者破坏了。有东西正在覆盖singleton的内存,以及它周围的数百个字节,值为0。通过new构造对象后,它在应用程序的生存期内是只读的。
我的目标
我想抓住腐败发生时的腐败分子。我想在构造后将对象的内存保护为只读。这样,当腐败发生时,系统将在腐败时出现分段故障。
我的问题
看起来mprotect在页面级别上是细粒度的。如何为singleton实例"过度分配"对象的完整页面(它远小于标准页面大小4k(,然后对该页面进行保护?
您可以使用匿名mmap
为singleton分配一个完整的页面,然后将对象构造到其中并放置new。
谢谢@Brian。下面是我使用mmap的最小例子,正如他所建议的那样,然后放置新的内存来使用该内存,然后mprotect使其只读:
#include <iostream>
#include <sys/mman.h>
#include <unistd.h>
using namespace std;
struct MySingleton
{
int some_value;
static MySingleton* init(int a_value)
{
// Get the system's page size.
const auto pagesize = getpagesize();
// mmap one page worth of memory, initially writable.
void* map = mmap(0, pagesize, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0);
// Use placement new using that memory.
MySingleton::_instance = new(map) MySingleton(a_value);
// Now make that memory read-only.
mprotect(map, pagesize, PROT_READ);
return MySingleton::_instance;
}
static MySingleton* instance()
{
return _instance;
}
private:
MySingleton(int a_value)
: some_value{a_value}
{
}
static MySingleton *_instance;
};
MySingleton *MySingleton::_instance = nullptr;
int
main(int argc, char* argv[])
{
MySingleton *instance = MySingleton::init(10);
// Read is OK.
cout << instance->some_value << endl;
// This should crash;
instance->some_value = 5;
cout << instance->some_value << endl;
return 0;
}
当我编译并运行这个时,我会得到我想要的崩溃:
g++ -g -Wall -Werror -std=c++17 test.cc -o test
./test
10
runit: line 4: 18029 Bus error: 10 ./test
调试器指向写入:
$ lldb test
(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb) run
Process 18056 launched: '<snip>' (x86_64)
10
Process 18056 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x10011d000)
frame #0: 0x0000000100000c50 test`main(argc=1, argv=0x00007ffeefbff9a8) at test.cc:50:26
47 cout << instance->some_value << endl;
48
49 // This should crash;
-> 50 instance->some_value = 5;
51 cout << instance->some_value << endl;
52
53 return 0;
Target 0: (test) stopped.
实际上,每个调试器都有一个工具来监视内存的变化(在gdb命令中,字面上称为watch
(
与其试图解决这个问题,你们必须找到来源,因为越界写入导致的腐败可能会触及其他东西,甚至是至关重要且难以检测的病态。
为了回答您的问题,C++得到了一个放置新表达式,一个新运算符的重载,它允许在预先分配的内存的特定地址分配对象