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), |