Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2022-0847 — CVE-2022-0847(Dirty Pipe)Linuxカーネルの権限昇格の脆弱性に関するPoCエクスプロイトと詳細な分析。テストおよびデバッグ用のDocker環境も含む。 | Kitploit
ツール/GitHubGitHub/chenaotian/cve-2022-0847
特権昇格脆弱性分析エクスプロイト学習と教育バイナリエクスプロイトラボと実践
GitHubchenaotian/cve-2022-0847

CVE-2022-0847

CVE-2022-0847(Dirty Pipe)Linuxカーネルの権限昇格の脆弱性に関するPoCエクスプロイトと詳細な分析。テストおよびデバッグ用のDocker環境も含む。

リポジトリを見る
257664年前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2022-0847 Dirty Pipe Linuxカーネル権限昇格分析

[toc]

本記事は華為セキュリティ公式アカウントに初出掲載されたものです。こちらはブログ版(より完全な内容)です。

初出リンク:https://mp.weixin.qq.com/s/6VhWBOzJ7uu80nzFxe5jpg

脆弱性概要

脆弱性ID: CVE-2022-0847 (別名: ダーティパイプ dirty pipe)

脆弱性対象: linux kernel - splice syscall

影響バージョン: linux 5.8 パッチ f6dd975583bd で導入~5.16.11、5.15.25、5.10.102 で修正

脆弱性の影響: 任意の読み取り可能ファイルに対して1ページ以内の内容を書き込むことが可能(十分な影響力)、ローカル権限昇格が可能。

環境構築

脆弱性分析用docker: chenaotian/cve-2022-0847 (まだアクセスできない場合は、まだアップロードが完了していない可能性があります。)

提供内容:

  • 脆弱性を含むデバッグ可能なカーネル5.13(コンパイル済み)
  • qemu、gdb、linux カーネル5.13ソースコード
  • exp

起動:

cd ~/cve-2022-0847
gcc exp.c -o exp --static && cp exp ./rootfs && cd rootfs
find . | cpio -o --format=newc > ../rootfs.img
cd ../ 
./boot.sh

デバッグ:

gdb ./vmlinux
target remote :10086
directory /root/linux-5.13
b do_splice
b copy_page_to_iter_pipe 
b pipe_write
ignore 3 15
...
p *(struct pipe_inode_info *) pipe
p (struct pipe_buffer)pipe->bufs[0]

脆弱性原理

脆弱性の簡単な原理は、splice 関数を呼び出すことで「ゼロコピー」形式でファイルを pipe に送信できることです。コードレベルでのゼロコピーは、ファイルキャッシュページ(page cache)をそのまま pipe の buf ページとして使用します。しかし、ここで変数の未初期化脆弱性が導入され、ファイルキャッシュページが後続の pipe チャネル内で通常の pipe キャッシュページとして扱われ、「追記」によって改ざんされてしまいます。ただし、この場合、カーネルはこのキャッシュページを「ダーティページ」と判断せず、短時間(次回再起動などまで)ディスクにフラッシュされません。この間、そのファイルにアクセスするすべてのシナリオで改ざんされたファイルキャッシュページが使用されるため、「短時間で任意の読み取り可能ファイルに任意の書き込みを行う」操作が達成されます。これによりローカル権限昇格が可能になります。

脆弱性発生箇所

パッチによると、脆弱性発生箇所は copy_page_to_iter_pipe 関数にあり、buf->flags の初期化処理が追加されています。したがって、これは変数の未初期化脆弱性です。

image-20220308170149137

copy_page_to_iter_pipe の呼び出し箇所は splice システムコール内にあります。splice 関数(システムコール)は「ゼロコピー」方式でファイル内容をパイプに転送します。従来のファイル内容をそのままパイプに送る方法よりもパフォーマンスが優れています。詳細は後述します。

pipeの原理とpipe_write

まず、脆弱性の別名が「ダーティパイプ」であることから、パイプ(pipe)について理解します。pipe はカーネルが提供する通信パイプで、pipe/pipe2 関数で作成され、2つのファイルディスクリプタを返します。1つはデータ送信用、もう1つはデータ受信用で、パイプの両端のようなものです。具体的な使用方法は割愛します。

image-20220309124007780

カーネル内での実装を簡単に説明します。通常、パイプのキャッシュ空間の総長さは65536バイトで、ページ単位で管理されます。合計16ページ(1ページ4096バイト)で、ページ同士は連続しておらず、配列で管理され、リング状のリンクリストを形成します。2つのリンクリストポインタが維持され、1つは書き込み用(pipe->head)、もう1つは読み取り用(pipe->tail)です。ここでは主に pipe_write 関数を分析します:

linux-5.13\fs\pipe.c : 400 : pipe_write

static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
	struct file *filp = iocb->ki_filp;
	struct pipe_inode_info *pipe = filp->private_data;
	unsigned int head;
	ssize_t ret = 0;
	size_t total_len = iov_iter_count(from);
	ssize_t chars;
	bool was_empty = false;
	bool wake_next_writer = false;

	··· ···
    ··· ···
	head = pipe->head;
	was_empty = pipe_empty(head, pipe->tail);
	chars = total_len & (PAGE_SIZE-1);
	if (chars && !was_empty) { 
        //[1]パイプキャッシュが空でなければ、現在の最終ページから「追記」できるか試みる
		unsigned int mask = pipe->ring_size - 1;
		struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];
		int offset = buf->offset + buf->len; 

		if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
		    offset + chars <= PAGE_SIZE) { 
            /*[2] 重要:PIPE_BUF_FLAG_CAN_MERGE フラグが存在する場合、そのページへの追記が許可される
             * 書き込み長さがページを跨がない場合は追記、そうでなければ新しいページを開始する */
			ret = pipe_buf_confirm(pipe, buf);
			···
			ret = copy_page_from_iter(buf->page, offset, chars, from);
			···
			}
			buf->len += ret;
			···
		}
	}

	for (;;) {//[3] 前のページに追記できない場合、新しいページを開始する
		··· ···
		head = pipe->head;
		if (!pipe_full(head, pipe->tail, pipe->max_usage)) {
			unsigned int mask = pipe->ring_size - 1;
			struct pipe_buffer *buf = &pipe->bufs[head & mask];
			struct page *page = pipe->tmp_page;
			int copied;

			if (!page) {//[4] 新しいページを新規申請
				page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
				if (unlikely(!page)) {
					ret = ret ? : -ENOMEM;
					break;
				}
				pipe->tmp_page = page;
			}

			spin_lock_irq(&pipe->rd_wait.lock);

			head = pipe->head;
			··· ···
			pipe->head = head + 1;
			spin_unlock_irq(&pipe->rd_wait.lock);

			/* Insert it into the buffer array */
			buf = &pipe->bufs[head & mask];
			buf->page = page;//[5] 新しく申請されたページをページ配列に格納
			buf->ops = &anon_pipe_buf_ops;
			buf->offset = 0;
			buf->len = 0;
			if (is_packetized(filp))
				buf->flags = PIPE_BUF_FLAG_PACKET;
			else
				buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
            	//[6] フラグを設定、デフォルトでPIPE_BUF_FLAG_CAN_MERGE
			pipe->tmp_page = NULL;

			copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); 
            //[7] コピー操作
			··· ···
			ret += copied;
			buf->offset = 0;
			buf->len = copied;

			··· ···
		}
        ··· ···
    }
	··· ···
	return ret;
}
  1. 現在のパイプが空でない場合(head==tail で空と判定)、パイプ内に未読み取りデータがあることを意味します。head ポインタ、つまり最新の書き込み用ページを指すポインタを取得し、そのページの len、offset(データ末尾を見つけるため)を調べます。次に、現在のページへの追記を試みます。
  2. 現在のページが PIPE_BUF_FLAG_CAN_MERGE フラグを持っているかどうかを判断します。フラグが存在しない場合、現在のページへの追記は許可されません。または、書き込むデータを以前のデータの後ろに結合した長さが1ページを超える場合(つまり書き込み操作がページを跨ぐ場合)、跨ぐ場合は追記できません。
  3. 前のページに追記できない場合、新しいページを開始します。
  4. alloc_page で新しいページを申請します。
  5. 新しいページを配列の先頭に配置し(既存のページを置き換える可能性あり)、初期化します。
  6. buf->flag はデフォルトで PIPE_BUF_FLAG_CAN_MERGE に初期化されます。なぜなら、デフォルト状態ではページへの追記が許可されているからです。
  7. 書き込みデータをコピーし、コピーが完了するまで上記操作を繰り返します。

脆弱性悪用の鍵は splice 内で初期化されていない PIPE_BUF_FLAG_CAN_MERGE フラグにあります。これにより、「書き込み未完了」の pipe ページに追記できるかどうかが決まります。

spliceからcopy_page_to_iter_pipeへ

上記で述べたように、pipe は16ページを管理してキャッシュとしています。splice のゼロコピー方式は、ファイルキャッシュページで直接 pipe 内のキャッシュページを置き換える(pipeキャッシュページのポインタをファイルキャッシュページに向けさせる)ことです。

image-20220309124515813

splice システムコールから脆弱性関数 copy_page_to_iter_pipe までの呼び出しスタックは深く、詳細な分析は行いませんが、呼び出しスタックは以下の通りです:

  • SYSCALL_DEFINE6(splice,...) -> __do_sys_splice -> __do_splice-> do_splice
    • splice_file_to_pipe -> do_splice_to
      • generic_file_splice_read(in->f_op->splice_read デフォルトは generic_file_splice_read)
        • call_read_iter -> filemap_read
          • copy_page_to_iter -> copy_page_to_iter_pipe

脆弱性のある copy_page_to_iter_pipe 関数の主な処理は、pipe キャッシュページ構造を転送対象のファイルキャッシュページに向けさせることです:

linux-5.13\lib\iov_iter.c : 417 : copy_page_to_iter_pipe

static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,
			 struct iov_iter *i)
{
	struct pipe_inode_info *pipe = i->pipe;
	struct pipe_buffer *buf;
	unsigned int p_tail = pipe->tail;
	unsigned int p_mask = pipe->ring_size - 1;
	unsigned int i_head = i->head;
	size_t off;

	··· ···

	off = i->iov_offset;
	buf = &pipe->bufs[i_head & p_mask];//[1]対応するpipeキャッシュページを取得
	··· ···
	
	buf->ops = &page_cache_pipe_buf_ops;//[2]pipeキャッシュページの関連情報をファイルキャッシュページに向けさせる
	get_page(page);
	buf->page = page;//[2]ページポインタをファイルキャッシュページに向ける
	buf->offset = offset;//[2]offset,lenなどを現在の情報に設定(spliceの引数により決定)
	buf->len = bytes;

	pipe->head = i_head + 1;
	i->iov_offset = offset + bytes;
	i->head = i_head;
out:
	i->count -= bytes;
	return bytes;
}
  1. まず、pipe ページ配列のリング構造に基づき、現在の書き込みポインタ(pipe->head)の位置を見つけます。
  2. 現在書き込みが必要なページを準備されたファイルキャッシュページに向けさせ、他の情報(例えば len は splice システムコールの引数で決定)を設定します。ここで唯一、flag が初期化されず、脆弱性の原因となります。

一般的に初期化後の pipe->bufs は以下のようになります:

image-20220308165052936

このとき、先ほど分析した pipe_write のコードに従えば、pipe_write を再び呼び出して pipe にデータを書き込むと、書き込みポインタ(pipe->head)は上の図のページを指し、flag が PIPE_BUF_FLAG_CAN_MERGE であるため、そのページに追記して書き込めるものとみなされます(書き込み長さがページを跨がない限り):

#define PIPE_BUF_FLAG_CAN_MERGE	0x10	/* can merge buffers */
ツールをダウンロード