
CVE-2022-0185 POC و Docker وتقرير التحليل
[toc]
رقم الثغرة: CVE-2022-0185
تقييم الثغرة:
المنتج المتأثر: linux kernel - fsconfig syscall
النطاق المتأثر: linux kernel 5.1-rc1 ~ 5.16.2
شروط الاستغلال: محلي على Linux; يتطلب صلاحية CAP_SYS_ADMIN (يمكن الحصول عليها مباشرة عن طريق 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/
بيئة Docker لترجمة نواة 5.X: chenaotian/kernelcompile
Docker لتحليل الثغرة: chenaotian/cve-2022-0185
/root/cve-2022-0185boot_exp.sh لتشغيل exp والتحقق من بيئة التصحيح، نواة غير موقعة 5.11.0-44boot_poc.sh لتشغيل poc والتحقق من البيئة، يمكن أن يتسبب في تعطل النواة لكن لا يمكن تشغيل exp، نواة موقعة مترجمة ذاتيًا 5.13exp، كود مصدر exp (المؤلف: BitsByWill)، قم بترجمة exploit_fuse مباشرة.بيئة qemu: https://github.com/chenaotian/CVE-2022-0185/tree/main/qemuANDexp
بيئة تشغيل exp على جهاز افتراضي Ubuntu 20.04 لتشغيل exp الأصلي
قم بإعداد جهاز افتراضي Ubuntu 20.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_SET_STRING` في `fsconfig`، ويُستخدم هذا الاستدعاء النظامي لتهيئة سياق نظام الملفات المفتوح مسبقًا، **يتطلب الشرط المسبق امتلاك صلاحية `CAP_SYS_ADMIN`**:
> الغرض الرئيسي من `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، يتم أولاً تهيئة هيكل السياق لنظام الملفات fc استنادًا إلى واصف الملف fd، ثم يتم تعيين هيكل param وفقًا للمعاملات التي يمررها المستخدم. متغير الهيكل هذا هو param المستخدم لاحقًا في دالة حدوث الثغرة legacy_parse_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;