LD_PRELOAD を試したら、Segv した。

はじめに

 本記事は、Binary Hackの第3章#22に出てくる【LD_PRELOAD】を簡単に試した結果、Segvしたので、その原因追求について、記述したものである。
作成したソースはGithubに置いておく。
https://github.com/tomohikoseven/LD_PRELOAD

LD_PRELOADとは

 バイナリを実行したときに、他のライブラリの読み込み前に、優先的に指定したライブラリを読み込むことができる環境変数である。

Segv 事象とソース

 下記のコマンドを実行したら、Segvした。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ LD_PRELOAD=./libhack.so ./test_main
Segmentation fault

 test_main( test_main.c ) と libhack.so( hack.c )を下記に示す。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ cat main.c
#include

int
main()
{
    puts("hello world!");
    return 0;
}
andre@andre-VirtualBox:~/work/LD_PRELOAD$ cat hack.c
#include
int
puts( const char *str )
{
    printf("hack!!\n");
}

原因:GCC の最適化

 30分くらい原因を探っていたが、答えは、Binary Hack 第3章 #22(LD_PRELOADの前の段落!)に書いてあった。
すなわち、printfは、putsに置き換えることがあると。

これにより、hack.c内のprintfがputsに置き換わって、libhack.so内でputsが再帰的に呼ばれ、スタックオーバーフローでSegvしていた。

スタックオーバーフローの確認

 本当にスタックオーバーフローしているのか、検証した。
検証ソースは以下。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ cat main.c_altstack 
#include
#include
#include
#include

#define ALT_STACK_SIZE (64*1024)

static sigjmp_buf return_point;

static void
signal_handler( int sig )
{
    if( sig == SIGSEGV )
    {
        siglongjmp( return_point, 1 );
    }
}

static void
register_sigaltstack()
{
    stack_t newSS;
    stack_t oldSS;

    newSS.ss_sp     = malloc( ALT_STACK_SIZE );
    newSS.ss_size   = ALT_STACK_SIZE;
    newSS.ss_flags  = 0;

    sigaltstack( &newSS, &oldSS );
}

int
main()
{
    struct sigaction newAct;
    struct sigaction oldAct;

    // 代替スタック作成
    register_sigaltstack();

    // シグナル集合の初期化(空)
    sigemptyset( &newAct.sa_mask );
    // シグナル集合の設定
    sigaddset( &newAct.sa_mask, SIGSEGV );
    // ハンドラ設定
    newAct.sa_handler = signal_handler;
    // シグナル発生時のスタックを代替スタックに設定
    newAct.sa_flags     = SA_ONSTACK;

    // SEGV 時の動作登録
    sigaction( SIGSEGV, &newAct, &oldAct );

    if( sigsetjmp( return_point, 1 ) == 0 )
    {
        puts("hello world!");
    }
    else
    {
        fprintf( stderr, "stack overflow error\n" );
    }

    return 0;
}

このソースをmain.cにリネームし、make そして実行すると、[ stack overflow error ]とメッセージが表示され、確認ができた。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ LD_PRELOAD=./libhack.so ./test_main
stack overflow error

今回使用したGCCのバージョンを下記に示す。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ gcc --version
gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.