読者です 読者をやめる 読者になる 読者になる

φ(.. )メモシテオコウ kmemleakがレポートしたメモリリークの場所探し

linux kernel

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オプションを使いましょうということです。。