diff options
author | Mandeep Singh Baines <msb@google.com> | 2009-01-17 13:31:48 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-01-18 13:20:17 -0500 |
commit | 603a148f434742fff08273207ffa44176cad13a1 (patch) | |
tree | e0c051efd07a7b51390e13169f4c78a29c676fc1 /kernel | |
parent | af432eb1cc3178ec7109aca2283aafb1c12ccac1 (diff) |
softlockup: fix potential race in hung_task when resetting timeout
Impact: fix potential false panic
A potential race exists if sysctl_hung_task_timeout_secs is reset to 0
while inside check_hung_uniterruptible_tasks(). If check_task() is
entered, a comparison with 0 will result in a false hung_task being
detected.
If sysctl_hung_task_panic is set, the system will panic.
Signed-off-by: Mandeep Singh Baines <msb@google.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/hung_task.c | 24 |
1 files changed, 16 insertions, 8 deletions
diff --git a/kernel/hung_task.c b/kernel/hung_task.c index ba5a77cad3bb..ba8ccd432963 100644 --- a/kernel/hung_task.c +++ b/kernel/hung_task.c | |||
@@ -72,7 +72,8 @@ static unsigned long get_timestamp(void) | |||
72 | return cpu_clock(this_cpu) >> 30LL; /* 2^30 ~= 10^9 */ | 72 | return cpu_clock(this_cpu) >> 30LL; /* 2^30 ~= 10^9 */ |
73 | } | 73 | } |
74 | 74 | ||
75 | static void check_hung_task(struct task_struct *t, unsigned long now) | 75 | static void check_hung_task(struct task_struct *t, unsigned long now, |
76 | unsigned long timeout) | ||
76 | { | 77 | { |
77 | unsigned long switch_count = t->nvcsw + t->nivcsw; | 78 | unsigned long switch_count = t->nvcsw + t->nivcsw; |
78 | 79 | ||
@@ -84,8 +85,7 @@ static void check_hung_task(struct task_struct *t, unsigned long now) | |||
84 | t->last_switch_timestamp = now; | 85 | t->last_switch_timestamp = now; |
85 | return; | 86 | return; |
86 | } | 87 | } |
87 | if ((long)(now - t->last_switch_timestamp) < | 88 | if ((long)(now - t->last_switch_timestamp) < timeout) |
88 | sysctl_hung_task_timeout_secs) | ||
89 | return; | 89 | return; |
90 | if (!sysctl_hung_task_warnings) | 90 | if (!sysctl_hung_task_warnings) |
91 | return; | 91 | return; |
@@ -96,8 +96,7 @@ static void check_hung_task(struct task_struct *t, unsigned long now) | |||
96 | * complain: | 96 | * complain: |
97 | */ | 97 | */ |
98 | printk(KERN_ERR "INFO: task %s:%d blocked for more than " | 98 | printk(KERN_ERR "INFO: task %s:%d blocked for more than " |
99 | "%ld seconds.\n", t->comm, t->pid, | 99 | "%ld seconds.\n", t->comm, t->pid, timeout); |
100 | sysctl_hung_task_timeout_secs); | ||
101 | printk(KERN_ERR "\"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\"" | 100 | printk(KERN_ERR "\"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\"" |
102 | " disables this message.\n"); | 101 | " disables this message.\n"); |
103 | sched_show_task(t); | 102 | sched_show_task(t); |
@@ -115,7 +114,7 @@ static void check_hung_task(struct task_struct *t, unsigned long now) | |||
115 | * a really long time (120 seconds). If that happens, print out | 114 | * a really long time (120 seconds). If that happens, print out |
116 | * a warning. | 115 | * a warning. |
117 | */ | 116 | */ |
118 | static void check_hung_uninterruptible_tasks(void) | 117 | static void check_hung_uninterruptible_tasks(unsigned long timeout) |
119 | { | 118 | { |
120 | int max_count = sysctl_hung_task_check_count; | 119 | int max_count = sysctl_hung_task_check_count; |
121 | unsigned long now = get_timestamp(); | 120 | unsigned long now = get_timestamp(); |
@@ -134,7 +133,7 @@ static void check_hung_uninterruptible_tasks(void) | |||
134 | goto unlock; | 133 | goto unlock; |
135 | /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */ | 134 | /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */ |
136 | if (t->state == TASK_UNINTERRUPTIBLE) | 135 | if (t->state == TASK_UNINTERRUPTIBLE) |
137 | check_hung_task(t, now); | 136 | check_hung_task(t, now, timeout); |
138 | } while_each_thread(g, t); | 137 | } while_each_thread(g, t); |
139 | unlock: | 138 | unlock: |
140 | read_unlock(&tasklist_lock); | 139 | read_unlock(&tasklist_lock); |
@@ -180,8 +179,17 @@ static int watchdog(void *dummy) | |||
180 | update_poll_jiffies(); | 179 | update_poll_jiffies(); |
181 | 180 | ||
182 | for ( ; ; ) { | 181 | for ( ; ; ) { |
182 | unsigned long timeout; | ||
183 | |||
183 | while (schedule_timeout_interruptible(hung_task_poll_jiffies)); | 184 | while (schedule_timeout_interruptible(hung_task_poll_jiffies)); |
184 | check_hung_uninterruptible_tasks(); | 185 | |
186 | /* | ||
187 | * Need to cache timeout here to avoid timeout being set | ||
188 | * to 0 via sysctl while inside check_hung_*_tasks(). | ||
189 | */ | ||
190 | timeout = sysctl_hung_task_timeout_secs; | ||
191 | if (timeout) | ||
192 | check_hung_uninterruptible_tasks(timeout); | ||
185 | } | 193 | } |
186 | 194 | ||
187 | return 0; | 195 | return 0; |