
CVE-2022-0185の教育用ケーススタディおよびエクスプロイト開発ウォークスルー。Linuxカーネルのヒープベースのバッファオーバーフローを利用してローカル権限昇格を可能にします。POC、QEMUデバッグ、および詳細な技術分析を伴うUbuntuエクスプロイトを含みます。
このケーススタディは、ECE 9069: Introduction to Hacking の課題の成果物です: https://whisperlab.org/introduction-to-hacking/
CVE-2022-0185 は、Linux カーネルの Filesystem Context 機能の legacy_parse_param 関数が、指定されたパラメータの長さを検証する方法に存在するヒープベースのバッファオーバーフローの脆弱性です。特権のない(非特権ユーザーネームスペースが有効な場合。それ以外の場合は、ネームスペース内の CAP_SYS_ADMIN 権限が必要)ローカルユーザーが、Filesystem Context API をサポートしておらず(したがってレガシー処理にフォールバックする)ファイルシステムを開くことができる場合、この欠陥を利用してシステム上の権限を昇格させることができました。[1]
この脆弱性が報告された後、このバグを修正するパッチがリリースされました:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=722d94847de2
https://ubuntu.com/security/CVE-2022-0185#impact-score
発見者による詳細な解説があります: https://www.hackthebox.com/blog/CVE-2022-0185:_A_case_study
このリポジトリでは、この脆弱性を再現するための基本的な手順と関連する背景情報を説明します。また、不明な点があれば、[email protected] までメールを送ってください。喜んでお答えします。
CVE-2022-0185 の脆弱性は、02/11/2022 に公開され、CVSS 3.x の基本スコアは 8.4(高)です。[1] この脆弱性は、符号なし整数のアンダーフローによって引き起こされるヒープベースのバッファオーバーフローです。
この脆弱性は Linux v5.1 カーネルで導入され、5.1 より高いカーネルバージョンを搭載するすべての Linux ディストリビューションに影響を与えました。例えば、Ubuntu 20.04 LTS(focal)はこのバグに対して脆弱でした。ただし、パッチはリリースされており、バージョン 5.4.0-96.109 以降で利用可能です。[3]
この脆弱性を悪用すると、特権のないローカルユーザーがシステム上の権限を昇格させ、システム全体を危険にさらす可能性があります。[1] [2] CVSS スコアの詳細な分析は次のとおりです: 基本スコア: 8.4。これは、即時の対応が必要な重大なセキュリティリスクを示しています。 影響スコア: 5.9。悪用された場合、かなりの潜在的な被害が想定されます。高い機密性、完全性、可用性の値がこのスコアに寄与しています。 悪用可能性スコア: 2.5。比較的高い悪用可能性を示しています。ローカルの攻撃ベクトル、高い完全性、高い可用性の値がこのスコアに寄与しています。
表 1.1 と表 1.2 は、これらのスコアとその構成要素に関する詳細情報を示しています。
| CVSS v3.1 深刻度 | 値 |
|---|---|
| 基本スコア | 8.4 高 |
| 影響スコア | 5.9 |
| 悪用可能性スコア | 2.5 |
表 1.1 CVSS 深刻度スコア[1]
| CVSS v3.1 メトリクス | 値 |
|---|---|
| 攻撃ベクトル (AV) | ローカル |
| 必要な権限 (PR) | なし |
| ユーザー操作 (UI) | なし |
| 機密性 (C) | 高 |
| 完全性 (I) | 高 |
| 可用性 (A) | 高 |
表 1.2 CVSS ベクター[1]
現代のコンピュータには、符号付きと符号なしの2種類の整数型があります。符号付き数の表現には、一般に2の補数と呼ばれる操作が関与します。[4] 「2の補数は、最上位の桁の2進数ビットを符号として使用し、2進数が正であるか負であるかを示します」[4]
2の補数を導入することで、減算の計算を加算に変換できるため、CPUの設計と実装が簡素化されます。整数の2の補数を生成するには、次の3つのステップがあります:[4]
図 2.1.1.1 は、「-6」を2の補数形式に変換する実際の例を図で示しています。

図 2.1.1.2 2の補数を用いた加算
図 2.1.1.2 は、'-6' の2の補数を '+6' に加算するプロセスを示しています。これは、2の補数を使用することで加算を減算の代用として使用できることを示しています。

図 2.1.1.2 2の補数を用いた加算
セクション 2.1.1 で、2の補数が何であるかを理解しました。次に、コンピュータにおける符号なし整数のアンダーフローのシナリオを見てみましょう。現代のコンピュータでは、符号なし数を使用する場合、最上位ビットは符号ビットとして扱われず、符号なし数自体の一部となります。つまり、符号なし数で減算を実行するときは注意が必要です。符号なし整数のアンダーフローとして知られる状態につながる可能性があるためです。[5]
図 2.1.2.1 は、8ビットの符号なし数で5から6を減算する状況を示しています。符号なし数がラップアラウンドするため、最終結果は255になります。このアンダーフローが条件文内で発生すると、その文の機能を破壊する可能性があります。

図 2.1.2.1 符号なし整数のアンダーフロー
Linux カーネルでは、Slab アロケータは小さなメモリチャンクを効率的に割り当ておよび解放するためのメモリ管理メカニズムです。それぞれが固定サイズのメモリブロックを含む複数のスラブキャッシュを維持することで、パフォーマンスを提供します。通常、kmalloc-32 は32バイトのメモリを割り当てるため kmalloc-32 スラブであり、一方 kmalloc-4k は4096バイトのメモリを割り当てるため kmalloc-4k スラブです。[6]
さらに、Linux カーネルのスラブ割り当ては、通常、カーネルのヒープメモリ領域内の連続したアドレス空間からメモリを割り当てることを伴います。この連続したアドレスはカーネルによって管理され、さまざまなカーネルオブジェクトやデータ構造にメモリを割り当てるために使用されます。図 2.2.1.1 は、Linux カーネルメモリ内のスラブのレイアウトを示しています。

図 2.2.1.1 Linux の Slab アロケータ [7] (この図の作者は https://leviathan.vip/ です)
自分でコンパイルした Linux カーネルでこのプロセスを再現したい場合は、以下のマークダウンファイルを読んで背景情報を入手してください:
注意:
このリポジトリのマークダウンファイル、コード、スクリプトはすべて異なるフォルダにあります。各フォルダには専用のマークダウンファイルがあるので、何かを試す前にそれを読んでください!
セクション 2.1 では、符号なしアンダーフローの仕組みを説明しました。次に、この脆弱性を含むカーネル関数を調べます。
ユーザー「clubby789」は、カーネル関数 legacy_parse_param に脆弱性を発見しました。この関数は主に、カーネルに渡されるパラメータの解析を担当します。CVE-2022-0185 では、fsopen を使用してファイル記述子を開いた後、fsconfig 関数を使用して設定のキーと値のペアをカーネルに渡すことで、この関数が呼び出されました。legacy_parse_param の簡略化されたバージョンを次のコードに示します。[2]```c static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param) { struct legacy_fs_context *ctx = fc->fs_private; // [1] unsigned int size = ctx->data_size; // [2] size_t len = 0; int ret; [ ... ] switch (param->type) { case fs_value_is_string: len = 1 + param->size; // [3] case fs_value_is_flag: len += strlen(param->key); break; default: return invalf(fc, "VFS: Legacy: Parameter type for '%s' not supported", param->key); } if (len > PAGE_SIZE-2-size) return invalf(fc, "VFS: Legacy: Cumulative options too large"); // [4] [ ... ] if (!ctx->legacy_data) { ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); // [5] if (!ctx->legacy_data) return -ENOMEM; } ctx->legacy_data[size++] = ','; // [6] len = strlen(param->key); memcpy(ctx->legacy_data + size, param->key, len); size += len; if (param->type == fs_value_is_string) { ctx->legacy_data[size++] = '='; memcpy(ctx->legacy_data + size, param->string, param->size); size += param->size; } ctx->legacy_data[size] = '\0'; ctx->data_size = size; ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS; return 0; }
上記のコードスニペットから、行 [1] と [2] がコードのコンテキストを設定していることがわかります。一方、行 [4] には、符号なしアンダーフローが発生するステートメントが含まれています。行 [5] はヒープスラブの割り当てを処理し、行 [6] と [7] は割り当てられたスラブへのデータの投入を担当します。特に、行 [6] では区切り文字としてカンマ (',') が追加され、等号 ('=') も追加されるため、実際のデータサイズより2バイト多く消費されます。
行 [4] では、if 文内の変数に PAGE_SIZE (4096 に設定されたマクロ) と size (符号なし 64 ビット数) が含まれています。符号なし数値が 4095 まで累積するとアンダーフローが発生し、if 文が常に false と評価されるようになります。これにより、隣接するスラブへの領域外書き込みが可能になります。このアンダーフローは、符号なし数値の減算によって引き起こされ、4096 - 4095 の結果として、符号なし数値 18446744073709551615 が得られます。[2]
`18446744073709551615` の結果については、以下の本文で説明されています。```c
if (len > PAGE_SIZE-2-size) return invalf(fc, "VFS: Legacy: Cumulative options too large");
ここで、PAGE_SIZE は 4096 バイトに等しく、2 は各 key-value ペアを区切るために追加される文字 , と = に相当します。
問題は、size が 符号なし整数値 であることです。したがって、size が 4095 に達すると、PAGE_SIZE-2-size という式は singned value: -1 に等しくなります。しかし、符号なし整数値: 18446744073709551615 として扱うと、次の図に示すように2の補数表現のため、この値になります! [3]

したがって、上記の if 文は常に false になります。つまり、残りのデータは、割り当てたスラブを超えてヒープにコピーされることになります!
この符号なし整数のアンダーフローがどのように発生するかを理解したら、脆弱性を実証するための概念実証 (POC) コードの作成に進むことができます。
ユーザー “clubby789” が、以下のコードスニペットに示す詳細な POC コードを提供しています。このコードは簡潔です。まず ext4 というファイルディスクリプタを開き、次に fsconfig を複数回使用してカーネルにデータを投入します。
ここで注意すべき点は2つあります: