linuxカーネルkmemleak機能を使うとにメモリリークの発見に役立ちますが、実際にどこでなのかを探すめも。
結論から先にいうとCONFIG_DEBUG_INFO=yのカーネルを使ってるならobjdumpは-Sオプションを使おうと言うことです〆(.. )カリカリッ!!
仮想環境で動かしているFedora20 + 3.12-rc7でこれ位の数のリークらしきものがkmemleakによってレポートされてます。
(agetty) :8 NetworkManager :1 agetty :312 kworker/0:2 :1617 kworker/1:1 :2386 kworker/1:2 :1035 plymouthd :18251 systemd :5939 systemd-udevd :344
この中でsystemd関連のものを見てみるとこのような感じです。
60991 unreferenced object 0xffff8800d680afd0 (size 8): 60992 comm "systemd", pid 1, jiffies 4294669592 (age 133.340s) 60993 hex dump (first 8 bytes): 60994 87 05 00 00 00 88 ff ff ........ 60995 backtrace: 60996 [<ffffffff8163fb1e>] kmemleak_alloc+0x4e/0xb0 60997 [<ffffffff81184a5a>] kmem_cache_alloc_trace+0xca/0x220 60998 [<ffffffff8129fe1d>] policydb_read+0xb5d/0x1220 60999 [<ffffffff812a49b9>] security_load_policy+0x69/0x5d0 61000 [<ffffffff81297e05>] sel_write_load+0xa5/0x720 61001 [<ffffffff8119c47d>] vfs_write+0xbd/0x1e0 61002 [<ffffffff8119ceb9>] SyS_write+0x49/0xa0 61003 [<ffffffff81656729>] system_call_fastpath+0x16/0x1b 61004 [<ffffffffffffffff>] 0xffffffffffffffff 61005 unreferenced object 0xffff8800d6824680 (size 16): 61006 comm "systemd", pid 1, jiffies 4294669592 (age 133.352s) 61007 hex dump (first 16 bytes): 61008 2e 58 61 75 74 68 6f 72 69 74 79 00 ff ff ff ff .Xauthority..... 61009 backtrace: 61010 [<ffffffff8163fb1e>] kmemleak_alloc+0x4e/0xb0 61011 [<ffffffff811854cb>] __kmalloc+0xdb/0x250 61012 [<ffffffff8129fe5c>] policydb_read+0xb9c/0x1220 61013 [<ffffffff812a49b9>] security_load_policy+0x69/0x5d0 61014 [<ffffffff81297e05>] sel_write_load+0xa5/0x720 61015 [<ffffffff8119c47d>] vfs_write+0xbd/0x1e0 61016 [<ffffffff8119ceb9>] SyS_write+0x49/0xa0 61017 [<ffffffff81656729>] system_call_fastpath+0x16/0x1b 61018 [<ffffffffffffffff>] 0xffffffffffffffff 61019 unreferenced object 0xffff8800d687f200 (size 32): 61020 comm "systemd", pid 1, jiffies 4294669592 (age 133.352s) 61021 hex dump (first 32 bytes): 61022 cc 0c 00 00 4f 03 00 00 06 00 00 00 00 00 00 00 ....O........... 61023 90 46 82 d6 00 88 ff ff 00 00 00 00 00 00 00 00 .F.............. 61024 backtrace: 61025 [<ffffffff8163fb1e>] kmemleak_alloc+0x4e/0xb0 61026 [<ffffffff81184a5a>] kmem_cache_alloc_trace+0xca/0x220 61027 [<ffffffff8129fdfb>] policydb_read+0xb3b/0x1220 61028 [<ffffffff812a49b9>] security_load_policy+0x69/0x5d0 61029 [<ffffffff81297e05>] sel_write_load+0xa5/0x720 61030 [<ffffffff8119c47d>] vfs_write+0xbd/0x1e0 61031 [<ffffffff8119ceb9>] SyS_write+0x49/0xa0 61032 [<ffffffff81656729>] system_call_fastpath+0x16/0x1b 61033 [<ffffffffffffffff>] 0xffffffffffffffff
systemd絡みの場合はpolicydb_read()までのコールフローは一緒で、policydb_read()の中でメモリ確保したものがリークしているというような感じです。
これが何パターン位あるのかというと以下の3パターンでした。
masami@kerntest:~$ grep policydb_read kmem.txt | sort | uniq [<ffffffff8129fdfb>] policydb_read+0xb3b/0x1220 [<ffffffff8129fe1d>] policydb_read+0xb5d/0x1220 [<ffffffff8129fe5c>] policydb_read+0xb9c/0x1220
例えば、リークしているアドレスはffffffff8129fdfb、policydb_read+0xb3bのところとまではkmemleakによってレポートされているのでvmlinuxをobjdump -d /path/to/vmlinuxで見ると、
ffffffff8129fda8: e8 63 cb ff ff callq ffffffff8129c910 <next_entry> ffffffff8129fdad: 85 c0 test %eax,%eax ffffffff8129fdaf: 0f 85 70 f9 ff ff jne ffffffff8129f725 <policydb_read+0x465> ffffffff8129fdb5: 8b 44 24 58 mov 0x58(%rsp),%eax ffffffff8129fdb9: 45 89 ef mov %r13d,%r15d ffffffff8129fdbc: c7 44 24 20 00 00 00 movl $0x0,0x20(%rsp) ffffffff8129fdc3: 00 ffffffff8129fdc4: 49 89 dd mov %rbx,%r13 ffffffff8129fdc7: 89 44 24 18 mov %eax,0x18(%rsp) ffffffff8129fdcb: 48 8d 83 08 01 00 00 lea 0x108(%rbx),%rax ffffffff8129fdd2: 48 89 44 24 08 mov %rax,0x8(%rsp) ffffffff8129fdd7: 8b 44 24 18 mov 0x18(%rsp),%eax ffffffff8129fddb: 39 44 24 20 cmp %eax,0x20(%rsp) ffffffff8129fddf: 0f 84 7b 03 00 00 je ffffffff812a0160 <policydb_read+0xea0> ffffffff8129fde5: 48 8b 3d 7c d1 d4 00 mov 0xd4d17c(%rip),%rdi # ffffffff81fecf68 <kmalloc_caches+0x28> ffffffff8129fdec: ba 18 00 00 00 mov $0x18,%edx ffffffff8129fdf1: be d0 80 00 00 mov $0x80d0,%esi ffffffff8129fdf6: e8 95 4b ee ff callq ffffffff81184990 <kmem_cache_alloc_trace> ffffffff8129fdfb: 48 85 c0 test %rax,%rax ffffffff8129fdfe: 49 89 c6 mov %rax,%r14 ffffffff8129fe01: 0f 84 40 03 00 00 je ffffffff812a0147 <policydb_read+0xe87> ffffffff8129fe07: 48 8b 3d 4a d1 d4 00 mov 0xd4d14a(%rip),%rdi # ffffffff81fecf58 <kmalloc_caches+0x18> ffffffff8129fe0e: ba 04 00 00 00 mov $0x4,%edx ffffffff8129fe13: be d0 00 00 00 mov $0xd0,%esi ffffffff8129fe18: e8 73 4b ee ff callq ffffffff81184990 <kmem_cache_alloc_trace>
となりますが、このアセンブラコードとソースコード上のどこかってのを一致させるのが結構面倒ですね。
目印になりそうなのはnext_entry()の呼び出しの後にメモリ確保が呼ばれているというところでしょうか。
policydb_read()はsecurity/selinux/ss/policydb.cにある関数です。
policydb_rad()内では以下の部分と
2371 rc = next_entry(buf, fp, sizeof(u32)); 2372 if (rc) 2373 goto bad; 2374 nel = le32_to_cpu(buf[0]); 2375 ltr = NULL; 2376 for (i = 0; i < nel; i++) { 2377 rc = -ENOMEM; 2378 tr = kzalloc(sizeof(*tr), GFP_KERNEL); 2379 if (!tr) 2380 goto bad; 2381 if (ltr) 2382 ltr->next = tr; 2383 else 2384 p->role_tr = tr; 2385 rc = next_entry(buf, fp, sizeof(u32)*3); 2386 if (rc) 2387 goto bad;
この2ヶ所になると思います。
2409 rc = next_entry(buf, fp, sizeof(u32)); 2410 if (rc) 2411 goto bad; 2412 nel = le32_to_cpu(buf[0]); 2413 lra = NULL; 2414 for (i = 0; i < nel; i++) { 2415 rc = -ENOMEM; 2416 ra = kzalloc(sizeof(*ra), GFP_KERNEL); 2417 if (!ra) 2418 goto bad;
next_entry()を呼んでその戻り値をチェックしてエラーならgotoでジャンプ。le32_to_cpu()はマクロですし、x86_64はそもそもリトルエンディアンなので処理としては特にありません。
movl $0x0,0x20(%rsp)の部分はiを0で初期化ですよね。
2218 int policydb_read(struct policydb *p, void *fp) 2219 { 2220 struct role_allow *ra, *lra; 2221 struct role_trans *tr, *ltr; 2222 int i, j, rc;
そして、kmalloc()、kzalloc()等呼ばれているのでこれらがその場所かな~とも思えますが、最適化の関係で実は違ったりします。関数によってはアセンブラコードとcのコードの対応分かったりしますが。前にパッチを投げた「sysfs: Fix memory leak in sysfs_sd_setsecdata().」は割と簡単にいけました。
CONFIG_DEBUG_INFOがyになって入ればobjdump実行時にソースも表示できると思いますのでこれで確認してみると、このように出ました。
rc = next_entry(buf, fp, sizeof(u32)); if (rc) return rc; nel = le32_to_cpu(buf[0]); for (i = 0; i < nel; i++) { ffffffff8129fdd7: 8b 44 24 18 mov 0x18(%rsp),%eax ffffffff8129fddb: 39 44 24 20 cmp %eax,0x20(%rsp) ffffffff8129fddf: 0f 84 7b 03 00 00 je ffffffff812a0160 <policydb_read+0xea0> ffffffff8129fde5: 48 8b 3d 7c d1 d4 00 mov 0xd4d17c(%rip),%rdi # ffffffff81fecf68 <kmalloc_caches+0x28> ffffffff8129fdec: ba 18 00 00 00 mov $0x18,%edx ffffffff8129fdf1: be d0 80 00 00 mov $0x80d0,%esi ffffffff8129fdf6: e8 95 4b ee ff callq ffffffff81184990 <kmem_cache_alloc_trace> otype = NULL; name = NULL; rc = -ENOMEM; ft = kzalloc(sizeof(*ft), GFP_KERNEL); if (!ft) ffffffff8129fdfb: 48 85 c0 test %rax,%rax ffffffff8129fdfe: 49 89 c6 mov %rax,%r14 ffffffff8129fe01: 0f 84 40 03 00 00 je ffffffff812a0147 <policydb_read+0xe87> ffffffff8129fe07: 48 8b 3d 4a d1 d4 00 mov 0xd4d14a(%rip),%rdi # ffffffff81fecf58 <kmalloc_caches+0x18> ffffffff8129fe0e: ba 04 00 00 00 mov $0x4,%edx ffffffff8129fe13: be d0 00 00 00 mov $0xd0,%esi ffffffff8129fe18: e8 73 4b ee ff callq ffffffff81184990 <kmem_cache_alloc_trace> goto out; rc = -ENOMEM; otype = kmalloc(sizeof(*otype), GFP_KERNEL); if (!otype) ffffffff8129fe1d: 48 85 c0 test %rax,%rax ffffffff8129fe20: 48 89 44 24 30 mov %rax,0x30(%rsp) ffffffff8129fe25: 0f 84 0c 03 00 00 je ffffffff812a0137 <policydb_read+0xe77> goto out; /* length of the path component string */ rc = next_entry(buf, fp, sizeof(u32)); ffffffff8129fe2b: 48 8b 7c 24 28 mov 0x28(%rsp),%rdi ffffffff8129fe30: ba 04 00 00 00 mov $0x4,%edx ffffffff8129fe35: 4c 89 e6 mov %r12,%rsi ffffffff8129fe38: e8 d3 ca ff ff callq ffffffff8129c910 <next_entry> if (rc) ffffffff8129fe3d: 85 c0 test %eax,%eax
next_entry()呼んでfor文もあるんですが、kmallc()とかの結果を入れる変数が違うんですね( ´゜д゜`)エー policydb_read()だったらraとかtrですから。
それではこいつらはどこの処理なのか?って調べてみるとpolicydb_read()と同じファイルにあるfilename_trans_read()と分かりました。
コードを見てみるとこんな感じです。
1879 static int filename_trans_read(struct policydb *p, void *fp) 1880 { 1881 struct filename_trans *ft; 1882 struct filename_trans_datum *otype; 1883 char *name; 1884 u32 nel, len; 1885 __le32 buf[4]; 1886 int rc, i; 1887 1888 if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) 1889 return 0; 1890 1891 rc = next_entry(buf, fp, sizeof(u32)); 1892 if (rc) 1893 return rc; 1894 nel = le32_to_cpu(buf[0]); 1895 1896 for (i = 0; i < nel; i++) { 1897 ft = NULL; 1898 otype = NULL; 1899 name = NULL; 1900 1901 rc = -ENOMEM; 1902 ft = kzalloc(sizeof(*ft), GFP_KERNEL); 1903 if (!ft) 1904 goto out; 1905 1906 rc = -ENOMEM; 1907 otype = kmalloc(sizeof(*otype), GFP_KERNEL); 1908 if (!otype) 1909 goto out;
next_entry()が呼ばれて、for文があって変数名もあってますね。この関数はどこから呼ばれているかというとpolicydb_read()からでした。
2436 rc = filename_trans_read(p, fp); 2437 if (rc) 2438 goto bad;
なにはともあれこれで実際の関数が分かったので見てみます。
ループ内でkzalloc()、kmalloc()、kmalloc()と呼ばれています。
ここでメモリリークとして3パターンあったということを思い出して一個ずつ確認。
- [
] policydb_read+0xb3b/0x1220
ffffffff8129fdf6: e8 95 4b ee ff callq ffffffff81184990 <kmem_cache_alloc_trace> otype = NULL; name = NULL; rc = -ENOMEM; ft = kzalloc(sizeof(*ft), GFP_KERNEL); if (!ft) ffffffff8129fdfb: 48 85 c0 test %rax,%rax
- [
] policydb_read+0xb5d/0x1220
ffffffff8129fe18: e8 73 4b ee ff callq ffffffff81184990 <kmem_cache_alloc_trace> goto out; rc = -ENOMEM; otype = kmalloc(sizeof(*otype), GFP_KERNEL); if (!otype) ffffffff8129fe1d: 48 85 c0 test %rax,%rax ffffffff8129fe20: 48 89 44 24 30 mov %rax,0x30(%rsp)
- [
] policydb_read+0xb9c/0x1220
ffffffff8129fe4f: 41 8d 79 01 lea 0x1(%r9),%edi ffffffff8129fe53: 44 89 0c 24 mov %r9d,(%rsp) ffffffff8129fe57: e8 94 55 ee ff callq ffffffff811853f0 <__kmalloc> if (!name) ffffffff8129fe5c: 48 85 c0 test %rax,%rax ffffffff8129fe5f: 48 89 c3 mov %rax,%rbx
はい、アドレスもあってそうですね。ということで3パターン報告されていたのはこの3個の呼び出しであってそうです。
そうすると次はどのタイミングでkfree()を呼ぶべきかというところになります。
まずnameですがこれはftのnameメンバ変数に代入されます。ということでft開放までは開放できません。
1922 ft->name = name;
ではftとotypeはというとhashtab_insert()にハッシュテーブル(p->filename_trans)を渡し、ftをキーとしてotypeを登録します。ということでこれまたしばらくは開放できなさそうです。
1943 1944 hashtab_insert(p->filename_trans, ft, otype);
このハッシュテーブルはpolicydb_read()から渡ってきており、これはpolicydb_read()の呼び出し元のsecurity_load_policy()から渡されます。security_load_policy()でpolicydb_read()を呼ぶ箇所は2ヶ所あります。
これもどこからpolicydb_read()を呼んだのかはスタックトレースから分かりますので、objdumpで確認すると、ss_initializedが0の場合と分かります。
ffffffff812a4984: 48 89 bd a8 fb ff ff mov %rdi,-0x458(%rbp) if (!ss_initialized) { ffffffff812a498b: 85 f6 test %esi,%esi ffffffff812a498d: 0f 85 b5 00 00 00 jne ffffffff812a4a48 <security_load_policy+0xf8> avtab_cache_init(); ffffffff812a4993: 48 c7 c7 98 3d a2 81 mov $0xffffffff81a23d98,%rdi ffffffff812a499a: 31 c0 xor %eax,%eax ffffffff812a499c: e8 09 f6 39 00 callq ffffffff81643faa <printk> rc = policydb_read(&policydb, fp); ffffffff812a49a1: e8 5a 7b ff ff callq ffffffff8129c500 <avtab_cache_init> if (rc) { ffffffff812a49a6: 48 8d b5 a8 fb ff ff lea -0x458(%rbp),%rsi ffffffff812a49ad: 48 c7 c7 80 fc ff 81 mov $0xffffffff81fffc80,%rdi ffffffff812a49b4: e8 07 a9 ff ff callq ffffffff8129f2c0 <policydb_read> avtab_cache_destroy(); ffffffff812a49b9: 85 c0 test %eax,%eax struct policy_file file = { data, len }, *fp = &file; if (!ss_initialized) { avtab_cache_init(); rc = policydb_read(&policydb, fp); if (rc) { ffffffff812a49bb: 41 89 c4 mov %eax,%r12d avtab_cache_destroy(); ffffffff812a49be: 0f 85 c8 04 00 00 jne ffffffff812a4e8c <security_load_policy+0x53c> }
この場合はservices.cの上の方で定義している変数(policydb)が渡されます。ということはこの変数はシステム起動中に消えることはなさそうと考えられます。
そうすると、false positiveなのかもしくはss_initializedが1の場合に何か問題があるかの2点になりますね。
といっても後者の場合はまだ調べてませんが(´・ω・`)
とりあえずまとめると、objdumpは-Sオプションを使いましょうということです。。