Skip to content

Commit

Permalink
s390/pfault: fix task state race
Browse files Browse the repository at this point in the history
When setting the current task state to TASK_UNINTERRUPTIBLE this can
race with a different cpu. The other cpu could set the task state after
it inspected it (while it was still TASK_RUNNING) to TASK_RUNNING which
would change the state from TASK_UNINTERRUPTIBLE to TASK_RUNNING again.

This race was always present in the pfault interrupt code but didn't
cause anything harmful before commit f2db2e6 "[S390] pfault: cpu hotplug
vs missing completion interrupts" which relied on the fact that after
setting the task state to TASK_UNINTERRUPTIBLE the task would really
sleep.
Since this is not necessarily the case the result may be a list corruption
of the pfault_list or, as observed, a use-after-free bug while trying to
access the task_struct of a task which terminated itself already.

To fix this, we need to get a reference of the affected task when receiving
the initial pfault interrupt and add special handling if we receive yet
another initial pfault interrupt when the task is already enqueued in the
pfault list.

Signed-off-by: Heiko Carstens <[email protected]>
Reviewed-by: Martin Schwidefsky <[email protected]>
Cc: <[email protected]> # needed for v3.0 and newer
Signed-off-by: Martin Schwidefsky <[email protected]>
  • Loading branch information
heicarst authored and Martin Schwidefsky committed May 16, 2012
1 parent 473e66b commit d5e50a5
Showing 1 changed file with 12 additions and 2 deletions.
14 changes: 12 additions & 2 deletions arch/s390/mm/fault.c
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ static void pfault_interrupt(struct ext_code ext_code,
tsk->thread.pfault_wait = 0;
list_del(&tsk->thread.list);
wake_up_process(tsk);
put_task_struct(tsk);
} else {
/* Completion interrupt was faster than initial
* interrupt. Set pfault_wait to -1 so the initial
Expand All @@ -588,14 +589,22 @@ static void pfault_interrupt(struct ext_code ext_code,
put_task_struct(tsk);
} else {
/* signal bit not set -> a real page is missing. */
if (tsk->thread.pfault_wait == -1) {
if (tsk->thread.pfault_wait == 1) {
/* Already on the list with a reference: put to sleep */
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
set_tsk_need_resched(tsk);
} else if (tsk->thread.pfault_wait == -1) {
/* Completion interrupt was faster than the initial
* interrupt (pfault_wait == -1). Set pfault_wait
* back to zero and exit. */
tsk->thread.pfault_wait = 0;
} else {
/* Initial interrupt arrived before completion
* interrupt. Let the task sleep. */
* interrupt. Let the task sleep.
* An extra task reference is needed since a different
* cpu may set the task state to TASK_RUNNING again
* before the scheduler is reached. */
get_task_struct(tsk);
tsk->thread.pfault_wait = 1;
list_add(&tsk->thread.list, &pfault_list);
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
Expand All @@ -620,6 +629,7 @@ static int __cpuinit pfault_cpu_notify(struct notifier_block *self,
list_del(&thread->list);
tsk = container_of(thread, struct task_struct, thread);
wake_up_process(tsk);
put_task_struct(tsk);
}
spin_unlock_irq(&pfault_lock);
break;
Expand Down

0 comments on commit d5e50a5

Please sign in to comment.