表題の通り CVE-2018-6554 が出ており、その PoC を書いてどのような影響があるのを検証・観察した
CVE の Description
CVE-2018-6554 は Linux Kernel に付いた CVE です
Memory leak in the irda_bind function in net/irda/af_irda.c and later in drivers/staging/irda/net/af_irda.c in the Linux kernel before 4.17 allows local users to cause a denial of service (memory consumption) by repeatedly binding an AF_IRDA socket.
CVE を調べたモチベーション
- CVE の情報やパッチをみたら「あれ、これだったら自分でも再現できるんじゃない?」という内容だった
- 隣の席に mrtc0 というセキュリティに滅法強い若者がいて、彼が日々あれこれ Poc PoC チーンしているのに触発された
- https://blog.ssrf.in で有用なエントリをたくさん書いている。生活能力に懸念を感じる話も混じっている
- (真面目な理由) 弊社 GMOペパボは、共有ホスティングやコンテナホスティングを Linux ディストリビューション上でサービスしており、ローカルで任意のコードが実行されうるため Linux カーネル の CVE については詳細や影響度を把握しておきたい
Linux カーネルのパッチとそのコメント
既に修正パッチが投稿されており、コメントを読むことで CVE がどのような技術的詳細をもつのかを知ることができた
The irda_bind() function allocates memory for self->ias_obj without checking to see if the socket is already bound. A userspace process could repeatedly bind the socket, have each new object added into the LM-IAS database, and lose the reference to the old object assigned to the socket to exhaust memory resources. This patch errors out of the bind operation when self->ias_obj is already assigned. CVE-2018-6554 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Tyler Hicks <tyhicks@xxxxxxxxxxxxx> Reviewed-by: Seth Arnold <seth.arnold@xxxxxxxxxxxxx> Reviewed-by: Stefan Bader <stefan.bader@xxxxxxxxxxxxx> --- net/irda/af_irda.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/irda/af_irda.c b/net/irda/af_irda.c index 4a116d766c15..82e632b2c5a1 100644 --- a/net/irda/af_irda.c +++ b/net/irda/af_irda.c @@ -774,6 +774,13 @@ static int irda_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) return -EINVAL; lock_sock(sk); + + /* Ensure that the socket is not already bound */ + if (self->ias_obj) { + err = -EINVAL; + goto out; + } + #ifdef CONFIG_IRDA_ULTRA /* Special care for Ultra sockets */ if ((sk->sk_type == SOCK_DGRAM) && -- 2.7.4
Linux カーネルのソースに慣れてれば、どこのシステムコールからここにたどり着くかはすぐわかりそう
oss-sec: CVE-2018-6554 and CVE-2018-6555: Linux kernel: irda memory leak and use after free
同時に oss-sec ML に流れていた内容も参考にした。
Two issues were discovered in the irda subsystem within the Linux kernel. The irda subsystem has been removed from the upstream kernel starting in v4.17 but it is present in many distro kernels and the stable kernel tree. Memory leak in the irda_bind function in net/irda/af_irda.c and later in drivers/staging/irda/net/af_irda.c in the Linux kernel before 4.17 allows local users to cause a denial of service (memory consumption) by repeatedly binding an AF_IRDA socket. (CVE-2018-6554) The irda_setsockopt function in net/irda/af_irda.c and later in drivers/staging/irda/net/af_irda.c in the Linux kernel before 4.17 allows local users to cause a denial of service (ias_object use-after-free and system crash) or possibly have unspecified other impact via an AF_IRDA socket. (CVE-2018-6555) I've sent the fixes to the stable kernel list but I don't yet see my submissions in the list archive on Spinics. Here are the equivalent versions of the patches against the Ubuntu kernel:
余談: 関連する CVE-2018-6555 も含まれている
PoC のソース
⚠️ すいません.ソース非公開です。悪用されたら大変 ☠️
CVE やパッチの説明を読んでいくと
というのが分かったので、推測しつつソースを読みつつで実装していった
具体的なことを書くとよくないのでぼんやりと書いています ... せっかく再現できたのにもどかしいですね
PoC で再現をとり観察
PoC を実行しながら どのような状態になるのかを観測していった
再現の環境
Ubuntu Xenial + VirtualBox
vagrant@vagrant:~$ uname -a Linux vagrant 4.4.0-134-generic #160-Ubuntu SMP Wed Aug 15 14:58:00 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
Slab kmalloc-{96, 32, 8} がリークする
PoC を実行しながら slabtop で観察すると kmalloc-{96, 32, 8} が増えていくのが観察できた。明らかにリークしている
Active / Total Objects (% used) : 6866369 / 6867490 (100.0%) Active / Total Slabs (% used) : 104023 / 104023 (100.0%) Active / Total Caches (% used) : 72 / 124 (58.1%) Active / Total Size (% used) : 428379.30K / 428634.12K (99.9%) Minimum / Average / Maximum Object : 0.01K / 0.06K / 8.00K OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 3368946 3368946 100% 0.09K 80213 42 320852K kmalloc-96 👈 2252672 2252672 100% 0.03K 17599 128 70396K kmalloc-32 👈 1126400 1126400 100% 0.01K 2200 512 8800K kmalloc-8 👈 25092 25092 100% 0.12K 738 34 2952K kernfs_node_cache
実行に伴って /proc/net/irda/irias
のエントリ数も増え続ける ( slab/kmalloc でこれらを確保しているのだろう )
LM-IAS Objects: name: AAA, id=-47462 - Attribute name: "IrDA:TinyTP:LsapSel", value[IAS_INTEGER]: 94 name: AAA, id=-47471 - Attribute name: "IrDA:TinyTP:LsapSel", value[IAS_INTEGER]: 93 name: AAA, id=-47471 - Attribute name: "IrDA:TinyTP:LsapSel", value[IAS_INTEGER]: 92 ....
Out of Memory and no killable processes ...
slab の kmalloc-{96,32,8} が増え続けて限界に達すると Kernel Panic を起こした
8GB 載せた VM で PoC を実行して 1〜2分かかったので、再現はのんびりしている
コンテナでの検証
mrtc0 とわいわいしながら検証していたが、コンテナ = Ubuntu Xenial + Docker では以下の通り Address family not supported by protocol = EAFNOSUPPORT
を返し、AF_IRDA を扱えず再現しなかった
root@0cea0f70d2b1:/# ./poc
socket: Address family not supported by protocol
ソースを読んだところ net namespace が init_net 出ない場合には AF_IRDA ソケットはサポートされてないのだと理解した。
static int irda_create(struct net *net, struct socket *sock, int protocol, int kern) { struct sock *sk; struct irda_sock *self; if (protocol < 0 || protocol > SK_PROTOCOL_MAX) return -EINVAL; if (net != &init_net) 👈 return -EAFNOSUPPORT;
上記を調べる際に perf-tools の funcgraph でコールグラフを採取して、失敗してそうな箇所を推測してソースを読んでいった
3) | SyS_socket() { 3) | __sock_create() { 3) | security_socket_create() { 3) | apparmor_socket_create() { 3) | aa_sock_create_perm() { 3) | aa_af_perm() { 3) 0.109 us | aa_label_next_confined(); 3) | aa_profile_af_perm() { 3) 0.385 us | aa_apply_modes_to_perms(); 3) 0.061 us | aa_check_perms(); 3) 1.821 us | } 3) 0.053 us | aa_label_next_confined(); 3) 4.069 us | } 3) 4.525 us | } 3) 5.064 us | } 3) 5.825 us | } 3) | sock_alloc() { 3) | new_inode_pseudo() { 3) | alloc_inode() { 3) | sock_alloc_inode() { 3) | kmem_cache_alloc() { 3) | _cond_resched() { 3) 0.048 us | rcu_all_qs(); 3) 0.429 us | } 3) | memcg_kmem_get_cache() { 3) 0.075 us | get_mem_cgroup_from_mm(); 3) 0.937 us | } 3) 0.136 us | prefetch_freepointer(); 3) 0.145 us | memcg_kmem_put_cache(); 3) 3.326 us | } 3) | kmem_cache_alloc_trace() { 3) | _cond_resched() { 3) 0.052 us | rcu_all_qs(); 3) 0.465 us | } 3) 0.108 us | prefetch_freepointer(); 3) 0.091 us | memcg_kmem_put_cache(); 3) 2.186 us | } 3) 0.047 us | __init_waitqueue_head(); 3) 6.688 us | } 3) | inode_init_always() { 3) | make_kuid() { 3) 0.113 us | map_id_range_down(); 3) 0.546 us | } 3) | make_kgid() { 3) 0.054 us | map_id_range_down(); 3) 0.413 us | } 3) 0.138 us | security_inode_alloc(); 3) 0.041 us | __init_rwsem(); 3) 2.868 us | } 3) + 10.782 us | } 3) 0.049 us | _raw_spin_lock(); 3) + 11.746 us | } 3) 0.059 us | get_next_ino(); 3) + 12.880 us | } 3) 0.320 us | try_module_get(); 3) 0.051 us | irda_create [irda](); 🔥 3) 0.070 us | module_put(); 3) | sock_release() { 3) | iput() { 3) 0.052 us | _raw_spin_lock(); 3) | evict() { 3) | inode_wait_for_writeback() { 3) 0.045 us | _raw_spin_lock(); 3) | __inode_wait_for_writeback() { 3) 0.045 us | bit_waitqueue(); 3) 0.429 us | } 3) 1.310 us | } 3) 0.056 us | truncate_inode_pages_final(); 3) | clear_inode() { 3) | _cond_resched() { 3) 0.049 us | rcu_all_qs(); 3) 0.464 us | } 3) 0.055 us | _raw_spin_lock_irq(); 3) 1.315 us | } 3) 0.050 us | _raw_spin_lock(); 3) | wake_up_bit() { 3) 0.146 us | __wake_up_bit(); 3) 0.567 us | } 3) | destroy_inode() { 3) | __destroy_inode() { 3) 0.048 us | inode_has_buffers(); 3) | security_inode_free() { 3) 0.050 us | integrity_inode_free(); 3) 0.522 us | } 3) | __fsnotify_inode_delete() { 3) | fsnotify_destroy_marks() { 3) | fsnotify_grab_connector() { 3) 0.228 us | __srcu_read_lock(); 3) 0.052 us | __srcu_read_unlock(); 3) 1.064 us | } 3) 1.517 us | } 3) 1.991 us | } 3) 0.047 us | locks_free_lock_context(); 3) 4.803 us | } 3) | sock_destroy_inode() { 3) | kfree_call_rcu() { 3) | __call_rcu.constprop.66() { 3) 0.059 us | rcu_segcblist_enqueue(); 3) 0.480 us | } 3) 0.897 us | } 3) 0.286 us | kmem_cache_free(); 3) 1.925 us | } 3) 7.592 us | } 3) + 13.272 us | } 3) + 14.260 us | } 3) + 14.729 us | } 3) + 36.724 us | } 3) + 37.622 us | }
読む際のポイントは、socket(2) で新規にオブジェクトを割り当てる処理なのに、途中から sock_release(), module_put(), iput() とオブジェクトの解放のためのコードを呼ぶのに転じている箇所があるので、そこらが怪しいのだと検討をつけて読んでいった。
(余談: システムコールが返す errno から、カーネルのソースコードを逆引きしたい時ってあるよなぁ。いい方法ないだろうか )
感想
- 再現が難しい(例: 再現のためのシステムコールの呼び出しが多い、複雑なバイナリを扱う操作が必要、レースコンディションで起きる ... etc ) ではなかったので何とか書けた感じ
- CVE-2018-6555 を合わせると
use-after-free and system crash
となりえるらしいが、どうやるんだろうなぁ - PoC を書くことで パッチと CVE をよく理解できたので よいトレーニングとなった 💪
- 権限昇格だとかになるととにかく難しいんだろうな