firebush
firebush

Reputation: 5850

How to mprotect an object

My problem

I have a singleton whose memory is being corrupted by an unknown corruptor. Something is overwriting the memory for the singleton, and hundreds of bytes around it, with value 0. After the object is constructed via new, it is read-only for the lifetime of the application.

My goal

I'd like to capture the corruptor at the time of the corruption. I'd like to mprotect as read-only the memory of the object after construction. That way later when the corruption happens the system will segmentation fault at the time of corruption.

My question

It looks like mprotect is granular to the page level. How would I "over allocate" for the singleton instance a full page for the object (it is far smaller than 4k, the standard page size) and then mprotect that page?

Upvotes: 0

Views: 767

Answers (3)

Swift - Friday Pie
Swift - Friday Pie

Reputation: 14613

Practically every debugger got a tool to watch memory for change (in gdb command literally called watch)

Instead of trying to work around issue, you have to find source, corruption due to out-of-bound write may touch something else, even mory vital and hard to detect.

To answer your question, C++ got a placement new expression, an overload for new operator which allows to allocate object at particular address of pre-allocated memory

Upvotes: 0

firebush
firebush

Reputation: 5850

Thank you @Brian. Here's my minimal example of using mmap as he suggests, followed by placement new to use that memory and then mprotect to make it read-only:

#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;
}

When I compile and run this, I get the crash that I desire:

g++ -g -Wall -Werror -std=c++17 test.cc -o test
./test
10
runit: line 4: 18029 Bus error: 10           ./test

The debugger points right to the write:

$ 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.

Upvotes: 2

Brian Bi
Brian Bi

Reputation: 119382

You can use anonymous mmap to allocate a full page for the singleton, then construct the object into it with placement new.

Upvotes: 2

Related Questions