
[toc]
脆弱性ID: CVE-2022-0185
脆弱性スコア:
脆弱性製品: Linux カーネル - fsconfig syscall
影響範囲: Linux カーネル 5.1-rc1 ~ 5.16.2
利用条件: Linux ローカル; CAP_SYS_ADMIN cap権限を保有(unshare で直接取得できるため、事実上無制限)
利用効果: ローカル権限昇格; コンテナエスケープ
ソースコード取得: git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git -b Ubuntu-hwe-5.11-5.11.0-27.29_20.04.1 --depth 1
または https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/
5.x カーネルコンパイル環境 Docker :chenaotian/kernelcompile
脆弱性解析用 Docker:chenaotian/cve-2022-0185
2つのカーネルを用意: ディストリビューション版と自作コンパイル版
qemu、gdb、gdb-peda などをインストール
脆弱性関連ファイルは /root/cve-2022-0185 に配置
boot_exp.sh: exp起動検証デバッグ環境用、ディストリビューション版5.11.0-44のシンボルなしカーネルboot_poc.sh: poc起動検証環境用、カーネルをクラッシュさせられるがexpは実行不可、自作コンパイル5.13シンボル付きカーネルexp ディレクトリ、exp ソースコード(作者: BitsByWill)、exploit_fuse を直接コンパイルqemu 環境:https://github.com/chenaotian/CVE-2022-0185/tree/main/qemuANDexp
ubuntu 20.04 仮想マシン上で原作者 exp を実行する環境
ubuntu20.04 仮想マシンを準備し、カーネルを入れ替え:```shell apt-get install linux-image-5.11.0-44-generic
grep menuentry /boot/grub/grub.cfg vim /etc/default/grub #修改 GRUB_DEFAULT 选项为上面结果中想要启动内核的下标 update-grub #如果不生效的话则直接进入/boot 目录将之前的内核相关文件(带之前内核编号的文件)全部删掉,然后启动时候报找不到内核,然后手动选择内核启动也可以
#编译exp make fuse ./exploit
提权效果

## 脆弱性の原理
脆弱性が発生するシステムコールは、`fsconfig` の `FSCONFIG_SET_STRING` 操作オプションです。このシステムコールは、既に開かれているファイルシステムコンテキストに対して設定を行うために使用されます。**必要な前提条件は、`CAP_SYS_ADMIN` cap権限を有していることです**:
> `fsopen`の主な目的は、ファイルシステムコンテキストを作成し、それをファイル記述子に関連付けて、ファイル記述子を返すことです。`fsopen`の後には`fsconfig`が続きます。字面から推測できるように、上記で`fsopen`を使用してファイルシステムコンテキストを作成し、次の`fsconfig`はそのファイルシステムコンテキストの内容を設定するために使用される可能性があります。実際、`fsconfig`は主にこの設定作業を行いますが、ファイルシステムコンテキスト以外にも他の作業もサポートしています。
### 脆弱性発生箇所
まず、脆弱性は `legacy_parse_param` 関数内に現れます:
linux-5.11\fs\fs_context.c : 502 : legacy_parse_param```c
static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct legacy_fs_context *ctx = fc->fs_private;
unsigned int size = ctx->data_size;
size_t len = 0;
··· ···
··· ···
switch (param->type) {
case fs_value_is_string:
len = 1 + param->size;
fallthrough;
··· ···
}
if (len > PAGE_SIZE - 2 - size) //此处边界检查有问题
return invalf(fc, "VFS: Legacy: Cumulative options too large");
if (strchr(param->key, ',') ||
(param->type == fs_value_is_string &&
memchr(param->string, ',', param->size)))
return invalf(fc, "VFS: Legacy: Option '%s' contained comma",
param->key);
if (!ctx->legacy_data) {
ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); //在第一次时会分配一页大小
if (!ctx->legacy_data)
return -ENOMEM;
}
ctx->legacy_data[size++] = ',';
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;
}
鍵となるのは後方のmemcpyで、ここで渡したparam->stringをctx->legacy_dataにコピーします。コピーが範囲外かどうかの判定は、前方の(len > PAGE_SIZE - 2 - size)で行われています。この判定には問題があり、判定の型はsize_t、つまりunsigned intです。もしsize > PAGE_SIZE - 2の場合、整数オーバーフローが反転し、len < PAGE_SIZE - 2 - sizeとなり、判定を通過してしまいます。後方のコピーではsizeがPAGE_SIZE - 2より大きいため、コピーが範囲外となります。
使用される一部のデータ構造:```c struct fs_context { const struct fs_context_operations ops; struct mutex uapi_mutex; / Userspace access mutex */ struct file_system_type *fs_type; void fs_private; / The filesystem's context */ void *sget_key; struct dentry root; / The root and superblock */ struct user_namespace user_ns; / The user namespace for this mount */ struct net net_ns; / The network namespace for this mount */ const struct cred cred; / The mounter's credentials / struct p_log log; / Logging buffer */ const char source; / The source name (eg. dev path) */ void security; / Linux S&M options / void s_fs_info; / Proposed s_fs_info / unsigned int sb_flags; / Proposed superblock flags (SB_) / unsigned int sb_flags_mask; / Superblock flags that were changed / unsigned int s_iflags; / OR'd with sb->s_iflags / unsigned int lsm_flags; / Information flags from the fs to the LSM / enum fs_context_purpose purpose:8; enum fs_context_phase phase:8; / The phase the context is in / bool need_free:1; / Need to call ops->free() / bool global:1; / Goes into &init_user_ns / bool oldapi:1; / Coming from mount(2) */ };
struct legacy_fs_context { char legacy_data; / Data page for legacy filesystems */ size_t data_size; enum legacy_fs_param param_type; };
struct fs_parameter { const char key; / Parameter name / enum fs_value_type type:8; / The type of value here */ union { char *string; void *blob; struct filename *name; struct file *file; }; size_t size; int dirfd; };
### 呼び出しパス
以下で関数呼び出しスタックを分析します。まず、エントリは間違いなく `fsconfig` システムコールです:
linux-5.11\fs\fsopen.c : 314 : SYSCALL_DEFINE5(fsconfig,...```c
SYSCALL_DEFINE5(fsconfig,
int, fd,
unsigned int, cmd,
const char __user *, _key,
const void __user *, _value,
int, aux)
{
struct fs_context *fc;
struct fd f;
int ret;
int lookup_flags = 0;
struct fs_parameter param = {
.type = fs_value_is_undefined,
};
··· ···
f = fdget(fd);
if (!f.file)
return -EBADF;
ret = -EINVAL;
if (f.file->f_op != &fscontext_fops)
goto out_f;
fc = f.file->private_data; //设置fc
··· ···
switch (cmd) {
··· ···
case FSCONFIG_SET_STRING:
param.type = fs_value_is_string;
//初始化结构体中的联合体中的string成员为用户传入的字符串
param.string = strndup_user(_value, 256);
if (IS_ERR(param.string)) {
ret = PTR_ERR(param.string);
goto out_key;
}
param.size = strlen(param.string);//设置size
break;
··· ···
··· ···
}
ret = mutex_lock_interruptible(&fc->uapi_mutex);
if (ret == 0) {
ret = vfs_fsconfig_locked(fc, cmd, ¶m);
mutex_unlock(&fc->uapi_mutex);
}
··· ···
··· ···
}
fsconfig システムコールのエントリでは、まずファイルディスクリプタfdに基づいてファイルシステムコンテキスト構造体fcを初期化し、次にユーザーから渡されたパラメータに従ってparam構造体を設定します。この構造体変数は、後で脆弱性発生関数legacy_parse_paramで使用されるparamです。次にvfs_fsconfig_locked関数に入ります:
linux-5.11\fs\fsopen.c : 216 : vfs_fsconfig_locked```c static int vfs_fsconfig_locked(struct fs_context *fc, int cmd, struct fs_parameter *param) { struct super_block *sb; int ret;
ret = finish_clean_context(fc);
if (ret)
return ret;
switch (cmd) {
··· ···
default:
if (fc->phase != FS_CONTEXT_CREATE_PARAMS &&
fc->phase != FS_CONTEXT_RECONF_PARAMS)
return -EBUSY;
return vfs_parse_fs_param(fc, param);
}
fc->phase = FS_CONTEXT_FAILED;
return ret;
}
最初に`finish_clean_context`関数を呼び出します。ここで`legacy_init_fs_context`関数を呼び出してコールバック関数テーブルを登録します。このコールバック関数テーブルには、脆弱性がある関数である`legacy_parse_param`が含まれています。