Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PM: prevent frozen user mode helpers from failing the freezing of tasks

At present, if a user mode helper is running while
usermodehelper_pm_callback() is executed, the helper may be frozen and the
completion in call_usermodehelper_exec() won't be completed until user
space processes are thawed. As a result, the freezing of kernel threads
may fail, which is not desirable.

Prevent this from happening by introducing a counter of running user mode
helpers and allowing usermodehelper_pm_callback() to succeed for action =
PM_HIBERNATION_PREPARE or action = PM_SUSPEND_PREPARE only if there are no
helpers running. [Namely, usermodehelper_pm_callback() waits for at most
RUNNING_HELPERS_TIMEOUT for the number of running helpers to become zero
and fails if that doesn't happen.]

Special thanks to Uli Luckas <u.luckas@road.de>, Pavel Machek
<pavel@ucw.cz> and Oleg Nesterov <oleg@tv-sign.ru> for reviewing the
previous versions of this patch and for very useful comments.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Uli Luckas <u.luckas@road.de>
Acked-by: Nigel Cunningham <nigel@nigel.suspend2.net>
Acked-by: Pavel Machek <pavel@ucw.cz>
Cc: Oleg Nesterov <oleg@tv-sign.ru>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Rafael J. Wysocki and committed by
Linus Torvalds
ccd4b65a 8cdd4936

+68 -10
+68 -10
kernel/kmod.c
··· 41 41 42 42 static struct workqueue_struct *khelper_wq; 43 43 44 - /* 45 - * If set, both call_usermodehelper_keys() and call_usermodehelper_pipe() exit 46 - * immediately returning -EBUSY. Used for preventing user land processes from 47 - * being created after the user land has been frozen during a system-wide 48 - * hibernation or suspend operation. 49 - */ 50 - static int usermodehelper_disabled; 51 - 52 44 #ifdef CONFIG_KMOD 53 45 54 46 /* ··· 267 275 } 268 276 } 269 277 278 + #ifdef CONFIG_PM 279 + /* 280 + * If set, call_usermodehelper_exec() will exit immediately returning -EBUSY 281 + * (used for preventing user land processes from being created after the user 282 + * land has been frozen during a system-wide hibernation or suspend operation). 283 + */ 284 + static int usermodehelper_disabled; 285 + 286 + /* Number of helpers running */ 287 + static atomic_t running_helpers = ATOMIC_INIT(0); 288 + 289 + /* 290 + * Wait queue head used by usermodehelper_pm_callback() to wait for all running 291 + * helpers to finish. 292 + */ 293 + static DECLARE_WAIT_QUEUE_HEAD(running_helpers_waitq); 294 + 295 + /* 296 + * Time to wait for running_helpers to become zero before the setting of 297 + * usermodehelper_disabled in usermodehelper_pm_callback() fails 298 + */ 299 + #define RUNNING_HELPERS_TIMEOUT (5 * HZ) 300 + 270 301 static int usermodehelper_pm_callback(struct notifier_block *nfb, 271 302 unsigned long action, 272 303 void *ignored) 273 304 { 305 + long retval; 306 + 274 307 switch (action) { 275 308 case PM_HIBERNATION_PREPARE: 276 309 case PM_SUSPEND_PREPARE: 277 310 usermodehelper_disabled = 1; 278 - return NOTIFY_OK; 311 + smp_mb(); 312 + /* 313 + * From now on call_usermodehelper_exec() won't start any new 314 + * helpers, so it is sufficient if running_helpers turns out to 315 + * be zero at one point (it may be increased later, but that 316 + * doesn't matter). 317 + */ 318 + retval = wait_event_timeout(running_helpers_waitq, 319 + atomic_read(&running_helpers) == 0, 320 + RUNNING_HELPERS_TIMEOUT); 321 + if (retval) { 322 + return NOTIFY_OK; 323 + } else { 324 + usermodehelper_disabled = 0; 325 + return NOTIFY_BAD; 326 + } 279 327 case PM_POST_HIBERNATION: 280 328 case PM_POST_SUSPEND: 281 329 usermodehelper_disabled = 0; ··· 324 292 325 293 return NOTIFY_DONE; 326 294 } 295 + 296 + static void helper_lock(void) 297 + { 298 + atomic_inc(&running_helpers); 299 + smp_mb__after_atomic_inc(); 300 + } 301 + 302 + static void helper_unlock(void) 303 + { 304 + if (atomic_dec_and_test(&running_helpers)) 305 + wake_up(&running_helpers_waitq); 306 + } 307 + 308 + static void register_pm_notifier_callback(void) 309 + { 310 + pm_notifier(usermodehelper_pm_callback, 0); 311 + } 312 + #else /* CONFIG_PM */ 313 + #define usermodehelper_disabled 0 314 + 315 + static inline void helper_lock(void) {} 316 + static inline void helper_unlock(void) {} 317 + static inline void register_pm_notifier_callback(void) {} 318 + #endif /* CONFIG_PM */ 327 319 328 320 /** 329 321 * call_usermodehelper_setup - prepare to call a usermode helper ··· 453 397 DECLARE_COMPLETION_ONSTACK(done); 454 398 int retval; 455 399 400 + helper_lock(); 456 401 if (sub_info->path[0] == '\0') { 457 402 retval = 0; 458 403 goto out; ··· 475 418 476 419 out: 477 420 call_usermodehelper_freeinfo(sub_info); 421 + helper_unlock(); 478 422 return retval; 479 423 } 480 424 EXPORT_SYMBOL(call_usermodehelper_exec); ··· 517 459 { 518 460 khelper_wq = create_singlethread_workqueue("khelper"); 519 461 BUG_ON(!khelper_wq); 520 - pm_notifier(usermodehelper_pm_callback, 0); 462 + register_pm_notifier_callback(); 521 463 }