
POC de la CVE-2022-0185, Docker et write-up d'analyse
[toc]
Identifiant de la vulnérabilité : CVE-2022-0185
Score de la vulnérabilité :
Produit concerné : noyau linux - fsconfig syscall
Versions affectées : noyau linux 5.1-rc1 ~ 5.16.2
Conditions d'exploitation : linux local ; disposer de la capacité CAP_SYS_ADMIN (peut être obtenue directement via unshare, donc sans restriction)
Impact : élévation de privilèges locale ; évasion de conteneur
Obtention du code source : 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
ou https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/
Docker pour la compilation du noyau 5.X : chenaotian/kernelcompile
Docker d'analyse de la vulnérabilité : chenaotian/cve-2022-0185
Préparé deux noyaux : un noyau de distribution et une version compilée soi-même
Installer qemu, gdb, gdb-peda, etc.
Les éléments liés à la vulnérabilité se trouvent dans /root/cve-2022-0185
boot_exp.sh sert à démarrer l'exp pour valider l'environnement de débogage, avec le noyau de distribution 5.11.0-44 sans symbolesboot_poc.sh sert à démarrer le poc pour valider l'environnement ; il peut faire planter le noyau mais ne peut pas exécuter l'exp, avec le noyau 5.13 compilé avec symbolesexp, code source de l'exp (auteur : BitsByWill), il suffit de compiler directement exploit_fuse.Environnement qemu : https://github.com/chenaotian/CVE-2022-0185/tree/main/qemuANDexp
Environnement d'exécution de l'exp sur une machine virtuelle Ubuntu 20.04 avec l'exp de l'auteur original
Préparer une machine virtuelle Ubuntu 20.04, puis remplacer le noyau :```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
Effet d'élévation de privilèges

## Principe de la vulnérabilité
L'appel système concerné par la vulnérabilité est l'option d'opération `FSCONFIG_SET_STRING` dans `fsconfig`. Cet appel système est utilisé pour configurer un contexte de système de fichiers déjà ouvert. **La condition préalable requise est de disposer de la capacité `CAP_SYS_ADMIN`** :
> Le but principal de `fsopen` est de créer un contexte de système de fichiers, puis de l'associer à un descripteur de fichier et de renvoyer ce descripteur. Après `fsopen` vient `fsconfig`. Comme on peut le deviner d'après le sens littéral, nous avons créé un contexte de système de fichiers via `fsopen` ci-dessus ; le `fsconfig` suivant sert probablement à configurer le contenu du contexte de système de fichiers. En fait, `fsconfig` sert principalement à effectuer ce travail de configuration. Outre le contexte de système de fichiers, il prend également en charge d'autres tâches.
### Point de déclenchement de la vulnérabilité
Tout d'abord, la vulnérabilité apparaît dans la fonction `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;
}
Le point clé réside dans le memcpy situé plus bas, qui copie notre param->string passé dans ctx->legacy_data. La vérification du dépassement de copie se fait dans le (len > PAGE_SIZE - 2 - size) précédent ; cette vérification est erronée. Le type de comparaison est size_t, c'est-à-dire unsigned int : si size > PAGE_SIZE - 2, un débordement d'entier avec inversion se produit, d'où len < PAGE_SIZE - 2 - size, et la vérification est donc validée. Lors de la copie ultérieure, size étant supérieur à PAGE_SIZE - 2, cela provoque une copie hors limites.
Quelques structures de données utilisées :```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; };
### Chemin d'appel
Analysons ci-dessous la pile d'appels de fonctions ; tout d'abord, l'entrée est bien évidemment l'appel système `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;