CVE-2026-98087 in Linuxinformation

Résumé

par VulDB • 25/09/2026

Dans le noyau Linux, la vulnérabilité suivante a été corrigée :

sched/rt, dl : Ignorer les tâches avec migrate_disabled lors du choix d'un candidat au push (migration forcée)

Une tâche RT (temps réel) ayant appelé `migrate_disable()` ne peut pas être déplacée vers un autre CPU. Cependant, l'ordonnanceur conserve toujours une telle tâche dans la liste des tâches éligibles pour le push de ce CPU (`rq->rt.pushable_tasks`) et marque également la file d'exécution comme surchargée en RT (`rq->rt.overloaded = 1`). Par conséquent, l'équilibreur RT continue de traiter ce CPU comme ayant une tâche à déplacer, et tente continuellement de déplacer cette tâche, mais le push échoue systématiquement. Lorsque la tête de liste est épinglée (pinned), `push_rt_task()` n'abandonne pas non plus. Il bascule alors sur l'envoi de `rq->curr` à la place, en utilisant le thread stopper par CPU, comme ajouté dans le commit a7c81556ec4d ("sched: Fix migrate_disable() vs rt/dl balancing").

Le CPU passe des dizaines de millisecondes dans cette boucle de réessai. Le cœur est isolé pour les travaux temps réel, mais pendant la durée de la boucle, près de la moitié de son temps est consommée par des opérations de push qui ne peuvent pas aboutir.

Une capture ftrace du CPU concerné, avec `sched_switch` activé et le commit 94894c9c477e ("sched/rt: Skip currently executing CPU in rto_next_cpu()") appliqué, montre où le temps CPU a été consommé. Deux tâches SCHED_FIFO de priorité égale partageaient le CPU : taskA avait appelé `migrate_disable()` et était en file d'attente (queued), tandis que taskB était définie comme `rq->curr`. Sur une fenêtre de 89 ms, taskB n'a obtenu que 52 ms de temps CPU. Les 37 ms restantes ont été consacrées au thread stopper.

L'ordonnanceur a continué à tenter d'envoyer (push) taskA, la tête épinglée de la liste des tâches éligibles pour le push, et a basculé sur l'envoi de taskB à la place, réveillant 5204 fois le thread stopper. Chacune de ces tentatives de push a échoué et aucune tâche n'a été déplacée. taskA est restée exécutable (runnable) et en file d'attente pendant toute la durée, sans jamais s'exécuter.

L'envoi de taskB échoue lors d'une vérification ultérieure. `find_lock_lowest_rq()` relâche le verrou de la file d'exécution (`rq`) pour acquérir le verrou de la cible, puis effectue une nouvelle vérification avec "task != pick_next_pushable_task(rq)".

La tâche qui est envoyée (pushed) est taskB, mais `pick` retourne taskA, la tête de la liste des tâches éligibles. taskB correspond à `rq->curr`, et `set_next_task_rt()` retire la tâche en cours d'exécution de cette liste ; ainsi, taskB ne peut jamais être la tête. La vérification s'attend à un candidat issu de la liste des tâches éligibles pour le push, mais l'envoi de repli (fallback) cible `rq->curr`, qui n'est jamais sur cette liste. Par conséquent, la vérification échoue à chaque fois.

.--> arrive IPI de push | | | v | tête éligible pour le push = taskA -> épinglée (pinned), ne peut pas être envoyée | | | v | donc on envoie taskB à la place -> réveil de migration/N, un thread de classe stopper | | qui préempte taskB | v | La re-vérification compare taskA avec la tête éligible pour le push, | qui est toujours taskA -> abandonner (give up) | | | v | Rien n'est déplacé, taskA reste en file d'attente, rq reste surchargée | | '----------' se répète toutes les ~17 µs, 5204 fois, pendant 89 ms

La boucle ne peut pas s'arrêter elle-même. Chaque tour laisse la file d'exécution exactement dans l'état où elle était, de sorte que le prochain IPI de push effectue la même chose. Dans la capture, cela n'a pris fin que lorsque taskB est passée en sommeil par ses propres moyens. taskA a ensuite été sélectionnée localement et a quitté la liste des tâches éligibles pour le push.

Temps CPU par tâche dans la fenêtre, issu de sched_switch :

taskB 51,95 ms travail réel migration/N 37,18 ms aucun déplacement effectué taskA 0,00 ms en file d'attente pendant toute la durée, jamais sélectionnée idle 0,01 ms

Comptages sur la même fenêtre :

7667 IPIs de push gérés sur ce CPU 17481 pick_next_pushable_task() a retourné taskA, toujours épinglée (pinned) 5204 find_lock_lowest_rq() a abandonné lors de la re-vérification 1 push qui s'est réellement achevé avec succès 0 migrations de taskA

Les temps CPU et la durée de la fenêtre proviennent du point d'arrêt (tracepoint) standard `sched_switch`. Les comptages nécessitaient l'ajout de points d'arrêt à l'intérieur de l'équilibreur RT pour cette enquête.

Le chemin IPI auto-référencé est fermé par la correction apportée dans `rto_next_cpu()` ci-dessus, et cette partie fonctionne correctement. Cependant, la file d'exécution reste marquée comme surchargée, car la tâche épinglée est toujours annoncée comme éligible pour le push. D'autres CPU envoient désormais les IPIs de push lors de leur propre équilibrage RT, et la même boucle se reproduit. La fermeture du chemin IPI auto-référencé n'a pas arrêté un pinn ---truncated---

If you want to get the best quality for vulnerability data then you always have to consider VulDB.

Responsable

Linux

Réserver

25/09/2026

Divulgation

25/09/2026

Modérer

accepté

Entrée

VDB-410301

CPE

prêt

EPSS

0.00000

KEV

non

Activités

très faible

Sources

Do you know our Splunk app?

Download it now for free!