SEGV起こすアドレスを事前チェック

未確保なアドレスやガードページに対して参照、実行しようすると
SEGVで落とされるが、アクセスする前にダメなアドレスかどうか
判定できないかと思い色々試してみた。今回は読み書きのみ対象。
OSはLinuxで2.6カーネル


1. SEGVシグナル無視
POSIXでは無視した場合の動作は未定義とのこと。
SIG_IGNセットは普通にSEGV食らって落とされたのでダミーでハンドラセットしてみた。

int main(int argc,char ** argv){
    struct sigaction sa;
    int *p;
    void *i,*j;
    memset(&sa,0,sizeof(struct sigaction));
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = segv_handler;
    sigaction(SIGSEGV,&sa,NULL);
    j = (void*)0xaaaaaaaa;
    p = (int *)j;
    i = (void*)*p;
    printf("%p\n",i);
    return 0;
}
void segv_handler(int signum){
	return;
}

実行すると無限ループ。gdb経由でステップ実行してみると
シグナルハンドラからの戻りが、SEGV起こした箇所( i = (void*)*p; )な為ループする模様。
まあ、予想通りダメ。


2. mprotectでチェック
未確保アドレスならエラーが返ってくるのでは無いかと思い試してみる。

int main(int argc,char ** argv){
        int ret;
        void *ptr,*aptr;
        void *buff_heap = malloc(77);
        void *buff_stack = calloc(77,1);

        ptr = (void *)0xdeadbeef;
        aptr = (void *)((unsigned long)ptr & (0xFFFFFFFFUL << 12));
        if((ret = mprotect(aptr,256,PROT_READ | PROT_WRITE)) != 0){
                perror("mprotect");
        }
        else{
                printf("PASS\n");
        }

        ptr = (void *)buff_stack;
        aptr = (void *)((unsigned long)ptr & (0xFFFFFFFFUL << 12));
        if((ret = mprotect(aptr,256,PROT_READ | PROT_WRITE)) != 0){
                perror("mprotect");
        }
        else{
                printf("PASS\n");
        }

        ptr = (void *)buff_heap;
        aptr = (void *)((unsigned long)ptr & (0xFFFFFFFFUL << 12));
        if((ret = mprotect(aptr,256,PROT_READ | PROT_WRITE)) != 0){
                perror("mprotect");
        }
        else{
                printf("PASS\n");
        }
	return 0;

結果は以下。

mprotect: Cannot allocate memory
PASS
PASS

一応意図したことはできるが、ページ単位に合わせる必要があるので粒度が荒いのと
成功するとアクセス保護が変更されてしまう。(ptr = main等でも成功するが実行ビット落ちて即死)


3. /procを読む
/proc//maps から該当プロセスのメモリマッピングが読み出せる。
中身はこんなの。詳細はman proc等参照。

address           perms offset  dev   inode      pathname
0035e000-00377000 r-xp 00000000 08:01 1366850    /lib/ld-2.5.so
00377000-00378000 r-xp 00018000 08:01 1366850    /lib/ld-2.5.so
00378000-00379000 rwxp 00019000 08:01 1366850    /lib/ld-2.5.so
00380000-004b7000 r-xp 00000000 08:01 1368813    /lib/libc-2.5.so
004b7000-004b9000 r-xp 00137000 08:01 1368813    /lib/libc-2.5.so
004b9000-004ba000 rwxp 00139000 08:01 1368813    /lib/libc-2.5.so
08047000-080f2000 r-xp 00000000 08:01 2831364    /bin/bash
080f2000-080f7000 rw-p 000ab000 08:01 2831364    /bin/bash
b7f3c000-b7f3d000 rw-p b7f3c000 00:00 0
bfc7e000-bfc94000 rw-p bfc7e000 00:00 0          [stack]

判定する毎に毎回openして読み込む必要があるが、これが一番確実かもしれず。


何かもう少しスマートな方法は無いのだろうか。
そもそもSEGVしてる時点でアレというのはあるがソースコード無いし。