Adi*_*Adi 68 c linux system-calls mprotect signal-handling
我想写一个信号处理程序来捕获SIGSEGV.我使用保护内存块进行读写
char *buffer;
char *p;
char a;
int pagesize = 4096;
mprotect(buffer,pagesize,PROT_NONE)
Run Code Online (Sandbox Code Playgroud)
这可以保护从缓冲区开始的内存大小字节的内存,防止任何读取或写入.
其次,我尝试读取内存:
p = buffer;
a = *p
Run Code Online (Sandbox Code Playgroud)
这将生成一个SIGSEGV,我的处理程序将被调用.到现在为止还挺好.我的问题是,一旦调用处理程序,我想通过这样做来改变内存的访问写入
mprotect(buffer,pagesize,PROT_READ);
Run Code Online (Sandbox Code Playgroud)
并继续正常运行我的代码.我不想退出该功能.在将来写入相同内存时,我想再次捕获信号并修改写入权限,然后记录该事件.
这是代码:
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
char *buffer;
int flag=0;
static void handler(int sig, siginfo_t *si, void *unused)
{
printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr);
printf("Implements the handler only\n");
flag=1;
//exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
char *p; char a;
int pagesize;
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handler;
if (sigaction(SIGSEGV, &sa, NULL) == -1)
handle_error("sigaction");
pagesize=4096;
/* Allocate a buffer aligned on a page boundary;
initial protection is PROT_READ | PROT_WRITE */
buffer = memalign(pagesize, 4 * pagesize);
if (buffer == NULL)
handle_error("memalign");
printf("Start of region: 0x%lx\n", (long) buffer);
printf("Start of region: 0x%lx\n", (long) buffer+pagesize);
printf("Start of region: 0x%lx\n", (long) buffer+2*pagesize);
printf("Start of region: 0x%lx\n", (long) buffer+3*pagesize);
//if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1)
if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1)
handle_error("mprotect");
//for (p = buffer ; ; )
if(flag==0)
{
p = buffer+pagesize/2;
printf("It comes here before reading memory\n");
a = *p; //trying to read the memory
printf("It comes here after reading memory\n");
}
else
{
if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1)
handle_error("mprotect");
a = *p;
printf("Now i can read the memory\n");
}
/* for (p = buffer;p<=buffer+4*pagesize ;p++ )
{
//a = *(p);
*(p) = 'a';
printf("Writing at address %p\n",p);
}*/
printf("Loop completed\n"); /* Should never happen */
exit(EXIT_SUCCESS);
}
Run Code Online (Sandbox Code Playgroud)
问题是只有信号处理程序运行,并且在捕获信号后我无法返回主函数.
Chr*_*odd 68
当你的信号处理程序返回时(假设它没有调用exit或longjmp或阻止它实际返回的东西),代码将在信号发生时继续,重新执行相同的指令.由于此时内存保护尚未更改,它只会再次抛出信号,并且您将在无限循环中返回信号处理程序.
因此,要使其工作,您必须在信号处理程序中调用mprotect.不幸的是,正如Steven Schansker所说,mprotect不是异步安全的,所以你不能安全地从信号处理程序中调用它.所以,就POSIX而言,你被搞砸了.
幸运的是,在大多数实现(据我所知的所有现代UNIX和Linux变体)中,mprotect是一个系统调用,因此可以安全地从信号处理程序中调用,因此您可以完成所需的大部分操作.问题是,如果您想在读取后更改保护,则必须在读取后在主程序中执行此操作.
另一种可能性是对信号处理程序的第三个参数执行某些操作,该参数指向OS和特定于拱的结构,该结构包含有关信号发生位置的信息.在Linux上,这是一个ucontext结构,它包含有关$ PC地址和信号发生的其他寄存器内容的机器特定信息.如果你修改它,你可以改变信号处理程序返回的位置,这样你就可以将$ PC更改为故障指令之后,这样它就不会在处理程序返回后重新执行.这是非常棘手的(和非便携式).
编辑
所述ucontext结构定义<ucontext.h>.在该ucontext字段中uc_mcontext包含机器上下文,并且在其中,该数组gregs包含通用寄存器上下文.所以在你的信号处理程序:
ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];
Run Code Online (Sandbox Code Playgroud)
会给你发生异常的电脑.你可以阅读它来弄清楚出现故障的指令,并做一些与众不同的事情.
就信号处理程序中调用mprotect的可移植性而言,任何遵循SVID规范或BSD4规范的系统都应该是安全的 - 它们允许在信号中调用任何系统调用(手册第2部分中的任何内容)处理程序.
Ste*_*ker 25
你陷入了所有人第一次尝试处理信号时所做的陷阱.陷阱?认为你实际上可以对信号处理程序做任何有用的事情.从信号处理程序,您只能调用异步和可重入安全的库调用.
请参阅此CERT建议,了解为何以及安全的POSIX函数列表.
请注意,您已经调用的printf()不在该列表中.
也不是mprotect.你不能从信号处理程序中调用它.它可能有用,但我可以保证你会遇到问题.对信号处理程序要非常小心,他们很难做到正确!
编辑
由于我现在已经成为一个可移植性的douchebag,我会指出你也不应该在没有采取适当预防措施的情况下写入共享(即全局)变量.
Ben*_*igt 12
您可以从linux上的SIGSEGV恢复.您还可以从Windows上的分段错误中恢复(您将看到结构化异常而不是信号).但POSIX标准并不保证恢复,因此您的代码将非常不便携.
看看libsigsegv.
你不应该从信号处理程序返回,因为行为是未定义的。而是使用 longjmp 跳出它。
只有在异步信号安全函数中生成信号时,这才可以。否则,如果程序调用另一个异步信号不安全函数,则行为未定义。因此,信号处理程序应该只在需要之前立即建立,并尽快解除。
事实上,我知道 SIGSEGV 处理程序的用途很少:
最后,请注意,触发 SIGSEGV 的任何操作都可能是 UB,因为这是访问无效内存。但是,如果信号是 SIGFPE,则情况并非如此。