diff options
author | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2014-10-29 17:50:35 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-29 19:33:14 -0400 |
commit | 0baf2a4dbf75abb7c186fd6c8d55d27aaa354a29 (patch) | |
tree | ec69becc6598104a07f795d221af87e311d4ba29 /kernel | |
parent | c8d523a4b053d1678adb92976b5ef84d9bc481e8 (diff) |
kernel/kmod: fix use-after-free of the sub_info structure
Found this in the message log on a s390 system:
BUG kmalloc-192 (Not tainted): Poison overwritten
Disabling lock debugging due to kernel taint
INFO: 0x00000000684761f4-0x00000000684761f7. First byte 0xff instead of 0x6b
INFO: Allocated in call_usermodehelper_setup+0x70/0x128 age=71 cpu=2 pid=648
__slab_alloc.isra.47.constprop.56+0x5f6/0x658
kmem_cache_alloc_trace+0x106/0x408
call_usermodehelper_setup+0x70/0x128
call_usermodehelper+0x62/0x90
cgroup_release_agent+0x178/0x1c0
process_one_work+0x36e/0x680
worker_thread+0x2f0/0x4f8
kthread+0x10a/0x120
kernel_thread_starter+0x6/0xc
kernel_thread_starter+0x0/0xc
INFO: Freed in call_usermodehelper_exec+0x110/0x1b8 age=71 cpu=2 pid=648
__slab_free+0x94/0x560
kfree+0x364/0x3e0
call_usermodehelper_exec+0x110/0x1b8
cgroup_release_agent+0x178/0x1c0
process_one_work+0x36e/0x680
worker_thread+0x2f0/0x4f8
kthread+0x10a/0x120
kernel_thread_starter+0x6/0xc
kernel_thread_starter+0x0/0xc
There is a use-after-free bug on the subprocess_info structure allocated
by the user mode helper. In case do_execve() returns with an error
____call_usermodehelper() stores the error code to sub_info->retval, but
sub_info can already have been freed.
Regarding UMH_NO_WAIT, the sub_info structure can be freed by
__call_usermodehelper() before the worker thread returns from
do_execve(), allowing memory corruption when do_execve() failed after
exec_mmap() is called.
Regarding UMH_WAIT_EXEC, the call to umh_complete() allows
call_usermodehelper_exec() to continue which then frees sub_info.
To fix this race the code needs to make sure that the call to
call_usermodehelper_freeinfo() is always done after the last store to
sub_info->retval.
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Cc: Tetsuo Handa <penguin-kernel@i-love.sakura.ne.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/kmod.c | 76 |
1 files changed, 37 insertions, 39 deletions
diff --git a/kernel/kmod.c b/kernel/kmod.c index 8637e041a247..80f7a6d00519 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c | |||
@@ -196,12 +196,34 @@ int __request_module(bool wait, const char *fmt, ...) | |||
196 | EXPORT_SYMBOL(__request_module); | 196 | EXPORT_SYMBOL(__request_module); |
197 | #endif /* CONFIG_MODULES */ | 197 | #endif /* CONFIG_MODULES */ |
198 | 198 | ||
199 | static void call_usermodehelper_freeinfo(struct subprocess_info *info) | ||
200 | { | ||
201 | if (info->cleanup) | ||
202 | (*info->cleanup)(info); | ||
203 | kfree(info); | ||
204 | } | ||
205 | |||
206 | static void umh_complete(struct subprocess_info *sub_info) | ||
207 | { | ||
208 | struct completion *comp = xchg(&sub_info->complete, NULL); | ||
209 | /* | ||
210 | * See call_usermodehelper_exec(). If xchg() returns NULL | ||
211 | * we own sub_info, the UMH_KILLABLE caller has gone away | ||
212 | * or the caller used UMH_NO_WAIT. | ||
213 | */ | ||
214 | if (comp) | ||
215 | complete(comp); | ||
216 | else | ||
217 | call_usermodehelper_freeinfo(sub_info); | ||
218 | } | ||
219 | |||
199 | /* | 220 | /* |
200 | * This is the task which runs the usermode application | 221 | * This is the task which runs the usermode application |
201 | */ | 222 | */ |
202 | static int ____call_usermodehelper(void *data) | 223 | static int ____call_usermodehelper(void *data) |
203 | { | 224 | { |
204 | struct subprocess_info *sub_info = data; | 225 | struct subprocess_info *sub_info = data; |
226 | int wait = sub_info->wait & ~UMH_KILLABLE; | ||
205 | struct cred *new; | 227 | struct cred *new; |
206 | int retval; | 228 | int retval; |
207 | 229 | ||
@@ -221,7 +243,7 @@ static int ____call_usermodehelper(void *data) | |||
221 | retval = -ENOMEM; | 243 | retval = -ENOMEM; |
222 | new = prepare_kernel_cred(current); | 244 | new = prepare_kernel_cred(current); |
223 | if (!new) | 245 | if (!new) |
224 | goto fail; | 246 | goto out; |
225 | 247 | ||
226 | spin_lock(&umh_sysctl_lock); | 248 | spin_lock(&umh_sysctl_lock); |
227 | new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset); | 249 | new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset); |
@@ -233,7 +255,7 @@ static int ____call_usermodehelper(void *data) | |||
233 | retval = sub_info->init(sub_info, new); | 255 | retval = sub_info->init(sub_info, new); |
234 | if (retval) { | 256 | if (retval) { |
235 | abort_creds(new); | 257 | abort_creds(new); |
236 | goto fail; | 258 | goto out; |
237 | } | 259 | } |
238 | } | 260 | } |
239 | 261 | ||
@@ -242,12 +264,13 @@ static int ____call_usermodehelper(void *data) | |||
242 | retval = do_execve(getname_kernel(sub_info->path), | 264 | retval = do_execve(getname_kernel(sub_info->path), |
243 | (const char __user *const __user *)sub_info->argv, | 265 | (const char __user *const __user *)sub_info->argv, |
244 | (const char __user *const __user *)sub_info->envp); | 266 | (const char __user *const __user *)sub_info->envp); |
267 | out: | ||
268 | sub_info->retval = retval; | ||
269 | /* wait_for_helper() will call umh_complete if UHM_WAIT_PROC. */ | ||
270 | if (wait != UMH_WAIT_PROC) | ||
271 | umh_complete(sub_info); | ||
245 | if (!retval) | 272 | if (!retval) |
246 | return 0; | 273 | return 0; |
247 | |||
248 | /* Exec failed? */ | ||
249 | fail: | ||
250 | sub_info->retval = retval; | ||
251 | do_exit(0); | 274 | do_exit(0); |
252 | } | 275 | } |
253 | 276 | ||
@@ -258,26 +281,6 @@ static int call_helper(void *data) | |||
258 | return ____call_usermodehelper(data); | 281 | return ____call_usermodehelper(data); |
259 | } | 282 | } |
260 | 283 | ||
261 | static void call_usermodehelper_freeinfo(struct subprocess_info *info) | ||
262 | { | ||
263 | if (info->cleanup) | ||
264 | (*info->cleanup)(info); | ||
265 | kfree(info); | ||
266 | } | ||
267 | |||
268 | static void umh_complete(struct subprocess_info *sub_info) | ||
269 | { | ||
270 | struct completion *comp = xchg(&sub_info->complete, NULL); | ||
271 | /* | ||
272 | * See call_usermodehelper_exec(). If xchg() returns NULL | ||
273 | * we own sub_info, the UMH_KILLABLE caller has gone away. | ||
274 | */ | ||
275 | if (comp) | ||
276 | complete(comp); | ||
277 | else | ||
278 | call_usermodehelper_freeinfo(sub_info); | ||
279 | } | ||
280 | |||
281 | /* Keventd can't block, but this (a child) can. */ | 284 | /* Keventd can't block, but this (a child) can. */ |
282 | static int wait_for_helper(void *data) | 285 | static int wait_for_helper(void *data) |
283 | { | 286 | { |
@@ -336,18 +339,8 @@ static void __call_usermodehelper(struct work_struct *work) | |||
336 | kmod_thread_locker = NULL; | 339 | kmod_thread_locker = NULL; |
337 | } | 340 | } |
338 | 341 | ||
339 | switch (wait) { | 342 | if (pid < 0) { |
340 | case UMH_NO_WAIT: | 343 | sub_info->retval = pid; |
341 | call_usermodehelper_freeinfo(sub_info); | ||
342 | break; | ||
343 | |||
344 | case UMH_WAIT_PROC: | ||
345 | if (pid > 0) | ||
346 | break; | ||
347 | /* FALLTHROUGH */ | ||
348 | case UMH_WAIT_EXEC: | ||
349 | if (pid < 0) | ||
350 | sub_info->retval = pid; | ||
351 | umh_complete(sub_info); | 344 | umh_complete(sub_info); |
352 | } | 345 | } |
353 | } | 346 | } |
@@ -588,7 +581,12 @@ int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait) | |||
588 | goto out; | 581 | goto out; |
589 | } | 582 | } |
590 | 583 | ||
591 | sub_info->complete = &done; | 584 | /* |
585 | * Set the completion pointer only if there is a waiter. | ||
586 | * This makes it possible to use umh_complete to free | ||
587 | * the data structure in case of UMH_NO_WAIT. | ||
588 | */ | ||
589 | sub_info->complete = (wait == UMH_NO_WAIT) ? NULL : &done; | ||
592 | sub_info->wait = wait; | 590 | sub_info->wait = wait; |
593 | 591 | ||
594 | queue_work(khelper_wq, &sub_info->work); | 592 | queue_work(khelper_wq, &sub_info->work); |