http://kernhack.hatenablog.com/entry/20100130/1264858832
昨日久々に,double freeのバグに當たったので,それのデバッグ方法をメモ書き.
こんな感じで,2重にfreeするバグを持ったプログラムがあったとして,
サンプルなので,原因は簡単に分かりますけど
[masami@moonlight:~]% cat double_free.c#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#include <unistd.h>char *ptr;void handler(int signo) { printf("%s: %s\n", __FUNCTION__, ptr); free(ptr); }int main(int argc, char **argv) { struct sigaction act; memset(&act, 0, sizeof(act)); ptr = malloc(32); if (!ptr) { perror("malloc"); exit(-1); } if (argc == 1) strcpy(ptr, "Hello World"); else strncpy(ptr, argv[1], 31); act.sa_handler = &handler; sigaction(SIGALRM, &act, NULL); alarm(5); sleep(5); free(ptr); return 0; }
これを実行すると,こんな感じでglibcが2重freeを検出してくれます.
[masami@moonlight:~]% ./a.out handler: Hello World *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000f34010 *** ======= Backtrace: ========= /lib/libc.so.6[0x7fca40d3ad56] /lib/libc.so.6(cfree+0x6c)[0x7fca40d3f9bc] ./a.out[0x400916] /lib/libc.so.6(__libc_start_main+0xfd)[0x7fca40ce8abd] ./a.out[0x400729] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 08:01 1818 /home/masami/a.out 00600000-00601000 rw-p 00000000 08:01 1818 /home/masami/a.out 00f34000-00f55000 rw-p 00000000 00:00 0 [heap] 7fca3c000000-7fca3c021000 rw-p 00000000 00:00 0 7fca3c021000-7fca40000000 ---p 00000000 00:00 0 7fca40ab4000-7fca40aca000 r-xp 00000000 08:01 131765 /lib/libgcc_s.so.1 7fca40aca000-7fca40cc9000 ---p 00016000 08:01 131765 /lib/libgcc_s.so.1 7fca40cc9000-7fca40cca000 rw-p 00015000 08:01 131765 /lib/libgcc_s.so.1 7fca40cca000-7fca40e14000 r-xp 00000000 08:01 138066 /lib/libc-2.10.2.so 7fca40e14000-7fca41014000 ---p 0014a000 08:01 138066 /lib/libc-2.10.2.so 7fca41014000-7fca41018000 r--p 0014a000 08:01 138066 /lib/libc-2.10.2.so 7fca41018000-7fca41019000 rw-p 0014e000 08:01 138066 /lib/libc-2.10.2.so 7fca41019000-7fca4101e000 rw-p 00000000 00:00 0 7fca4101e000-7fca4103b000 r-xp 00000000 08:01 131120 /lib/ld-2.10.2.so 7fca41225000-7fca41227000 rw-p 00000000 00:00 0 7fca41236000-7fca4123a000 rw-p 00000000 00:00 0 7fca4123a000-7fca4123b000 r--p 0001c000 08:01 131120 /lib/ld-2.10.2.so 7fca4123b000-7fca4123c000 rw-p 0001d000 08:01 131120 /lib/ld-2.10.2.so 7fff87315000-7fff8732a000 rw-p 00000000 00:00 0 [stack] 7fff8737d000-7fff8737e000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] zsh: abort (core dumped) ./a.out
この出力から,どこで2回目のfreeが呼ばれたかを探すには,Backtraceを見ます.
======= Backtrace: ========= /lib/libc.so.6[0x7fca40d3ad56] /lib/libc.so.6(cfree+0x6c)[0x7fca40d3f9bc] ./a.out[0x400916] /lib/libc.so.6(__libc_start_main+0xfd)[0x7fca40ce8abd] ./a.out[0x400729]
トレースは上記なので,glibcに入る前に呼ばれたコードは「./a.out[0x400916]」です.
masami@moonlight:~]% objdump -D a.out | less 000000000040081e <main>: 40081e: 55 push %rbp 40081f: 48 89 e5 mov %rsp,%rbp 400822: 48 81 ec b0 00 00 00 sub $0xb0,%rsp 中略 4008f3: bf 05 00 00 00 mov $0x5,%edi 4008f8: e8 d3 fd ff ff callq 4006d0 <alarm@plt> 4008fd: bf 05 00 00 00 mov $0x5,%edi 400902: e8 99 fd ff ff callq 4006a0 <sleep@plt> 400907: 48 8b 05 5a 04 20 00 mov 0x20045a(%rip),%rax # 600d68 <ptr> 40090e: 48 89 c7 mov %rax,%rdi 400911: e8 7a fd ff ff callq 400690 <free@plt> 400916: b8 00 00 00 00 mov $0x0,%eax 40091b: c9 leaveq 40091c: c3 retq
そうすると,直前の0x400911でfreeを呼んでいる箇所があるので,2回目のfreeはこいつだなとわかります.
400911: e8 7a fd ff ff callq 400690 <free@plt>
サンプルコードの場合は,シグナルハンドラ內でfree()するか,main()の最後にfree()するかで解決するのですが,
そうは簡単にいかない場合もあると思うので,一番簡単な解決策はfreeしたらNULLを代入しておくのが良いんじゃないかと.
free(NULL)は何もしないので(manにも書かれてます),比較的簡単安全かと.
昨日當たったバグは,ちょっと厄介で,全てのfree()呼び出し後にNULLを入れたんですが,
バッファを獲得する処理でfree済みのアドレスが取得されて,それをfreeしたために2重freeで落ちてる感じでした.
ちゃんと調べると面倒そうだったので(仕事でもないし),free()を呼ばないという手抜きな方法を使っちゃいましたけどw