diff options
author | Oleg Nesterov <oleg@redhat.com> | 2011-03-06 12:02:54 -0500 |
---|---|---|
committer | Oleg Nesterov <oleg@redhat.com> | 2011-04-09 09:53:56 -0400 |
commit | 0e028465d18b7c6797fcbdea632299d16097c5cd (patch) | |
tree | d03a1f0f688e9c4a780b2a1a3ef8354378b0ad42 | |
parent | ba2d01629d0d167598cfea85adc7926822bbfc45 (diff) |
exec: unify do_execve/compat_do_execve code
Add the appropriate members into struct user_arg_ptr and teach
get_user_arg_ptr() to handle is_compat = T case correctly.
This allows us to remove the compat_do_execve() code from fs/compat.c
and reimplement compat_do_execve() as the trivial wrapper on top of
do_execve_common(is_compat => true).
In fact, this fixes another (minor) bug. "compat_uptr_t str" can
overflow after "str += len" in compat_copy_strings() if a 64bit
application execs via sys32_execve().
Unexport acct_arg_size() and get_arg_page(), fs/compat.c doesn't
need them any longer.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Tested-by: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
-rw-r--r-- | fs/compat.c | 235 | ||||
-rw-r--r-- | fs/exec.c | 62 | ||||
-rw-r--r-- | include/linux/binfmts.h | 4 |
3 files changed, 50 insertions, 251 deletions
diff --git a/fs/compat.c b/fs/compat.c index 72fe6cda9108..0ea00832de23 100644 --- a/fs/compat.c +++ b/fs/compat.c | |||
@@ -1306,241 +1306,6 @@ compat_sys_openat(unsigned int dfd, const char __user *filename, int flags, int | |||
1306 | return do_sys_open(dfd, filename, flags, mode); | 1306 | return do_sys_open(dfd, filename, flags, mode); |
1307 | } | 1307 | } |
1308 | 1308 | ||
1309 | /* | ||
1310 | * compat_count() counts the number of arguments/envelopes. It is basically | ||
1311 | * a copy of count() from fs/exec.c, except that it works with 32 bit argv | ||
1312 | * and envp pointers. | ||
1313 | */ | ||
1314 | static int compat_count(compat_uptr_t __user *argv, int max) | ||
1315 | { | ||
1316 | int i = 0; | ||
1317 | |||
1318 | if (argv != NULL) { | ||
1319 | for (;;) { | ||
1320 | compat_uptr_t p; | ||
1321 | |||
1322 | if (get_user(p, argv)) | ||
1323 | return -EFAULT; | ||
1324 | if (!p) | ||
1325 | break; | ||
1326 | argv++; | ||
1327 | if (i++ >= max) | ||
1328 | return -E2BIG; | ||
1329 | |||
1330 | if (fatal_signal_pending(current)) | ||
1331 | return -ERESTARTNOHAND; | ||
1332 | cond_resched(); | ||
1333 | } | ||
1334 | } | ||
1335 | return i; | ||
1336 | } | ||
1337 | |||
1338 | /* | ||
1339 | * compat_copy_strings() is basically a copy of copy_strings() from fs/exec.c | ||
1340 | * except that it works with 32 bit argv and envp pointers. | ||
1341 | */ | ||
1342 | static int compat_copy_strings(int argc, compat_uptr_t __user *argv, | ||
1343 | struct linux_binprm *bprm) | ||
1344 | { | ||
1345 | struct page *kmapped_page = NULL; | ||
1346 | char *kaddr = NULL; | ||
1347 | unsigned long kpos = 0; | ||
1348 | int ret; | ||
1349 | |||
1350 | while (argc-- > 0) { | ||
1351 | compat_uptr_t str; | ||
1352 | int len; | ||
1353 | unsigned long pos; | ||
1354 | |||
1355 | if (get_user(str, argv+argc) || | ||
1356 | !(len = strnlen_user(compat_ptr(str), MAX_ARG_STRLEN))) { | ||
1357 | ret = -EFAULT; | ||
1358 | goto out; | ||
1359 | } | ||
1360 | |||
1361 | if (len > MAX_ARG_STRLEN) { | ||
1362 | ret = -E2BIG; | ||
1363 | goto out; | ||
1364 | } | ||
1365 | |||
1366 | /* We're going to work our way backwords. */ | ||
1367 | pos = bprm->p; | ||
1368 | str += len; | ||
1369 | bprm->p -= len; | ||
1370 | |||
1371 | while (len > 0) { | ||
1372 | int offset, bytes_to_copy; | ||
1373 | |||
1374 | if (fatal_signal_pending(current)) { | ||
1375 | ret = -ERESTARTNOHAND; | ||
1376 | goto out; | ||
1377 | } | ||
1378 | cond_resched(); | ||
1379 | |||
1380 | offset = pos % PAGE_SIZE; | ||
1381 | if (offset == 0) | ||
1382 | offset = PAGE_SIZE; | ||
1383 | |||
1384 | bytes_to_copy = offset; | ||
1385 | if (bytes_to_copy > len) | ||
1386 | bytes_to_copy = len; | ||
1387 | |||
1388 | offset -= bytes_to_copy; | ||
1389 | pos -= bytes_to_copy; | ||
1390 | str -= bytes_to_copy; | ||
1391 | len -= bytes_to_copy; | ||
1392 | |||
1393 | if (!kmapped_page || kpos != (pos & PAGE_MASK)) { | ||
1394 | struct page *page; | ||
1395 | |||
1396 | page = get_arg_page(bprm, pos, 1); | ||
1397 | if (!page) { | ||
1398 | ret = -E2BIG; | ||
1399 | goto out; | ||
1400 | } | ||
1401 | |||
1402 | if (kmapped_page) { | ||
1403 | flush_kernel_dcache_page(kmapped_page); | ||
1404 | kunmap(kmapped_page); | ||
1405 | put_page(kmapped_page); | ||
1406 | } | ||
1407 | kmapped_page = page; | ||
1408 | kaddr = kmap(kmapped_page); | ||
1409 | kpos = pos & PAGE_MASK; | ||
1410 | flush_cache_page(bprm->vma, kpos, | ||
1411 | page_to_pfn(kmapped_page)); | ||
1412 | } | ||
1413 | if (copy_from_user(kaddr+offset, compat_ptr(str), | ||
1414 | bytes_to_copy)) { | ||
1415 | ret = -EFAULT; | ||
1416 | goto out; | ||
1417 | } | ||
1418 | } | ||
1419 | } | ||
1420 | ret = 0; | ||
1421 | out: | ||
1422 | if (kmapped_page) { | ||
1423 | flush_kernel_dcache_page(kmapped_page); | ||
1424 | kunmap(kmapped_page); | ||
1425 | put_page(kmapped_page); | ||
1426 | } | ||
1427 | return ret; | ||
1428 | } | ||
1429 | |||
1430 | /* | ||
1431 | * compat_do_execve() is mostly a copy of do_execve(), with the exception | ||
1432 | * that it processes 32 bit argv and envp pointers. | ||
1433 | */ | ||
1434 | int compat_do_execve(char * filename, | ||
1435 | compat_uptr_t __user *argv, | ||
1436 | compat_uptr_t __user *envp, | ||
1437 | struct pt_regs * regs) | ||
1438 | { | ||
1439 | struct linux_binprm *bprm; | ||
1440 | struct file *file; | ||
1441 | struct files_struct *displaced; | ||
1442 | bool clear_in_exec; | ||
1443 | int retval; | ||
1444 | |||
1445 | retval = unshare_files(&displaced); | ||
1446 | if (retval) | ||
1447 | goto out_ret; | ||
1448 | |||
1449 | retval = -ENOMEM; | ||
1450 | bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); | ||
1451 | if (!bprm) | ||
1452 | goto out_files; | ||
1453 | |||
1454 | retval = prepare_bprm_creds(bprm); | ||
1455 | if (retval) | ||
1456 | goto out_free; | ||
1457 | |||
1458 | retval = check_unsafe_exec(bprm); | ||
1459 | if (retval < 0) | ||
1460 | goto out_free; | ||
1461 | clear_in_exec = retval; | ||
1462 | current->in_execve = 1; | ||
1463 | |||
1464 | file = open_exec(filename); | ||
1465 | retval = PTR_ERR(file); | ||
1466 | if (IS_ERR(file)) | ||
1467 | goto out_unmark; | ||
1468 | |||
1469 | sched_exec(); | ||
1470 | |||
1471 | bprm->file = file; | ||
1472 | bprm->filename = filename; | ||
1473 | bprm->interp = filename; | ||
1474 | |||
1475 | retval = bprm_mm_init(bprm); | ||
1476 | if (retval) | ||
1477 | goto out_file; | ||
1478 | |||
1479 | bprm->argc = compat_count(argv, MAX_ARG_STRINGS); | ||
1480 | if ((retval = bprm->argc) < 0) | ||
1481 | goto out; | ||
1482 | |||
1483 | bprm->envc = compat_count(envp, MAX_ARG_STRINGS); | ||
1484 | if ((retval = bprm->envc) < 0) | ||
1485 | goto out; | ||
1486 | |||
1487 | retval = prepare_binprm(bprm); | ||
1488 | if (retval < 0) | ||
1489 | goto out; | ||
1490 | |||
1491 | retval = copy_strings_kernel(1, &bprm->filename, bprm); | ||
1492 | if (retval < 0) | ||
1493 | goto out; | ||
1494 | |||
1495 | bprm->exec = bprm->p; | ||
1496 | retval = compat_copy_strings(bprm->envc, envp, bprm); | ||
1497 | if (retval < 0) | ||
1498 | goto out; | ||
1499 | |||
1500 | retval = compat_copy_strings(bprm->argc, argv, bprm); | ||
1501 | if (retval < 0) | ||
1502 | goto out; | ||
1503 | |||
1504 | retval = search_binary_handler(bprm, regs); | ||
1505 | if (retval < 0) | ||
1506 | goto out; | ||
1507 | |||
1508 | /* execve succeeded */ | ||
1509 | current->fs->in_exec = 0; | ||
1510 | current->in_execve = 0; | ||
1511 | acct_update_integrals(current); | ||
1512 | free_bprm(bprm); | ||
1513 | if (displaced) | ||
1514 | put_files_struct(displaced); | ||
1515 | return retval; | ||
1516 | |||
1517 | out: | ||
1518 | if (bprm->mm) { | ||
1519 | acct_arg_size(bprm, 0); | ||
1520 | mmput(bprm->mm); | ||
1521 | } | ||
1522 | |||
1523 | out_file: | ||
1524 | if (bprm->file) { | ||
1525 | allow_write_access(bprm->file); | ||
1526 | fput(bprm->file); | ||
1527 | } | ||
1528 | |||
1529 | out_unmark: | ||
1530 | if (clear_in_exec) | ||
1531 | current->fs->in_exec = 0; | ||
1532 | current->in_execve = 0; | ||
1533 | |||
1534 | out_free: | ||
1535 | free_bprm(bprm); | ||
1536 | |||
1537 | out_files: | ||
1538 | if (displaced) | ||
1539 | reset_files_struct(displaced); | ||
1540 | out_ret: | ||
1541 | return retval; | ||
1542 | } | ||
1543 | |||
1544 | #define __COMPAT_NFDBITS (8 * sizeof(compat_ulong_t)) | 1309 | #define __COMPAT_NFDBITS (8 * sizeof(compat_ulong_t)) |
1545 | 1310 | ||
1546 | static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, | 1311 | static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, |
@@ -55,6 +55,7 @@ | |||
55 | #include <linux/fs_struct.h> | 55 | #include <linux/fs_struct.h> |
56 | #include <linux/pipe_fs_i.h> | 56 | #include <linux/pipe_fs_i.h> |
57 | #include <linux/oom.h> | 57 | #include <linux/oom.h> |
58 | #include <linux/compat.h> | ||
58 | 59 | ||
59 | #include <asm/uaccess.h> | 60 | #include <asm/uaccess.h> |
60 | #include <asm/mmu_context.h> | 61 | #include <asm/mmu_context.h> |
@@ -167,7 +168,7 @@ out: | |||
167 | 168 | ||
168 | #ifdef CONFIG_MMU | 169 | #ifdef CONFIG_MMU |
169 | 170 | ||
170 | void acct_arg_size(struct linux_binprm *bprm, unsigned long pages) | 171 | static void acct_arg_size(struct linux_binprm *bprm, unsigned long pages) |
171 | { | 172 | { |
172 | struct mm_struct *mm = current->mm; | 173 | struct mm_struct *mm = current->mm; |
173 | long diff = (long)(pages - bprm->vma_pages); | 174 | long diff = (long)(pages - bprm->vma_pages); |
@@ -186,7 +187,7 @@ void acct_arg_size(struct linux_binprm *bprm, unsigned long pages) | |||
186 | #endif | 187 | #endif |
187 | } | 188 | } |
188 | 189 | ||
189 | struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos, | 190 | static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos, |
190 | int write) | 191 | int write) |
191 | { | 192 | { |
192 | struct page *page; | 193 | struct page *page; |
@@ -305,11 +306,11 @@ static bool valid_arg_len(struct linux_binprm *bprm, long len) | |||
305 | 306 | ||
306 | #else | 307 | #else |
307 | 308 | ||
308 | void acct_arg_size(struct linux_binprm *bprm, unsigned long pages) | 309 | static inline void acct_arg_size(struct linux_binprm *bprm, unsigned long pages) |
309 | { | 310 | { |
310 | } | 311 | } |
311 | 312 | ||
312 | struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos, | 313 | static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos, |
313 | int write) | 314 | int write) |
314 | { | 315 | { |
315 | struct page *page; | 316 | struct page *page; |
@@ -399,17 +400,36 @@ err: | |||
399 | } | 400 | } |
400 | 401 | ||
401 | struct user_arg_ptr { | 402 | struct user_arg_ptr { |
402 | const char __user *const __user *native; | 403 | #ifdef CONFIG_COMPAT |
404 | bool is_compat; | ||
405 | #endif | ||
406 | union { | ||
407 | const char __user *const __user *native; | ||
408 | #ifdef CONFIG_COMPAT | ||
409 | compat_uptr_t __user *compat; | ||
410 | #endif | ||
411 | } ptr; | ||
403 | }; | 412 | }; |
404 | 413 | ||
405 | static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr) | 414 | static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr) |
406 | { | 415 | { |
407 | const char __user *ptr; | 416 | const char __user *native; |
417 | |||
418 | #ifdef CONFIG_COMPAT | ||
419 | if (unlikely(argv.is_compat)) { | ||
420 | compat_uptr_t compat; | ||
421 | |||
422 | if (get_user(compat, argv.ptr.compat + nr)) | ||
423 | return ERR_PTR(-EFAULT); | ||
408 | 424 | ||
409 | if (get_user(ptr, argv.native + nr)) | 425 | return compat_ptr(compat); |
426 | } | ||
427 | #endif | ||
428 | |||
429 | if (get_user(native, argv.ptr.native + nr)) | ||
410 | return ERR_PTR(-EFAULT); | 430 | return ERR_PTR(-EFAULT); |
411 | 431 | ||
412 | return ptr; | 432 | return native; |
413 | } | 433 | } |
414 | 434 | ||
415 | /* | 435 | /* |
@@ -419,7 +439,7 @@ static int count(struct user_arg_ptr argv, int max) | |||
419 | { | 439 | { |
420 | int i = 0; | 440 | int i = 0; |
421 | 441 | ||
422 | if (argv.native != NULL) { | 442 | if (argv.ptr.native != NULL) { |
423 | for (;;) { | 443 | for (;;) { |
424 | const char __user *p = get_user_arg_ptr(argv, i); | 444 | const char __user *p = get_user_arg_ptr(argv, i); |
425 | 445 | ||
@@ -542,7 +562,7 @@ int copy_strings_kernel(int argc, const char *const *__argv, | |||
542 | int r; | 562 | int r; |
543 | mm_segment_t oldfs = get_fs(); | 563 | mm_segment_t oldfs = get_fs(); |
544 | struct user_arg_ptr argv = { | 564 | struct user_arg_ptr argv = { |
545 | .native = (const char __user *const __user *)__argv, | 565 | .ptr.native = (const char __user *const __user *)__argv, |
546 | }; | 566 | }; |
547 | 567 | ||
548 | set_fs(KERNEL_DS); | 568 | set_fs(KERNEL_DS); |
@@ -1516,10 +1536,28 @@ int do_execve(const char *filename, | |||
1516 | const char __user *const __user *__envp, | 1536 | const char __user *const __user *__envp, |
1517 | struct pt_regs *regs) | 1537 | struct pt_regs *regs) |
1518 | { | 1538 | { |
1519 | struct user_arg_ptr argv = { .native = __argv }; | 1539 | struct user_arg_ptr argv = { .ptr.native = __argv }; |
1520 | struct user_arg_ptr envp = { .native = __envp }; | 1540 | struct user_arg_ptr envp = { .ptr.native = __envp }; |
1541 | return do_execve_common(filename, argv, envp, regs); | ||
1542 | } | ||
1543 | |||
1544 | #ifdef CONFIG_COMPAT | ||
1545 | int compat_do_execve(char *filename, | ||
1546 | compat_uptr_t __user *__argv, | ||
1547 | compat_uptr_t __user *__envp, | ||
1548 | struct pt_regs *regs) | ||
1549 | { | ||
1550 | struct user_arg_ptr argv = { | ||
1551 | .is_compat = true, | ||
1552 | .ptr.compat = __argv, | ||
1553 | }; | ||
1554 | struct user_arg_ptr envp = { | ||
1555 | .is_compat = true, | ||
1556 | .ptr.compat = __envp, | ||
1557 | }; | ||
1521 | return do_execve_common(filename, argv, envp, regs); | 1558 | return do_execve_common(filename, argv, envp, regs); |
1522 | } | 1559 | } |
1560 | #endif | ||
1523 | 1561 | ||
1524 | void set_binfmt(struct linux_binfmt *new) | 1562 | void set_binfmt(struct linux_binfmt *new) |
1525 | { | 1563 | { |
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index c3d6512eded1..8845613fd7e3 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h | |||
@@ -60,10 +60,6 @@ struct linux_binprm { | |||
60 | unsigned long loader, exec; | 60 | unsigned long loader, exec; |
61 | }; | 61 | }; |
62 | 62 | ||
63 | extern void acct_arg_size(struct linux_binprm *bprm, unsigned long pages); | ||
64 | extern struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos, | ||
65 | int write); | ||
66 | |||
67 | #define BINPRM_FLAGS_ENFORCE_NONDUMP_BIT 0 | 63 | #define BINPRM_FLAGS_ENFORCE_NONDUMP_BIT 0 |
68 | #define BINPRM_FLAGS_ENFORCE_NONDUMP (1 << BINPRM_FLAGS_ENFORCE_NONDUMP_BIT) | 64 | #define BINPRM_FLAGS_ENFORCE_NONDUMP (1 << BINPRM_FLAGS_ENFORCE_NONDUMP_BIT) |
69 | 65 | ||