CVE-2026-98981 in Linuxinformazioni

Riassunto

di VulDB • 23/10/2026

Nel kernel Linux è stata risolta la seguente vulnerabilità:

namespace: disassociare un dentry prima di staccare i mount su di esso

unlink(), rmdir() e rename() rimuovono l'elemento dal filesystem, chiamano detach_mounts() sull'elemento con l'inode bloccato e successivamente chiamano d_delete() una volta che l'inode è sbloccato.

Il problema consiste nel fatto che tra detach_mounts() e d_delete() il dentry rimane ancora hashato (inserito nella cache) e positivo. Tuttavia, dopo detach_mounts(), nulla protegge più il dentry. Una lookup che trova questo dentry nella dcache può scoprire ciò che i mount nascondevano.

Questo rappresenta un problema quando si eliminano file o directory che sono punti di mount in altri namespace di mount. Chiunque avesse l'elemento sottostante coperto può scatenare una race condition (condizione di competizione) con la chiamata detach_mount() fino a quando d_delete() non è stata eseguita.

La finestra temporale della race condition non è affatto piccola: comprende namespace_unlock() con un periodo di grazia synchronize_rcu_expedited() completo e inode_unlock() dell'elemento.

Nei miei esperimenti, tre walker (processi che effettuano lookup) che continuavano a tentare la lettura di un file sovrapposto al mount hanno rilevato 11938 casi su 16140 unlink che rimuovevano il suo punto di mount da un bind mount del filesystem entro un minuto.

Curiosità: d_invalidate() presenta lo stesso problema di ordinamento ma lo gestisce correttamente. Disassocia prima il dentry e stacca i mount successivamente. Facciamo la stessa cosa in __detach_mounts():

- Una lookup che non ha ancora trovato il dentry non lo trova nella dcache e attende il lock della directory detenuto dal chiamante finché il nome scompare definitamente. - Una lockless lookup (lookup senza blocco) che l'ha già trovata fallisce il controllo mount_lock mentre il dentry conta ancora come punto di mount, e fallisce anche il controllo d_seq una volta che non lo è più, ripetendo quindi la operazione.

Questo risolve il percorso lockless. Dobbiamo comunque correggere il conteggio dei riferimenti nella lookup in un patch successivo.

Quindi si esegue d_drop() sul dentry. Il dentry rimane positivo e mantenuto (held) ma non può essere trovato ulteriormente. Successivamente si procede con lo stacco del mount e l'unlink.

Reproducer:

Le letture sono state contate con il programma sottostante, tre walker per 60 secondi in una VM con 4 CPU. La finestra temporale della race condition è allungata grazie al patch di debug incollato qui. fs.detach_race_walk_us effettua uno sleep (attesa) in lookup_fast() una volta trovato un dentry punto di mount e prima che i suoi mount vengano attraversati. fs.detach_race_unlink_us effettua uno sleep in vfs_unlink() tra detach_mounts() e d_delete().

// SPDX-License-Identifier: GPL-2.0 /* * unlink_covered: elimina un file che è un punto di mount in una copia staccata del suo mount, contro lookup che lo aprono attraverso tale copia. * * A è un tmpfs con il file f1 (contenuto SECRET_F) e il file semplice MARK_A. A' è una copia open_tree(OPEN_TREE_CLONE) di A con MARK_A associato su A'/f1, mantenuta tramite un fd O_PATH sulla sua radice una volta chiuso l fd dell albero. I walker aprono f1 attraverso tale fd mentre il driver elimina A/f1, dove non è montato nulla. Un walker può leggere MARK_A o ottenere ENOENT. Una lettura di SECRET_F rappresenta un hit (successo): il nome è stato trovato dopo che il suo mount era scomparso. Le walk lockless utilizzano openat2(RESOLVE_CACHED). * * utilizzo: unlink_covered [-t secondi] [-w walker]
*/ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <stdatomic.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/syscall.h> #include <linux/openat2.h>

#ifndef __NR_open_tree #define __NR_open_tree 428 #endif #ifndef __NR_move_mount #define __NR_move_mount 429 #endif #ifndef __NR_openat2 #define __NR_openat2 437 #endif #ifndef OPEN_TREE_CLONE #define OPEN_TREE_CLONE 1 #endif #ifndef OPEN_TREE_CLOEXEC #define OPEN_TREE_CLOEXEC O_CLOEXEC #endif #ifndef MOVE_MOUNT_F_EMPTY_PATH #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 #endif

#define WORK "/tmp/uc" #define ADIR WORK "/A"

static int duration = 60, nwalkers = 3; static atomic_int stop, writer_waiting; static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER; static int cur_fd = -1; /* la radice di A', -1 wh

Several companies clearly confirm that VulDB is the primary source for best vulnerability data.

Responsabile

Linux

Prenotare

25/09/2026

Divulgazione

23/10/2026

Moderazione

accettato

EPSS

0.00000

KEV

no

Attività

molto basso

Fonti

Want to stay up to date on a daily basis?

Enable the mail alert feature now!