0

Just to understand how things work, I am trying to allocate some memory from the kernel with mmap and then set the protection bits such that any memory access causes a segmentation fault, after that I want to try and set the protection bits to be such that the segmentation fault does not happen again.

The call to mprotect fails, and the address in si_addr is wrong even though the linux man page for sigaction says that the siginfo struct's si_addr function contains the address that causes the fault. And the address is not the address that was allocated in the main() function. The code works fine on a mac

#define _XOPEN_SOURCE

#include <iostream>
#include <signal.h>
#include <ucontext.h>
#include <sys/mman.h>
#include <string.h>
#include <cstdlib>

using std::cout;
using std::cerr;
using std::endl;

void handle_signal(int signal_number, siginfo_t* signal_info, void* context);
void register_signal_handler();

int counter = 0;

int main() {
    register_signal_handler();
    int* page_mapped = (int*) mmap(nullptr, 100, PROT_NONE,
            MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (page_mapped == MAP_FAILED) {
        cerr << "mmap failed" << endl;
    }
    cout << "page mapped is " << reinterpret_cast<uintptr_t>(page_mapped)
         << endl;

    // cause the segmentation fault
    cout << *page_mapped << endl;

    return 0;
}

void handle_signal(int, siginfo_t* siginfo, void*) {
    cout << "Handled a segmentation fault" << endl;
    cout << "The segmentation fault was caused by the address "
         << reinterpret_cast<uintptr_t>(siginfo->si_addr) << endl;
    if (mprotect(siginfo->si_addr, 100, PROT_READ | PROT_WRITE) == -1) {
        cerr << "mprotect failed" << endl;
        exit(1);
    }

    // stop an infinite loop
    ++counter;
    if (counter == 3) {
        cerr << "Counter got to 3, probably going into an infinite loop.. "
            "stopping" << endl;
        exit(1);
    }
}

void register_signal_handler() {
    struct sigaction sigaction_information;
    memset(&sigaction_information, 0, sizeof(struct sigaction));
    sigaction_information.sa_sigaction = &handle_signal;
    sigaction(SIGSEGV, &sigaction_information, nullptr);
}
Curious
  • 20,870
  • 8
  • 61
  • 146

2 Answers2

0

See this answer. It explains that a SIGSEGV signal handler should change the machine state, otherwise the same machine instruction is restarted and gives some exception that the kernel translates into the same signal being sent (in the same "context"), hence the loop.

BTW, using C++ I/O (or even <stdio.h>) inside a signal handler is wrong (because you are then using non-async signal safe functions). Read carefully signal(7). Be aware that a signal handler is forbidden to call many functions (those which are not the async-signal-safe ones).

And your call to mprotect(2) is wrong (and failing). The size should be a multiple of the page size (typically 4K) and the address should also be a multiple of it (you probably should use page_mapped, not siginfo->si_addr, as the address argument to mprotect; alternatively you might round down siginfo->si_addr to the previous multiple of the 4K pagesize). When I run your program (compiled by g++ -O -Wall curious.cc -o curious on Debian/x86-64 with GCC 6 & linux kernel 4.8) it complains: mprotect failed (with EINVAL error, given by perror(3)).

You could use strace(1) to understand more exactly what is happening.

At last, your counter should be declared as volatile.

By declaring both counter and page_mapped as volatile global variables:

volatile int counter;
int*volatile page_mapped;

and by having inside handle_signal the following code (on my system, page size is 4K):

if (mprotect(page_mapped, 4096, PROT_READ | PROT_WRITE) == -1) {
  /// this is still wrong in theory, 
  /// .... since we are using non-async signal safe functions
  perror("mprotect");
  exit(EXIT_FAILURE);
  /// but in practice mprotect is successful
}

it behaves differently (and a bit more like you wish) because mprotect is not failing and the final value of counter (at end of main) is 1 (as you want it to be).

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    *a SIGSEGV signal handler should change the machine state* Why is this necessary if the signal handler removes the protection from the page that caused the segfault (and therefore restarting the same machine instruction should not reraise the same signal)? – Leon Dec 04 '16 at 08:20
  • Changing the protection is changing the machine state (e.g. in MMU). – Basile Starynkevitch Dec 04 '16 at 08:37
  • So how do I get mprotect to properly protect the address that caused the segmentation fault? – Curious Dec 04 '16 at 16:03
  • This code seems to work fine on my mac but no longer works on a linux machine – Curious Dec 04 '16 at 16:07
0

You missed this from man sigaction only:

If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of sa_handler) specifies the signal-handling function for signum.

In other words, if you want to specify sa_sigaction instead of sa_handler, you must set that flag, so

    sigaction_information.sa_flags = SA_SIGINFO;

is to be added in register_signal_handler().

Armali
  • 18,255
  • 14
  • 57
  • 171