SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

signalについて

signalについて(中篇)

シグナルの実装


4. シグナルの実装(オーバーフロー)

4.6 オーバーフロー

 プログラムを書き始めた当初は、しょっちゅうバッファオーバーフローを起こすプログラムを忍ばせたものです。C言語はメモリ関係の管理がプログラマに依存しているので、大規模な業務アプリをCで作る場合のメモリ管理の堅牢さは、プロジェクトの成否を占うものでした。つまりC言語はバッファオーバーフローを生み出しやすい言語と言っても過言ではなく、バグが潜んでいるとそこを衝かれて権限を利用されてしまいます。

 実際にオーバーフローが発生するとSIGSEGVシグナルが発生します。このシグナルが発生するとデフォルトではプログラムが終了し、コアファイルが作られます。よってSIGSEGVシグナルを捕捉すればよいのですが、実際はそう簡単にいかない落とし穴があります。

 なお、オーバーフローには下記の2種類があります。

  1. バッファオーバーフロー:予約されたメモリ領域を超えて書き込みする事。
  2. スタックオーバーフロー:関数が実行する際に割り当てられたメモリ領域を超えてメモリを使用する事。

 OSがこれらの異常を検知すると、速やかにプロセスに対してSIGSEGVシグナルが発行されます。

4.6.1 バッファオーバーフロー

 バッファオーバーフローが発生するとSIGSEGVシグナルが発行されるので、それを捕捉するハンドラを用意すればよいです。ただし、以下の問題点があります。

  1. SEGVが発生した際、それがバッファオーバーフローによるものなのか判定が必要。
  2. 速やかに終了する際はexitすればよいが、復帰する場合はどうするか(今までのプログラムはハンドラ内でexitしていません)。

 問題点1については、sa_handlerを使用する限り、シグナルイベントしか得られませんから対応できません。さらに、sa_sigactionを使用し他のパラメータからバッファオーバーフローを判定しようとしても、判定できる情報がないので、やはりできません。

 問題点2についてですが、そもそもSIGSEGVやSIGBUS等はプログラム内のバグから発生するシグナルです。そのまま終了する分には問題ないのですが、終了しないとオーバーフローを起こした場所に復帰する事になり、ハンドラとの無限ループに陥ります。復帰方法については後述する4.9章で考えることにして、ここでは復帰は考えず、ハンドラ内で終了する仕様にします。

 以上から、バッファオーバーフローを検知するサンプルは下記のようになります。

signal_test_bufover.c
/*
gcc signal_test_bufover.c -o signal_test_bufover -W -Wall -g
*/
#include <stdio.h> #include <stdlib.h> #include <signal.h> #define BUF_SIZE 16 void signal_handler( int signum ); int main( ) { char * wrk = malloc( BUF_SIZE ); struct sigaction sa; sa.sa_handler = signal_handler; sigaction( SIGSEGV, &sa, 0 ); while( 1 ) { *wrk ++ = 0xcc; } return 0; } void signal_handler( int signum ) { fprintf( stdout, "sig : [%d]\n", signum ); exit( 1 ); }

 尚、SIGSEGVは、sa_sigaction を使用する事で、メモリ使用不正が発生した時のメモリアドレスを取得できます。

 下記のプログラム signal_segv_addr.c は不正アドレスに対するオペレーション違反。更に下記のプログラム signal_segv_addr_2.c はメモリに対するパーミッション違反です。

signal_segv_addr.c
/*
gcc signal_segv_addr.c -o signal_segv_addr -W -Wall -Werror
*/
#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/mman.h> void handler( int signo, siginfo_t * siginfo,void * dummy ); int main( ) { char * p = 0; struct sigaction sa; sa.sa_sigaction = handler; sa.sa_flags = SA_SIGINFO; sigemptyset( &sa.sa_mask ); sigaction( SIGSEGV, &sa, 0 ); p = ( char * )0x1234; *p = 0; return 0; } void handler( int signo, siginfo_t * siginfo,void * dummy ) { ( void )signo; ( void )dummy; fprintf( stdout, "addr = %pH, code = %s\n", siginfo -> si_addr, ( siginfo -> si_code == SEGV_MAPERR ) ? "SEGV_MAPERR" : "SEGV_ACCERR" ); exit( 1 ); }
signal_segv_addr_2.c
/*
gcc signal_segv_addr_2.c -o signal_segv_addr_2 -W -Wall -Werror
*/
#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/mman.h> #include <string.h> #include <errno.h> #include <unistd.h> void handler( int signo, siginfo_t * siginfo,void * dummy ); int main( ) { char * p = 0; struct sigaction sa; int pagesize = getpagesize( ); sa.sa_sigaction = handler; sa.sa_flags = SA_SIGINFO; sigemptyset( &sa.sa_mask ); sigaction( SIGSEGV, &sa, 0 ); p = malloc( 1024 + pagesize - 1 ); p = ( char * )(( int )( p + pagesize - 1 ) & ~( pagesize - 1 )); mprotect( p, 1024, PROT_READ ); /* read only */ fprintf( stdout, "addr = %pH\n", p ); p[0x123] = 'a'; /* write!! */ return 0; } void handler( int signo, siginfo_t * siginfo,void * dummy ) { ( void )signo; ( void )dummy; fprintf( stdout, "addr = %pH, code = %s\n", siginfo -> si_addr, ( siginfo -> si_code == SEGV_MAPERR ) ? "SEGV_MAPERR" : "SEGV_ACCERR" ); exit( 1 ); }

4.6.2 スタックオーバーフロー

 基本的にバッファオーバーフローと同じ実装なので対応は難しくないのですが、スタックオーバーフロー独特の厄介な問題があります。

 一般的にスタックオーバーフローが発生する要因として、再帰呼び出しが多いと思います。下記プログラムを実行してみてください。

signal_test_stackover.c
/*
gcc signal_test_stackover.c -o signal_test_stackover -W -Wall -g
*/
#include <stdio.h> #include <stdlib.h> #include <signal.h> void stackover_func( ); void signal_handler( int signum ); int main( ) { struct sigaction sa; sa.sa_handler = signal_handler; sigaction( SIGSEGV, &sa, 0 ); stackover_func( ); return 0; } void stackover_func( ) { stackover_func( ); } void signal_handler( int signum ) { fprintf( stdout, "sig : [%d]\n", signum ); exit( 1 ); }

 実行すると分かると思いますが、ハンドラ内のプリント文が出力されません。どうやらハンドラに入っていないようですが、異常終了メッセージからSIGSEGVシグナルを受け取っていることは確認できます。

 これはシグナルハンドラとプロセスのスタックの関係にあります。スタックオーバーフローはその名の通り、プロセスに割り当てられているスタックを食いつぶしたことによって発生します。一方シグナルハンドラも、その処理自体はプロセスのスタックに積まれていますが、上記の通りスタックが食いつぶされた状況ではシグナルハンドラを実行することができません、確保する場所がないためです。結果、シグナルが到着してもハンドラが呼ばれないので、デフォルトの動きとして終了します。

 このような場合の対処として代替シグナルスタックを使い、シグナル用に代わりのメモリ領域を用意します。実際にシグナルが発行されると、そのメモリ上でハンドラなどが実行されます。

int sigaltstack(const stack_t *ss, stack_t *oss);

 詳細はman sigaltstackで見てもらいたいのですが、要はシグナルスタック用のメモリ領域をヒープから取得する事で、スタックとは別の位置で実行するようにするものです。この機能を使用する事で、スタックオーバーフローが発生した時もシグナルハンドラを実行できるようになります。

signal_test_sigstack.c
/*
gcc signal_test_sigstack.c -o signal_test_sigstack -W -Wall -g
*/
#include <stdio.h> #include <stdlib.h> #include <signal.h> void stackover_func ( ); void signal_handler ( int signum ); int main( ) { stack_t ss; struct sigaction sa; ss.ss_sp = malloc( SIGSTKSZ ); ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; sigaltstack( &ss, 0 ); sa.sa_handler = signal_handler; sa.sa_flags = SA_ONSTACK; sigaction( SIGSEGV, &sa, 0 ); stackover_func( ); return 0; } void stackover_func( ) { stackover_func( ); } void signal_handler( int signum ) { fprintf( stdout, "sig : [%d]\n", signum ); exit( 1 ); }

次のページ
4. シグナルの実装(SIGHUP)

修正履歴

この記事は参考になりましたか?

signalについて連載記事一覧

もっと読む

この記事の著者

赤松 エイト(エイト)

(株)DTSに勤てます。WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。mixiもやってまして、こちらは子育てとか日々の日記メインです。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1590 2008/08/20 15:56

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー