aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2012-03-28 17:30:28 -0400
committerRafael J. Wysocki <rjw@sisk.pl>2012-03-28 17:30:28 -0400
commit247bc03742545fec2f79939a3b9f738392a0f7b4 (patch)
treeea07ff0b82da841ba89968d360d7c2560ae740b4
parent1e73203cd1157a03facc41ffb54050f5b28e55bd (diff)
PM / Sleep: Mitigate race between the freezer and request_firmware()
There is a race condition between the freezer and request_firmware() such that if request_firmware() is run on one CPU and freeze_processes() is run on another CPU and usermodehelper_disable() called by it succeeds to grab umhelper_sem for writing before usermodehelper_read_trylock() called from request_firmware() acquires it for reading, the request_firmware() will fail and trigger a WARN_ON() complaining that it was called at a wrong time. However, in fact, it wasn't called at a wrong time and freeze_processes() simply happened to be executed simultaneously. To avoid this race, at least in some cases, modify usermodehelper_read_trylock() so that it doesn't fail if the freezing of tasks has just started and hasn't been completed yet. Instead, during the freezing of tasks, it will try to freeze the task that has called it so that it can wait until user space is thawed without triggering the scary warning. For this purpose, change usermodehelper_disabled so that it can take three different values, UMH_ENABLED (0), UMH_FREEZING and UMH_DISABLED. The first one means that usermode helpers are enabled, the last one means "hard disable" (i.e. the system is not ready for usermode helpers to be used) and the second one is reserved for the freezer. Namely, when freeze_processes() is started, it sets usermodehelper_disabled to UMH_FREEZING which tells usermodehelper_read_trylock() that it shouldn't fail just yet and should call try_to_freeze() if woken up and cannot return immediately. This way all freezable tasks that happen to call request_firmware() right before freeze_processes() is started and lose the race for umhelper_sem with it will be frozen and will sleep until thaw_processes() unsets usermodehelper_disabled. [For the non-freezable callers of request_firmware() the race for umhelper_sem against freeze_processes() is unfortunately unavoidable.] Reported-by: Stephen Boyd <sboyd@codeaurora.org> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: stable@vger.kernel.org
-rw-r--r--include/linux/kmod.h21
-rw-r--r--kernel/kmod.c47
-rw-r--r--kernel/power/process.c3
3 files changed, 58 insertions, 13 deletions
diff --git a/include/linux/kmod.h b/include/linux/kmod.h
index b087377ae2c4..dd99c329e161 100644
--- a/include/linux/kmod.h
+++ b/include/linux/kmod.h
@@ -110,10 +110,27 @@ call_usermodehelper(char *path, char **argv, char **envp, int wait)
110 110
111extern struct ctl_table usermodehelper_table[]; 111extern struct ctl_table usermodehelper_table[];
112 112
113enum umh_disable_depth {
114 UMH_ENABLED = 0,
115 UMH_FREEZING,
116 UMH_DISABLED,
117};
118
113extern void usermodehelper_init(void); 119extern void usermodehelper_init(void);
114 120
115extern int usermodehelper_disable(void); 121extern int __usermodehelper_disable(enum umh_disable_depth depth);
116extern void usermodehelper_enable(void); 122extern void __usermodehelper_set_disable_depth(enum umh_disable_depth depth);
123
124static inline int usermodehelper_disable(void)
125{
126 return __usermodehelper_disable(UMH_DISABLED);
127}
128
129static inline void usermodehelper_enable(void)
130{
131 __usermodehelper_set_disable_depth(UMH_ENABLED);
132}
133
117extern int usermodehelper_read_trylock(void); 134extern int usermodehelper_read_trylock(void);
118extern long usermodehelper_read_lock_wait(long timeout); 135extern long usermodehelper_read_lock_wait(long timeout);
119extern void usermodehelper_read_unlock(void); 136extern void usermodehelper_read_unlock(void);
diff --git a/kernel/kmod.c b/kernel/kmod.c
index da7fcca279f9..05698a7415fe 100644
--- a/kernel/kmod.c
+++ b/kernel/kmod.c
@@ -322,7 +322,7 @@ static void __call_usermodehelper(struct work_struct *work)
322 * land has been frozen during a system-wide hibernation or suspend operation). 322 * land has been frozen during a system-wide hibernation or suspend operation).
323 * Should always be manipulated under umhelper_sem acquired for write. 323 * Should always be manipulated under umhelper_sem acquired for write.
324 */ 324 */
325static int usermodehelper_disabled = 1; 325static enum umh_disable_depth usermodehelper_disabled = UMH_DISABLED;
326 326
327/* Number of helpers running */ 327/* Number of helpers running */
328static atomic_t running_helpers = ATOMIC_INIT(0); 328static atomic_t running_helpers = ATOMIC_INIT(0);
@@ -347,13 +347,30 @@ static DECLARE_WAIT_QUEUE_HEAD(usermodehelper_disabled_waitq);
347 347
348int usermodehelper_read_trylock(void) 348int usermodehelper_read_trylock(void)
349{ 349{
350 DEFINE_WAIT(wait);
350 int ret = 0; 351 int ret = 0;
351 352
352 down_read(&umhelper_sem); 353 down_read(&umhelper_sem);
353 if (usermodehelper_disabled) { 354 for (;;) {
355 prepare_to_wait(&usermodehelper_disabled_waitq, &wait,
356 TASK_INTERRUPTIBLE);
357 if (!usermodehelper_disabled)
358 break;
359
360 if (usermodehelper_disabled == UMH_DISABLED)
361 ret = -EAGAIN;
362
354 up_read(&umhelper_sem); 363 up_read(&umhelper_sem);
355 ret = -EAGAIN; 364
365 if (ret)
366 break;
367
368 schedule();
369 try_to_freeze();
370
371 down_read(&umhelper_sem);
356 } 372 }
373 finish_wait(&usermodehelper_disabled_waitq, &wait);
357 return ret; 374 return ret;
358} 375}
359EXPORT_SYMBOL_GPL(usermodehelper_read_trylock); 376EXPORT_SYMBOL_GPL(usermodehelper_read_trylock);
@@ -392,25 +409,35 @@ void usermodehelper_read_unlock(void)
392EXPORT_SYMBOL_GPL(usermodehelper_read_unlock); 409EXPORT_SYMBOL_GPL(usermodehelper_read_unlock);
393 410
394/** 411/**
395 * usermodehelper_enable - allow new helpers to be started again 412 * __usermodehelper_set_disable_depth - Modify usermodehelper_disabled.
413 * depth: New value to assign to usermodehelper_disabled.
414 *
415 * Change the value of usermodehelper_disabled (under umhelper_sem locked for
416 * writing) and wakeup tasks waiting for it to change.
396 */ 417 */
397void usermodehelper_enable(void) 418void __usermodehelper_set_disable_depth(enum umh_disable_depth depth)
398{ 419{
399 down_write(&umhelper_sem); 420 down_write(&umhelper_sem);
400 usermodehelper_disabled = 0; 421 usermodehelper_disabled = depth;
401 wake_up(&usermodehelper_disabled_waitq); 422 wake_up(&usermodehelper_disabled_waitq);
402 up_write(&umhelper_sem); 423 up_write(&umhelper_sem);
403} 424}
404 425
405/** 426/**
406 * usermodehelper_disable - prevent new helpers from being started 427 * __usermodehelper_disable - Prevent new helpers from being started.
428 * @depth: New value to assign to usermodehelper_disabled.
429 *
430 * Set usermodehelper_disabled to @depth and wait for running helpers to exit.
407 */ 431 */
408int usermodehelper_disable(void) 432int __usermodehelper_disable(enum umh_disable_depth depth)
409{ 433{
410 long retval; 434 long retval;
411 435
436 if (!depth)
437 return -EINVAL;
438
412 down_write(&umhelper_sem); 439 down_write(&umhelper_sem);
413 usermodehelper_disabled = 1; 440 usermodehelper_disabled = depth;
414 up_write(&umhelper_sem); 441 up_write(&umhelper_sem);
415 442
416 /* 443 /*
@@ -425,7 +452,7 @@ int usermodehelper_disable(void)
425 if (retval) 452 if (retval)
426 return 0; 453 return 0;
427 454
428 usermodehelper_enable(); 455 __usermodehelper_set_disable_depth(UMH_ENABLED);
429 return -EAGAIN; 456 return -EAGAIN;
430} 457}
431 458
diff --git a/kernel/power/process.c b/kernel/power/process.c
index 56eaac7e88ab..19db29f67558 100644
--- a/kernel/power/process.c
+++ b/kernel/power/process.c
@@ -123,7 +123,7 @@ int freeze_processes(void)
123{ 123{
124 int error; 124 int error;
125 125
126 error = usermodehelper_disable(); 126 error = __usermodehelper_disable(UMH_FREEZING);
127 if (error) 127 if (error)
128 return error; 128 return error;
129 129
@@ -135,6 +135,7 @@ int freeze_processes(void)
135 error = try_to_freeze_tasks(true); 135 error = try_to_freeze_tasks(true);
136 if (!error) { 136 if (!error) {
137 printk("done."); 137 printk("done.");
138 __usermodehelper_set_disable_depth(UMH_DISABLED);
138 oom_killer_disable(); 139 oom_killer_disable();
139 } 140 }
140 printk("\n"); 141 printk("\n");