
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp

本記事の理論知識(名前空間、オーバーレイファイルシステム、FUSEファイルシステム等)はすべてchatGPTから得たものです。
脆弱性ID: CVE-2023-0386
脆弱性製品: linux kernel - オーバーレイファイルシステム
影響範囲: 5.11 ~ 5.19
利用条件: unshare可能、またはオーバーレイファイルシステムを作成可能
利用効果: ローカル権限昇格
自分でカーネルをコンパイルする:
脆弱性対象バージョン範囲内で、5.15以外(5.15は問題があるらしい)のものを用意し、overlayとfuseの2つのfsを有効にする:
CONFIG_SLUB_DEBUGOVERLAY_FS
CONFIG_FUSE_FS
ubuntu 21.10 カーネルバージョン5.13.0-16-genericで動作確認済み:

脆弱性の分析に入る前に、chatGPTにlinuxカーネル専門家の役を演じてもらいましょう:
(chatGPTへの質問:これからあなたはlinuxカーネル専門家として振る舞い、私の質問に答えてください)
脆弱性に関する公開情報は少なく、最も直接的には脆弱性のパッチ情報です。パッチのリンクは以下の通り:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f11ada10d0a

ovl_copy_up_one関数に1つのチェックが追加されていることがわかります。まずchatGPTにこの関数の役割を尋ねてみましょう:

つまり、この関数はオーバーレイファイルシステムの下層ファイルが上層にコピーされる動作の中で発生します。そして、文脈からこのパッチで新たに追加されたチェックを見ていきます:
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
int flags)
{
int err;
DEFINE_DELAYED_CALL(done);
struct path parentpath;
struct ovl_copy_up_ctx ctx = {
.parent = parent,
.dentry = dentry,
.workdir = ovl_workdir(dentry),
};
if (WARN_ON(!ctx.workdir))
return -EROFS;
ovl_path_lower(dentry, &ctx.lowerpath);
err = vfs_getattr(&ctx.lowerpath, &ctx.stat,//[1] 下層ファイルシステムのstatを取得
STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
if (err)
return err;
//[2] パッチで追加:ファイルのstat属性内のユーザーIDとグループIDが現在の名前空間にマッピングされているかをチェック
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
!kgid_has_mapping(current_user_ns(), ctx.stat.gid))
return -EOVERFLOW;
[1] まずvfs_getattr関数で下層ファイルシステムのターゲットファイルの属性を取得する。vfs_getattr関数はファイルのstruct path構造を渡すことで、対応するstruct stat構造を取得する。
[1.1] ctx.lowerpathはオーバーレイファイルシステムの下層ファイルシステム内のあるファイルパス。オーバーレイファイルシステムについては後述。
[1.2] struct stat構造体はファイルのメタデータ情報(ファイルの所有者・所有グループなど)を格納する。取得されたファイルの所有者情報は、次のパッチで追加されたチェックで判定される。
[2] 次にkuid_has_mapping関数を呼び出し、先ほど取得したファイルの所有者情報と所有グループ情報を判定する。ターゲットファイルの所有者と所有グループが現在のユーザー名前空間にマッピングされているかをチェックする。
[2.1] kuid_has_mapping関数は2つの引数(struct user_namespaceユーザー名前空間構造体とstruct kuidカーネルユーザー構造体)を受け取り、指定されたユーザー情報が指定されたユーザー名前空間にマッピングされているかを判定する。名前空間におけるユーザーのマッピングについては後で詳しく説明する。
つまり、現在この脆弱性のある関数(ovl_copy_up_one)の操作を実行する際、もしターゲットの下層ファイルの所有者ユーザーまたは所有グループユーザーが現在の名前空間にマッピングされていない場合、処理に失敗するというのがパッチの意味である。
これでパッチの原理は明らかになった。しかし、それでも以下の問題を解決しなければ、この脆弱性の再現は完了しない:
ovl_copy_up_oneが存在するロジック、すなわちオーバーレイファイルシステムで下層ファイルが上層にコピーされる処理をどのようにトリガーするか?lowerpathは、上記のロジックチェーンでどのような役割を果たすのか?これらの疑問を解く前に、いくつかの基本知識を整理しておく必要がある:
(chatGPTへの質問:Linuxカーネルにおける名前空間について説明してください)
Linuxでは、名前空間(namespaces)はリソース分離を実現するためのカーネル機能です。名前空間を使用することで、一連のプロセスがあたかも独立したシステム環境で実行されているかのように見せることができ、システムのセキュリティと管理性が向上します。名前空間はコンテナ技術(Dockerなど)で重要な役割を果たし、コンテナが他のコンテナやホストシステムに影響を与えずに隔離された環境で実行されることを可能にします。
Linuxカーネルは7種類の名前空間(mount、pid、net、ipc、user、time、cgroup)をサポートしており、各名前空間は特定のシステムリソースを分離します。名前空間はclone、unshare、setnsなどのシステムコールを通じて作成、変更、管理されます。コンテナランタイム(Dockerなど)やその他の仮想化ツールはこれらの名前空間機能を活用して、コンテナに独立した隔離された実行環境を提供します。
脆弱性パッチで追加された判定関数kuid_has_mappingは、上記7つの名前空間のうち**ユーザー名前空間(user namespace)**に関連します。
(chatGPTへの質問:この中のユーザー名前空間について説明してください)
ユーザー名前空間(User Namespace)は、ユーザーID(UID)とグループID(GID)を分離するために使用されます。ユーザー名前空間を使用すると、異なる名前空間で独立したユーザー・グループIDのセットを使用できます。つまり、あるユーザー名前空間のユーザーやグループは、別の名前空間では異なるIDや権限を持つ可能性があります。ユーザー名前空間は、特にコンテナ化環境において、システムのセキュリティと管理性を向上させます。
ユーザー名前空間の重要な特性はIDマッピングです。ユーザー名前空間は、ある名前空間のUID/GIDを別の名前空間のUID/GIDにマッピングすることを可能にします。つまり、異なるユーザー名前空間では、同じUID/GIDが別のユーザーやグループを表す可能性があります。例えば、コンテナ内のrootユーザー(UID 0)は、ホストシステムでは非特権ユーザーにマッピングされることがあります。
以下の点だけ覚えておけば十分です:
/proc/[pid]/uid_map; /proc/[pid]/gid_mapを変更する)。通常この操作には初期名前空間のroot権限が必要例として、breezeユーザーで新しいユーザー名前空間を作成し、そのユーザー名前空間内でroot所有のファイルを表示すると、所有グループがnobodyと表示される:

これは、新しい名前空間ではrootユーザーはその名前空間を作成したbreezeユーザーであり、初期名前空間のrootは手動で新しい名前空間にマッピングされていないため、新しい名前空間ではnobodyとして認識されるからです。
ここで、このパッチの意味がわかります:**コピー対象のオーバーレイ下層ファイルシステムのファイルについて、その所有者(グループ)ユーザー(グループ)が現在の名前空間にマッピングされている場合のみ、以降のコピー動作を継続し、そうでなければエラーを返す。**つまり、nobodyと認識されるようなケースはコピーに失敗するということです。
(chatGPTへの質問:Linuxにおけるオーバーレイファイルシステムについて説明してください)
オーバーレイファイルシステム(OverlayFS)は、Linuxカーネルの仮想ファイルシステムです。2つ以上の既存のディレクトリ階層("lower"および"upper"レイヤーと呼ばれる)を1つの統合されたビューにマージすることを可能にします。オーバーレイファイルシステムは、読み取り専用ファイルシステム(イメージなど)に書き込み操作を実装する場合に非常に有用であり、書き込み操作を重ね書き可能なレイヤーにリダイレクトできます。この方法はコンテナ技術(Dockerなど)で広く利用されており、軽量で高性能なファイルシステム仮想化ソリューションを提供します。
以下の図は、あるオーバーレイファイルシステムのディレクトリにおける実際の上下レイヤーファイルがmergeレイヤーのファイルに対応する効果を示しています:

上層ファイルシステムは書き込み可能なので、ユーザーが上層のファイルを変更する場合は直接変更されます。しかし、ユーザーが下層ファイルシステムのファイル(図のfile Dなど)を変更しようとする場合、下層ファイルシステムは読み取り専用であるため、file Dは上層にコピー(copy up)されてfile D'になり、その後変更操作が行われます。実際に変更されるのは上層にコピーされたfile D'であり、下層ファイルシステムのfile D自体は変更されません。これがオーバーレイファイルシステムにおけるCOW(copy on write、コピーオンライト)です:

(chatGPTへの質問:簡単なオーバーレイファイルシステムを作成する実際の操作例を教えてください)
以下の方法で、オーバーレイファイルシステムの作成方法を簡単に示します:
まず、lower1、lower2、upper、workディレクトリを作成します。これらのディレクトリはオーバーレイファイルシステムに使用されます。同時に、マウントポイント(例:merged)を作成してマージされたビューにアクセスします。そして、lower1およびlower2ディレクトリにいくつかのコンテンツを追加します:
mkdir lower1 lower2 upper work merged
echo "This is a file in lower1." > lower1/file1.txt
echo "This is a file in lower2." > lower2/file2.txt
mountコマンドと-t overlayオプションを使用してオーバーレイファイルシステムをマウントします。lowerdir、upperdir、workdirパラメータを以下のように指定します:
mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged
mergeディレクトリで上下レイヤーのファイルを確認できます:

このディレクトリ内で新しいファイルを作成、ファイルを削除、ファイルを変更しても、上層ファイルシステムのみが変更され、下層には影響しません。例えば、新しいファイルを作成した場合(実際にはupperに作成されます):

既存のファイルを変更する場合(ファイルをlower1からupperにコピーしてから変更):