表題の通り CVE-2019-9857 が出ており、その PoC を書いてどのような影響があるのを検証・観察した.
CVE-2019-9857 の概要
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 を引き起こせる 脆弱性です.
修正パッチ
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()")
とあるように、下記のコミットで紛れ込んでしまった不具合です
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
について下記のエントリで取り上げたはずなのだが、スッカリ記憶が揮発していた 😮
Linuxの備忘録とか・・・
さんの wiki を拝見するのがよい
nodeは親ディレクトリinodeに登録され、パスによるファイル走査を可能としていますが、ファイル走査の必要ないinodeは、親ディレクトリinodeに登録する必要ありません。このinodeの事をanon_inodeと言います
感想
- 小さなパッチだったので PoC の作成も容易だった
- 自分が想定していたのとは異なる DoS になった