diff options
Diffstat (limited to 'kernel/kmod.c')
| -rw-r--r-- | kernel/kmod.c | 37 |
1 files changed, 35 insertions, 2 deletions
diff --git a/kernel/kmod.c b/kernel/kmod.c index ff2c7cb86d77..6f99aead66c6 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c | |||
| @@ -45,6 +45,13 @@ extern int max_threads; | |||
| 45 | 45 | ||
| 46 | static struct workqueue_struct *khelper_wq; | 46 | static struct workqueue_struct *khelper_wq; |
| 47 | 47 | ||
| 48 | /* | ||
| 49 | * kmod_thread_locker is used for deadlock avoidance. There is no explicit | ||
| 50 | * locking to protect this global - it is private to the singleton khelper | ||
| 51 | * thread and should only ever be modified by that thread. | ||
| 52 | */ | ||
| 53 | static const struct task_struct *kmod_thread_locker; | ||
| 54 | |||
| 48 | #define CAP_BSET (void *)1 | 55 | #define CAP_BSET (void *)1 |
| 49 | #define CAP_PI (void *)2 | 56 | #define CAP_PI (void *)2 |
| 50 | 57 | ||
| @@ -221,6 +228,13 @@ fail: | |||
| 221 | return 0; | 228 | return 0; |
| 222 | } | 229 | } |
| 223 | 230 | ||
| 231 | static int call_helper(void *data) | ||
| 232 | { | ||
| 233 | /* Worker thread started blocking khelper thread. */ | ||
| 234 | kmod_thread_locker = current; | ||
| 235 | return ____call_usermodehelper(data); | ||
| 236 | } | ||
| 237 | |||
| 224 | static void call_usermodehelper_freeinfo(struct subprocess_info *info) | 238 | static void call_usermodehelper_freeinfo(struct subprocess_info *info) |
| 225 | { | 239 | { |
| 226 | if (info->cleanup) | 240 | if (info->cleanup) |
| @@ -295,9 +309,12 @@ static void __call_usermodehelper(struct work_struct *work) | |||
| 295 | if (wait == UMH_WAIT_PROC) | 309 | if (wait == UMH_WAIT_PROC) |
| 296 | pid = kernel_thread(wait_for_helper, sub_info, | 310 | pid = kernel_thread(wait_for_helper, sub_info, |
| 297 | CLONE_FS | CLONE_FILES | SIGCHLD); | 311 | CLONE_FS | CLONE_FILES | SIGCHLD); |
| 298 | else | 312 | else { |
| 299 | pid = kernel_thread(____call_usermodehelper, sub_info, | 313 | pid = kernel_thread(call_helper, sub_info, |
| 300 | CLONE_VFORK | SIGCHLD); | 314 | CLONE_VFORK | SIGCHLD); |
| 315 | /* Worker thread stopped blocking khelper thread. */ | ||
| 316 | kmod_thread_locker = NULL; | ||
| 317 | } | ||
| 301 | 318 | ||
| 302 | switch (wait) { | 319 | switch (wait) { |
| 303 | case UMH_NO_WAIT: | 320 | case UMH_NO_WAIT: |
| @@ -548,6 +565,16 @@ int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait) | |||
| 548 | retval = -EBUSY; | 565 | retval = -EBUSY; |
| 549 | goto out; | 566 | goto out; |
| 550 | } | 567 | } |
| 568 | /* | ||
| 569 | * Worker thread must not wait for khelper thread at below | ||
| 570 | * wait_for_completion() if the thread was created with CLONE_VFORK | ||
| 571 | * flag, for khelper thread is already waiting for the thread at | ||
| 572 | * wait_for_completion() in do_fork(). | ||
| 573 | */ | ||
| 574 | if (wait != UMH_NO_WAIT && current == kmod_thread_locker) { | ||
| 575 | retval = -EBUSY; | ||
| 576 | goto out; | ||
| 577 | } | ||
| 551 | 578 | ||
| 552 | sub_info->complete = &done; | 579 | sub_info->complete = &done; |
| 553 | sub_info->wait = wait; | 580 | sub_info->wait = wait; |
| @@ -577,6 +604,12 @@ unlock: | |||
| 577 | return retval; | 604 | return retval; |
| 578 | } | 605 | } |
| 579 | 606 | ||
| 607 | /* | ||
| 608 | * call_usermodehelper_fns() will not run the caller-provided cleanup function | ||
| 609 | * if a memory allocation failure is experienced. So the caller might need to | ||
| 610 | * check the call_usermodehelper_fns() return value: if it is -ENOMEM, perform | ||
| 611 | * the necessaary cleanup within the caller. | ||
| 612 | */ | ||
| 580 | int call_usermodehelper_fns( | 613 | int call_usermodehelper_fns( |
| 581 | char *path, char **argv, char **envp, int wait, | 614 | char *path, char **argv, char **envp, int wait, |
| 582 | int (*init)(struct subprocess_info *info, struct cred *new), | 615 | int (*init)(struct subprocess_info *info, struct cred *new), |
