
CVE-2023-3269: Vulnérabilité d'élévation de privilèges du noyau Linux
(exploit vérifié par GitHub CI)
Une faille a été découverte dans la gestion de l'expansion de la pile dans le noyau Linux 6.1 à 6.4, également appelée « Stack Rot ». L'arbre maple, responsable de la gestion des zones de mémoire virtuelle, peut subir un remplacement de nœud sans avoir correctement acquis le verrou d'écriture MM, ce qui conduit à des problèmes d'utilisation après libération (use-after-free). Un utilisateur local non privilégié pourrait utiliser cette faille pour compromettre le noyau et élever ses privilèges.
Comme StackRot est une vulnérabilité du noyau Linux trouvée dans le sous-système de gestion de la mémoire, elle affecte presque toutes les configurations du noyau et nécessite des capacités minimales pour être déclenchée. Cependant, il convient de noter que les nœuds maple sont libérés à l'aide de callbacks RCU, retardant la désallocation réelle de la mémoire jusqu'à après la période de grâce RCU. Par conséquent, l'exploitation de cette vulnérabilité est considérée comme difficile.
À ma connaissance, il n'existe actuellement aucun exploit disponible publiquement ciblant les bugs de type use-after-free-by-RCU (UAFBR). Cela marque la première fois que les bugs UAFBR se sont avérés exploitables, même sans la présence des paramètres CONFIG_PREEMPT ou CONFIG_SLAB_MERGE_DEFAULT. Notamment, cet exploit a été démontré avec succès dans l'environnement fourni par Google kCTF VRP (bzImage_upstream_6.1.25, config).
La vulnérabilité StackRot est présente dans le noyau Linux depuis la version 6.1, lorsque la structure de l'arbre VMA a été changée des arbres rouge-noir (red-black trees) aux arbres maple.
Chaque fois que l'appel système mmap() est utilisé pour établir un mappage mémoire, le noyau génère une structure appelée vm_area_struct pour représenter la zone de mémoire virtuelle correspondante (VMA). Cette structure stocke diverses informations, notamment des drapeaux (flags), des propriétés et d'autres détails pertinents liés au mappage.```c
struct vm_area_struct {
long unsigned int vm_start; /* 0 8 /
long unsigned int vm_end; / 8 8 /
struct mm_struct * vm_mm; / 16 8 /
pgprot_t vm_page_prot; / 24 8 /
long unsigned int vm_flags; / 32 8 /
union {
struct {
struct rb_node rb attribute((aligned(8))); / 40 24 /
/ --- cacheline 1 boundary (64 bytes) --- /
long unsigned int rb_subtree_last; / 64 8 /
} attribute((aligned(8))) shared attribute((aligned(8))); / 40 32 /
struct anon_vma_name * anon_name; / 40 8 /
} attribute((aligned(8))); / 40 32 /
/ --- cacheline 1 boundary (64 bytes) was 8 bytes ago --- /
struct list_head anon_vma_chain; / 72 16 /
struct anon_vma * anon_vma; / 88 8 /
const struct vm_operations_struct * vm_ops; / 96 8 /
long unsigned int vm_pgoff; / 104 8 /
struct file * vm_file; / 112 8 /
void * vm_private_data; / 120 8 /
/ --- cacheline 2 boundary (128 bytes) --- /
atomic_long_t swap_readahead_info; / 128 8 /
struct vm_userfaultfd_ctx vm_userfaultfd_ctx; / 136 0 */
/* size: 136, cachelines: 3, members: 14 */
/* forced alignments: 1 */
/* last cacheline: 8 bytes */
} attribute((aligned(8)));
Par la suite, lorsque le noyau rencontre des défauts de page ou d'autres appels système liés à la mémoire, il nécessite une recherche rapide de la VMA uniquement basée sur l'adresse. Auparavant, les VMA étaient gérées à l'aide d'arbres rouge-noir. Cependant, à partir de la version 6.1 du noyau Linux, la migration vers les arbres érables a eu lieu. Les [arbres érables][mt] sont des structures de données d'arbre B protégées par RCU, optimisées pour stocker des intervalles non chevauchants. Néanmoins, leur nature complexe ajoute de la complexité à la base de code et introduit la vulnérabilité StackRot.
[mt]: https://docs.kernel.org/6.4/core-api/maple_tree.html
Au cœur, un arbre érable est composé de nœuds érables. Bien que la structure de l'arbre puisse être complexe, il est important de noter que cette complexité n'a rien à voir avec le bogue StackRot. Par conséquent, dans cet article, il est supposé que l'arbre érable ne comprend qu'un seul nœud, c'est-à-dire le nœud racine.
Ce nœud racine peut contenir jusqu'à 16 intervalles. Ces intervalles peuvent soit représenter un espace vide, soit pointer vers une VMA. Comme les espaces vides comptent également comme des intervalles, tous les intervalles sont connectés séquentiellement, ce qui nécessite seulement 15 points d'extrémité, également appelés pivots, dans la structure du nœud. Notez que le point d'extrémité le plus à gauche et le point d'extrémité le plus à droite sont omis, car ils peuvent être récupérés à partir du nœud parent.```c
struct maple_range_64 {
struct maple_pnode * parent; /* 0 8 */
long unsigned int pivot[15]; /* 8 120 */
/* --- cacheline 2 boundary (128 bytes) --- */
union {
void * slot[16]; /* 128 128 */
struct {
void * pad[15]; /* 128 120 */
/* --- cacheline 3 boundary (192 bytes) was 56 bytes ago --- */
struct maple_metadata meta; /* 248 2 */
}; /* 128 128 */
}; /* 128 128 */