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

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

CVE の Description

CVE-2018-6554Linux 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 を起こした

f:id:hiboma:20180906111255p:plain

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-toolsfuncgraphコールグラフを採取して、失敗してそうな箇所を推測してソースを読んでいった

 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 をよく理解できたので よいトレーニングとなった 💪
  • 権限昇格だとかになるととにかく難しいんだろうな