Linux Kernel CVE-2019-9857 の PoC を書いて検証・観察した

表題の通り CVE-2019-9857 が出ており、その PoC を書いてどのような影響があるのを検証・観察した.

CVE-2019-9857 の概要

nvd.nist.gov

In the Linux kernel through 5.0.2, the function inotify_update_existing_watch() in fs/notify/inotify/inotify_user.c neglects to call fsnotify_put_mark() with IN_MASK_CREATE after fsnotify_find_mark(), which will cause a memory leak (aka refcount leak). Finally, this will cause a denial of service.

ローカルの攻撃者により inotify でDoS を引き起こせる 脆弱性です.

修正パッチ

git.kernel.org

From 62c9d2674b31d4c8a674bee86b7edc6da2803aea Mon Sep 17 00:00:00 2001
From: ZhangXiaoxu <zhangxiaoxu5@huawei.com>
Date: Sat, 2 Mar 2019 09:17:32 +0800
Subject: inotify: Fix fsnotify_mark refcount leak in
 inotify_update_existing_watch()

Commit 4d97f7d53da7dc83 ("inotify: Add flag IN_MASK_CREATE for
inotify_add_watch()") forgot to call fsnotify_put_mark() with
IN_MASK_CREATE after fsnotify_find_mark()

Fixes: 4d97f7d53da7dc83 ("inotify: Add flag IN_MASK_CREATE for inotify_add_watch()")
Signed-off-by: ZhangXiaoxu <zhangxiaoxu5@huawei.com>
Signed-off-by: Jan Kara <jack@suse.cz>
---
 fs/notify/inotify/inotify_user.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c
index e2901fb..7b53598 100644
--- a/fs/notify/inotify/inotify_user.c
+++ b/fs/notify/inotify/inotify_user.c
@@ -519,8 +519,10 @@ static int inotify_update_existing_watch(struct fsnotify_group *group,
    fsn_mark = fsnotify_find_mark(&inode->i_fsnotify_marks, group);
    if (!fsn_mark)
        return -ENOENT;
-  else if (create)
-      return -EEXIST;
+   else if (create) {
+       ret = -EEXIST;
+       goto out;
+   }
 
    i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
 
@@ -548,6 +550,7 @@ static int inotify_update_existing_watch(struct fsnotify_group *group,
    /* return the wd */
    ret = i_mark->wd;
 
+out:
    /* match the get from fsnotify_find_mark() */
    fsnotify_put_mark(fsn_mark);
 
-- 
cgit v1.1

Fixes: 4d97f7d53da7dc83 ("inotify: Add flag IN_MASK_CREATE for inotify_add_watch()") とあるように、下記のコミットで紛れ込んでしまった不具合です

github.com

PoC のソース

悪用されると大変なので非公開です. patch を読み解いてください

PoC の実行環境

CentOS7 に https://elrepo.org/linux/kernel/el7/x86_64/RPMS/kernel-ml-5.0.4-1.el7.elrepo.x86_64.rpm をインストールして PoC を作った.

PoC を実行して観察する

PoC を書いて不具合を再現してみると inotify_init(2) が常に Too many open files を返すようになってしまった

$ ./poc
inotify_init: Too many open files

$ ./poc
inotify_init: Too many open files

$ ./poc
inotify_init: Too many open files

inotify が使えなくなっちゃう DoS ってことでいいのかなぁ. CVE の概要には which will cause a memory leak (aka refcount leak) と書いてあるので、参照カウントのリークで起きる不具合にフォーカスした問題であるように思う

パッチの読み解き

改めてパッチを見てみると、不具合を抱えたカーネルでは fsnotify_put_mark() がスキップされてしまうコードになっていた

+out:
    /* match the get from fsnotify_find_mark() */
    fsnotify_put_mark(fsn_mark);

fsnotify_put_mark() を呼ぶべき箇所で呼んでいないため、 fefcount leak が発生しカーネル内オブジェクトで何らかの不整合が起きるのだろう

fsnotify_put_mark()

void fsnotify_put_mark(struct fsnotify_mark *mark)
{
    struct fsnotify_mark_connector *conn;
    void *objp = NULL;
    unsigned int type = FSNOTIFY_OBJ_TYPE_DETACHED;
    bool free_conn = false;

    /* Catch marks that were actually never attached to object */
    if (!mark->connector) {
        if (refcount_dec_and_test(&mark->refcnt)) 👈
            fsnotify_final_mark_destroy(mark); 👈
        return;
    }

ここらで参照カウントを減らして、オブジェクトをデストロイしている. 少しボリュームが大きくなるので割愛 (読めていない)

メモ書き

inotify のデスクリプタに付く inode は anonymous inode として実装されている.

static int do_inotify_init(int flags)
{

... 

    ret = anon_inode_getfd("inotify", &inotify_fops, group,
                  O_RDONLY | flags);

anon_inode_getfd について下記のエントリで取り上げたはずなのだが、スッカリ記憶が揮発していた 😮

hiboma.hatenadiary.jp

Linuxの備忘録とか・・・ さんの wiki を拝見するのがよい

wiki.bit-hive.com

nodeは親ディレクトリinodeに登録され、パスによるファイル走査を可能としていますが、ファイル走査の必要ないinodeは、親ディレクトリinodeに登録する必要ありません。このinodeの事をanon_inodeと言います

感想

  • 小さなパッチだったので PoC の作成も容易だった
  • 自分が想定していたのとは異なる DoS になった