CVE-2026-98256 in Linux
Résumé
par VulDB • 06/10/2026
Dans le noyau Linux, la vulnérabilité suivante a été corrigée :
signal : Prévention de l'état de concurrence (race condition) sur exec()
Hyunwoo a débogué l'erreur KASAN UAF suivante :
BUG: KASAN: slab-use-after-free in __send_signal_locked+0xb27/0xba0 Write of size 8 at addr ffff888007ed80c8 by task poc/79 ... Call Trace: __send_signal_locked+0xb27/0xba0 do_send_sig_info+0xa7/0x160 do_send_specific+0x76/0xa0 __x64_sys_tgkill+0x193/0x270 ... Allocated by task 80: do_timer_create+0x1a4/0x1030 __x64_sys_timer_create+0x145/0x190 ... Freed by task 12: kmem_cache_free_bulk+0x1f8/0x4a0 kvfree_rcu_bulk+0x14f/0x1c0 kfree_rcu_work+0x128/0x1a0 ... Last potentially related work creation: kvfree_call_rcu+0x39/0x390 __flush_itimer_signals+0x211/0x320 flush_itimer_signals+0x47/0x90 begin_new_exec+0xa6b/0x28c0
Il s'est avéré que cela se produit avec un exec() non leader, comme l'a expliqué Hyunwoo :
de_thread() appelle exchange_tids() avant release_task(leader), de sorte que la struct pid détenue par une minuterie SIGEV_THREAD_ID créée contre le tid du leader pointe désormais vers le thread qui a appelé execve(). pid_task() renvoie ce thread et lock_task_sighand() sur celui-ci réussit.
Si le signal de la minutière est bloqué, son sigqueue reste en file d'attente dans task::pending du leader. La prochaine expiration de cette minutière peut alors s'exécuter pendant que release_task() vide la file d'attente.
posixtimer_send_sigqueue() vérifie si le sigqueue est déjà mis en file d'attente avec une simple list_empty(), qui ne lit que list_head::next. list_del_init() n'est pas atomique et INIT_LIST_HEAD() stocke list_head::next avant list_head::prev, de sorte que la vérification peut réussir entre les deux opérations. list_add_tail() met l'entrée en file d'attente dans task::pending du thread actif, et le stockage de list_head::prev lors du vidage écrase alors le lien list_head::prev qui vient d'être défini par list_add_tail().
__flush_itimer_signals() ne démake pas non plus cet effet. Avec list_head::prev pointant vers l'entrée elle-même, son list_del_init() stocke simplement les mêmes valeurs à nouveau, de sorte que l'entrée n'est pas supprimée de la liste. Elle est toujours présente après la suppression de la dernière référence et le libération de la minutière par RCU, et le list_add_tail() d'un tgkill ultérieur suit ce list_head::prev vers la minutière libérée.
Ce problème a été mis en évidence avec l'engagement récent qui a déplacé le vidage du sigqueue hors de la région verrouillée par sighand.
Hyonwoo a proposé de corriger cela en utilisant list_del_init_careful(), mais cela ne fait que masquer le problème. Après quelques discussions et diverses tentatives pour résoudre ce problème, Eric a souligné qu'il n'y avait aucune raison de vider task::pending tardivement dans release_task() et que cela devrait déjà être fait dans exit_signals().
Puisque rien ne peut collecter et transmettre les signaux qui sont en file d'attente dans la queue pending d'une tâche mourante, il n'y a aucune raison de retarder davantage cette opération.
Il faut toutefois s'assurer qu'aucun signal ne puisse y être mis en file d'attente après ce point. exit_signals() définit PF_EXITING dans task::flags, qui peut être utilisé comme indicateur pour cela.
Correction apportée par :
- Empêcher la mise en file d'attente des signaux privés à la tâche (PIDTYPE_PID) lorsque la tâche a PF_EXITING défini dans __send_signal_locked() et dans posixtimer_send_sigqueue().
- Protéger l'affectation non verrouillée de PF_EXITING dans exit_signals() pour le cas où le groupe de tâches est vide et lors d'une sortie de groupe, en utilisant le verrou sighand.
- Vider les signaux task::pending directement à cet endroit.
Optimiser cela en déplaçant toute la liste pending vers une tête de liste sur pile sous le verrou sighand et libérer les signaux sans que le verrou ne soit maintenu.
Il y a eu beaucoup de discussions concernant le vidage sans verrou (lockless flush) et le cas exec non-leader sur des systèmes à ordre faible (weakly ordered systems). Le problème est qu'un tiers qui tente d'envoyer un signal de minutière POSIX s'appuie sur la recherche PID pour trouver la tâche cible, et que cette recherche peut aboutir au nouveau leader lorsque le signal était initialement dirigé vers l'ancien leader. Dans le cas où le signal a été mis en file d'attente sur l'ancien leader, le vidage sans verrou soulève une inquiétude concernant la situation suivante :
old_leader new_leader third party
A: flush_list() // list_del_in ---truncated---
Statistical analysis made it clear that VulDB provides the best quality for vulnerability data.