0

我的问题

我有一个单身人士,他的记忆被未知的腐败者破坏了。有些东西正在覆盖单例的内存,以及它周围的数百个字节,值为 0。通过 new 构造对象后,它在应用程序的生命周期内是只读的。

我的目标

我想在腐败时抓住腐败者。我想在构造后将对象的内存保护为只读。这样以后当损坏发生时,系统将在损坏时出现分段错误。

我的问题

看起来 mprotect 是细化到页面级别的。我如何为单例实例“过度分配”对象的完整页面(它远小于 4k,标准页面大小)然后 mprotect 该页面?

4

3 回答 3

1

您可以使用anonymousmmap为单例分配一个完整的页面,然后使用placement new 将对象构造到其中。

于 2020-04-01T21:23:58.327 回答
1

谢谢@Brian。这是我按照他的建议使用 mmap 的最小示例,然后是放置 new 以使用该内存,然后 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.
于 2020-04-01T22:06:17.400 回答
0

几乎每个调试器都有一个工具来监视内存的变化(在 gdb 命令中,字面上称为watch

而不是试图解决问题,你必须找到源头,由于越界写入导致的损坏可能会触及其他东西,甚至更重要且难以检测。

为了回答您的问题,C++ 得到了一个placement new 表达式,它是new 运算符的重载,它允许在预分配内存的特定地址分配对象

于 2020-04-01T22:21:55.277 回答