试图用MMAP引起分割故障

Trying to cause a segmentation fault with mmap

本文关键字:分割 故障 MMAP      更新时间:2023-10-16

只是为了了解事物的工作方式,我试图用 mmap从内核分配一些内存,然后设置保护位,以使任何内存访问都会导致分段故障,之后我想要试图将保护位设置为使得分割故障不会再次发生。

呼叫mprotect失败,si_addr中的地址是错误的,即使sigaction的Linux MAN页面说siginfo struct的si_addr函数包含导致故障的地址。地址不是在main()函数中分配的地址。该代码在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);
}

请参阅此答案。它解释说, SIGSEGV信号处理程序应更改机器状态,否则重新启动了相同的机器指令,并给出了一些例外,即内核可以翻译成相同信号被发送(在相同的"上下文"中),因此循环。

btw,在信号处理程序内使用C I/O(甚至<stdio.h>)是错误的(因为然后使用非Async信号安全功能)。仔细阅读信号(7)。请注意,信号处理程序被禁止调用许多功能(不是异步信号安全的功能)。

,您对mprotect(2)的呼叫是错误的(并且失败)。大小应该是页面大小的倍数(通常为4K),并且地址也应该是其中的一个倍数(您可能应该使用 page_mapped而不是 siginfo->si_addr,作为地址参数作为 mprotect;另外,您可能会绕 down down <</em> siginfo->si_addr到4K pageize的前一个倍数。当我运行您的程序时(由g++ -O -Wall curious.cc -o curious在Debian/X86-64上与GCC 6&amp; linux内核4.8编译时,它会抱怨:mprotect failedEINVAL错误,perror(3)给出。

给出。

您可以使用Strace(1)更多地了解正在发生的事情。

最后,您的counter应声明为volatile

通过将counterpage_mapped同时称为volatile Global 变量:

volatile int counter;
int*volatile page_mapped;

并且通过在handle_signal内部包含以下代码(在我的系统上,页面大小为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
}

它的行为不同(并且更像您希望),因为mprotect没有失败,并且counter的最终值(在main的末尾)为1(您希望它是)。

您仅从man sigaction中错过了这一点:

如果 sa_siginfo sa_flags 中指定,然后 sa_sigaction (而不是 sa_handler )指定 signum

的信号处理功能

换句话说,如果要指定 sa_sigaction 而不是 sa_handler ,您必须设置该标志,所以

    sigaction_information.sa_flags = SA_SIGINFO;

将在register_signal_handler()中添加。