ordb.org 復活?

しかし罠入り。
2006年の12月頃に終了したはずだけど、今更になってrelays.ordb.orgが
127.0.0.2を返すようになった模様。ワイルドカードで指定されているようで
何引いてもRBLに載っている扱いに。


引いてみるとこんな感じ。

$ dig 1.0.0.127.relays.ordb.org soa
;; AUTHORITY SECTION:
ordb.org.               10800   IN      SOA     koala.droso.dk. ordb.moensted.dk. 2008032504 14400 7200 604800 2419100
$ dig 1.0.0.127.relays.ordb.org a
;; ANSWER SECTION:
1.0.0.127.relays.ordb.org. 2419200 IN    A       127.0.0.2
$ dig 1.0.0.127.relays.ordb.org txt
;; ANSWER SECTION:
1.0.0.127.relays.ordb.org. 2419200 IN    TXT     "ordb.org was shut down on December 18, 2006. Please remove from your mailserver."
$ dig 0xdeadbeef.relays.ordb.org a
;; ANSWER SECTION:
0xdeadbeef.relays.ordb.org. 2419200 IN  A       127.0.0.2


RBLはただでさえ誤爆してトラブルが多いのに、入れたままで終了しても
一年以上放置とかMTA管理者の怠慢もよいところ。
放置してた人はご愁傷様。

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してる時点でアレというのはあるがソースコード無いし。

glibcのbacktrace

glibc付属のbacktraceでシグナルハンドラ内からbacktraceを呼んだ場合
中途半端な結果しか得られないのは何故かと思い調べてみた。


サンプルコード

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <execinfo.h>

int (*func_ptr)() = (void *)NULL;
int func1();
int func2();
void segv_handler(int signum);

int main(int argc,char ** argv){
        struct sigaction sa;
        memset(&sa,0,sizeof(struct sigaction));
        sigemptyset(&sa.sa_mask);
        sa.sa_handler = segv_handler;
        sigaction(SIGSEGV,&sa,NULL);
        func1();
        return 0;
}

int func1(){
        func2();
        return 0;
}

int func2(){
        func_ptr();
        return 0;
}

void segv_handler(int signum){
        int cnt;
        void **arr = alloca(256 * sizeof(void *));
        cnt = backtrace(arr, 256);
        backtrace_symbols_fd(arr,cnt,1);
        abort();
}


実行結果は以下、func2のスタックが見えていない。(0xc5b420は__kernel_sigreturn)
流れとしてはfunc2内部でcallした後NULLに飛んでSEGVする。

$ ./a.out
./a.out(segv_handler+0x3b)[0x8048799]
[0xc5b420]
./a.out(func1+0xb)[0x8048743]
./a.out(main+0x73)[0x8048727]
/lib/libc.so.6(__libc_start_main+0xdc)[0x395dec]
./a.out[0x8048631]
Aborted (core dumped)

glibcのbacktrace(sysdeps/i386/backtrace.c)あたりを読んだところ、libgccのunwindを使用して
%ebpを拾いチェインを辿っていくような動作をしている感じ。

func2からNULLに飛んだ時点で関数の初期化処理push %ebp, mov %esp,%ebpによるフレーム
操作が行われていないので、call NULLでスタックにリターンアドレスだけ積んだ状態で
シグナルでとばされると思われる。


確かにこれだとfunc2の分が待避されてないので辿れないよなーと思い、gdbに食わせてみたら
以下の通り普通に全部表示された。gdbはフレーム単位で追っていくようだけど、理解不足と
読み切れてないので正確なところは分からず。

#0  0x00c5b402 in __kernel_vsyscall ()
#1  0x003a8c00 in raise () from /lib/libc.so.6
#2  0x003aa451 in abort () from /lib/libc.so.6
#3  0x080487bb in segv_handler ()
#4  <signal handler called>
#5  0x00000000 in ?? ()
#6  0x08048757 in func2 ()
#7  0x08048743 in func1 ()
#8  0x08048727 in main ()

うーん、kernelのsignalによるスタック操作周りがいまいち不明瞭なので後で調べよう。
あと、大体標準で入っているlibgccにunwind関係の関数が入ってるので
libunwind入れなくても色々遊べそうな予感。

ダメ関数潰し

個人的にはシンボルがそのままな限り動くLD_PRELOADで対応したいところだが、
ローカル関数やアドレス指定で直接呼び出されている関数にはPLT経由で制御を奪う方法は
使えない。


本番用の弄るのはアレなので、適当にサンプルをでっち上げて練習してみるテスト。
(ソースあればこんな事しなくても済むというのは以下略)


てきとーなダメサンプル。無駄な物色々入っているけど気にしない。

int main(int argc,char ** argv){
        int a=1, b=2;
        int ret;
        char buff[128];
        ret = func1(a,1,a);
        ret = func2(a,NULL,b);
        printf("%d\n",ret);
        return 0;
}
int func1(int a,int b,int c){
        char buff[256];
        int ret;
        ret = a * 2 + c;
        return ret;
}
int func2(int a,char *b,int c){
        sprintf(b,"%d %d",a,c);
        return 0;
}

対象のdisassemble結果はこんなの。sprintfがNULL書き込みで死亡する。

08048587 <func2>:
 8048587:       55                      push   %ebp
 8048588:       89 e5                   mov    %esp,%ebp
 804858a:       53                      push   %ebx
 804858b:       83 ec 14                sub    $0x14,%esp
 804858e:       e8 34 00 00 00          call   80485c7 <__i686.get_pc_thunk.bx>
 8048593:       81 c3 85 12 00 00       add    $0x1285,%ebx
 8048599:       8b 45 10                mov    0x10(%ebp),%eax
 804859c:       89 44 24 0c             mov    %eax,0xc(%esp)
 80485a0:       8b 45 08                mov    0x8(%ebp),%eax
 80485a3:       89 44 24 08             mov    %eax,0x8(%esp)
 80485a7:       8d 83 0c ef ff ff       lea    0xffffef0c(%ebx),%eax
 80485ad:       89 44 24 04             mov    %eax,0x4(%esp)
 80485b1:       8b 45 0c                mov    0xc(%ebp),%eax
 80485b4:       89 04 24                mov    %eax,(%esp)
 80485b7:       e8 c4 fd ff ff          call   8048380 <sprintf@plt>
 80485bc:       b8 00 00 00 00          mov    $0x0,%eax
 80485c1:       83 c4 14                add    $0x14,%esp
 80485c4:       5b                      pop    %ebx
 80485c5:       5d                      pop    %ebp
 80485c6:       c3                      ret

1. 該当箇所をnopでつぶす
ある意味常套手段。そのまま滑っていく。

 - 80485b7:       e8 c4 fd ff ff          call   8048380 <sprintf@plt>
 + 80485b7:       90                      nop
 + 80485b8:       90                      nop
 + 80485b9:       90                      nop
 + 80485ba:       90                      nop
 + 80485bb:       90                      nop

2. 無理矢理returnする
nopで滑っていってその後の処理に当たるのが嫌な場合はこっち。
スタック弄ってる場合はretする前に戻す必要あるので注意。(この場合は%ebxと%ebp)
戻り値書きたい場合は数バイト確保して%eaxとかにmovして積む。

 - 804858e:       e8 34 00 00 00          call   80485c7 <__i686.get_pc_thunk.bx>
 - 8048593:       81 c3 85 12 00 00       add    $0x1285,%ebx
 + 804858e:       b8 01 00 00 00          mov    $0x1,%eax
 + 8048593:       83 c4 14                add    $0x14,%esp
 + 8048596:       5b                      pop    %ebx
 + 8048597:       5d                      pop    %ebp
 + 8048598:       c3                      ret

全く呼び出される必要がない場合は呼び出しもとを潰せばOK。
callは5byteなので、ちょうど%eaxにmovできる為リターンコードも制御可能。

2008/12/31 問題

新年早々呼び出されて吹いた。
とりあえず書いたヤツはman date or man strftime 100回読めと。

$ date +%G/%m/%d
2008/01/01
$ date --date=yesterday +%G/%m/%d
2008/12/31

今年も先行き不安な様子で。今年中にはどうするか考えないとなー。

Oracle Database 11g ライセンス比較

ふと眺めていたら11gのエディション別機能一覧から
ビットマップインデックスとフィジカルスタンバイに関する項目が
消えているのに気づいた。これは10gではEEでしか使用不可能な機能。


http://www.oracle.co.jp/oracle11g/product_editions.html
http://www.oracle.co.jp/database/function.html


ビットマップインデックスはカーディナリティの低いカラムへの索引が
効率よく処理できる。(追加、更新には向かないが)

フィジカルスタンバイはスタンバイデータベースにログをTNS経由で転送し
自動適用する機能だったはず。これがSE/SE Oneで使えるようになると
かなり楽になる。


単に書き漏れてるだけかも知れないが、この辺が使えると色々便利になりそう。
後でインストールして要確認だな。
むしろExpressEditionもはやく11g版になってくれないかなとか思ったり。