exec: separate MM_ANONPAGES and RLIMIT_STACK accounting
Subsystems (2)
PATCH 1/1 exec: separate MM_ANONPAGES and RLIMIT_STACK accounting
get_arg_page() checks bprm->rlim_stack.rlim_cur and re-calculates the "extra" size for argv/envp pointers every time, this is a bit ugly and even not strictly correct: acct_arg_size() must not account this size.
Remove all the rlimit code in get_arg_page(). Instead, add bprm->argmin calculated once at the start of __do_execve_file() and change copy_strings to check bprm->p >= bprm->argmin.
The patch adds the new helper, prepare_arg_pages() which initializes bprm->argc/envc and bprm->argmin.
--- fs/exec.c | 103 +++++++++++++++++++++++------------------------- include/linux/binfmts.h | 1 + 2 files changed, 51 insertions(+), 53 deletions(-)
fs/exec.c +50 -53
@@ -218,55 +218,10 @@ static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,if (ret <=0 )return NULL ; -if (write) { -unsigned long size = bprm->vma->vm_end - bprm->vma->vm_start; -unsigned long ptr_size, limit; - -/* -* Since the stack will hold pointers to the strings, we -* must account for them as well. -* -* The size calculation is the entire vma while each arg page is -* built, so each time we get here it's calculating how far it -* is currently (rather than each call being just the newly -* added size from the arg page). As a result, we need to -* always add the entire size of the pointers, so that on the -* last call to get_arg_page() we'll actually have the entire -* correct size. -*/ - ptr_size = (bprm->argc + bprm->envc) *sizeof (void *); -if (ptr_size >ULONG_MAX - size) - goto fail; - size += ptr_size; - -acct_arg_size (bprm, size /PAGE_SIZE ); - -/* -* We've historically supported up to 32 pages (ARG_MAX) -* of argument strings even with small stacks -*/ -if (size <=ARG_MAX ) -return page; - -/* -* Limit to 1/4 of the max stack size or 3/4 of _STK_LIM -* (whichever is smaller) for the argv+env strings. -* This ensures that: -* - the remaining binfmt code will not run out of stack space, -* - the program will have a reasonable amount of stack left -* to work from. -*/ - limit = _STK_LIM /4 *3 ; - limit =min (limit, bprm->rlim_stack.rlim_cur /4 ); -if (size > limit) - goto fail; - } +if (write) +acct_arg_size (bprm,vma_pages (bprm->vma));return page; - -fail: -put_page (page); -return NULL ; }static void put_arg_page (struct page *page)
@@ -492,6 +447,50 @@ static int count(struct user_arg_ptr argv, int max)return i; } +static int prepare_arg_pages (struct linux_binprm *bprm, +struct user_arg_ptr argv,struct user_arg_ptr envp) +{ +unsigned long limit, ptr_size; + + bprm->argc =count (argv,MAX_ARG_STRINGS ); +if (bprm->argc <0 ) +return bprm->argc; + + bprm->envc =count (envp,MAX_ARG_STRINGS ); +if (bprm->envc <0 ) +return bprm->envc; + +/* +* Limit to 1/4 of the max stack size or 3/4 of _STK_LIM +* (whichever is smaller) for the argv+env strings. +* This ensures that: +* - the remaining binfmt code will not run out of stack space, +* - the program will have a reasonable amount of stack left +* to work from. +*/ + limit = _STK_LIM /4 *3 ; + limit =min (limit, bprm->rlim_stack.rlim_cur /4 ); +/* +* We've historically supported up to 32 pages (ARG_MAX) +* of argument strings even with small stacks +*/ + limit =max (limit, (unsigned long )ARG_MAX ); +/* +* We must account for the size of all the argv and envp pointers to +* the argv and envp strings, since they will also take up space in +* the stack. They aren't stored until much later when we can't +* signal to the parent that the child has run out of stack space. +* Instead, calculate it here so it's possible to fail gracefully. +*/ + ptr_size = (bprm->argc + bprm->envc) *sizeof (void *); +if (limit <= ptr_size) +return -E2BIG ; + limit -= ptr_size; + + bprm->argmin = bprm->p - limit; +return 0 ; +} +/* * 'copy_strings()' copies argument/environment strings from the old * processes's memory to the new process's stack. The call to get_user_pages()
@@ -527,6 +526,8 @@ static int copy_strings(int argc, struct user_arg_ptr argv, pos = bprm->p; str += len; bprm->p -= len; +if (bprm->p < bprm->argmin) + goto out;while (len >0 ) {int offset, bytes_to_copy;
@@ -1789,12 +1790,8 @@ static int __do_execve_file(int fd, struct filename *filename,if (retval) goto out_unmark; - bprm->argc =count (argv,MAX_ARG_STRINGS ); -if ((retval = bprm->argc) <0 ) - goto out; - - bprm->envc =count (envp,MAX_ARG_STRINGS ); -if ((retval = bprm->envc) <0 ) + retval =prepare_arg_pages (bprm, argv, envp); +if (retval <0 ) goto out; retval =prepare_binprm (bprm);
include/linux/binfmts.h +1 -0
@@ -25,6 +25,7 @@ struct linux_binprm {#endif struct mm_struct *mm;unsigned long p;/* current top of mem */ +unsigned long argmin;/* rlimit marker for copy_strings() */ unsigned int /* * True after the bprm_set_creds hook has been called once
Signature · 1 line
2.5.0
Quoted 4 lines · Oleg Nesterov
get_arg_page()checksbprm->rlim_stack.rlim_curand re-calculates the "extra" size for argv/envp pointers every time, this is a bit ugly and even not strictly correct:acct_arg_size()must not account this size.
Quoted 8 lines
Remove all the rlimit code in
get_arg_page(). Instead, addbprm->argmincalculated once at the start of__do_execve_file()and changecopy_stringsto checkbprm->p>=bprm->argmin.The patch adds the new helper,
prepare_arg_pages()which initializes bprm->argc/envc andbprm->argmin.Signed-off-by Oleg Nesterov <[email protected]>
Thanks for nailing this all down. :)
-Kees
Quoted 158 lines
--- fs/exec.c | 103 +++++++++++++++++++++++------------------------- include/linux/binfmts.h | 1 + 2 files changed, 51 insertions(+), 53 deletions(-)fs/exec.c +50
-53@@ -218,55 +218,10 @@ static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos,if (ret <=0 )return NULL ; -if (write) { -unsigned long size = bprm->vma->vm_end - bprm->vma->vm_start; -unsigned long ptr_size, limit; - -/* -* Since the stack will hold pointers to the strings, we -* must account for them as well. -* -* The size calculation is the entire vma while each arg page is -* built, so each time we get here it's calculating how far it -* is currently (rather than each call being just the newly -* added size from the arg page). As a result, we need to -* always add the entire size of the pointers, so that on the -* last call to get_arg_page() we'll actually have the entire -* correct size. -*/ - ptr_size = (bprm->argc + bprm->envc) *sizeof (void *); -if (ptr_size >ULONG_MAX - size) - goto fail; - size += ptr_size; - -acct_arg_size (bprm, size /PAGE_SIZE ); - -/* -* We've historically supported up to 32 pages (ARG_MAX) -* of argument strings even with small stacks -*/ -if (size <=ARG_MAX ) -return page; - -/* -* Limit to 1/4 of the max stack size or 3/4 of _STK_LIM -* (whichever is smaller) for the argv+env strings. -* This ensures that: -* - the remaining binfmt code will not run out of stack space, -* - the program will have a reasonable amount of stack left -* to work from. -*/ - limit = _STK_LIM /4 *3 ; - limit =min (limit, bprm->rlim_stack.rlim_cur /4 ); -if (size > limit) - goto fail; - } +if (write) +acct_arg_size (bprm,vma_pages (bprm->vma));return page; - -fail: -put_page (page); -return NULL ; }static void put_arg_page (struct page *page)@@ -492,6 +447,50 @@ static int count(struct user_arg_ptr argv, int max)return i; } +static int prepare_arg_pages (struct linux_binprm *bprm, +struct user_arg_ptr argv,struct user_arg_ptr envp) +{ +unsigned long limit, ptr_size; + + bprm->argc =count (argv,MAX_ARG_STRINGS ); +if (bprm->argc <0 ) +return bprm->argc; + + bprm->envc =count (envp,MAX_ARG_STRINGS ); +if (bprm->envc <0 ) +return bprm->envc; + +/* +* Limit to 1/4 of the max stack size or 3/4 of _STK_LIM +* (whichever is smaller) for the argv+env strings. +* This ensures that: +* - the remaining binfmt code will not run out of stack space, +* - the program will have a reasonable amount of stack left +* to work from. +*/ + limit = _STK_LIM /4 *3 ; + limit =min (limit, bprm->rlim_stack.rlim_cur /4 ); +/* +* We've historically supported up to 32 pages (ARG_MAX) +* of argument strings even with small stacks +*/ + limit =max (limit, (unsigned long )ARG_MAX ); +/* +* We must account for the size of all the argv and envp pointers to +* the argv and envp strings, since they will also take up space in +* the stack. They aren't stored until much later when we can't +* signal to the parent that the child has run out of stack space. +* Instead, calculate it here so it's possible to fail gracefully. +*/ + ptr_size = (bprm->argc + bprm->envc) *sizeof (void *); +if (limit <= ptr_size) +return -E2BIG ; + limit -= ptr_size; + + bprm->argmin = bprm->p - limit; +return 0 ; +} +/* * 'copy_strings()' copies argument/environment strings from the old * processes's memory to the new process's stack. The call to get_user_pages() @@ -527,6 +526,8 @@ static int copy_strings(int argc, struct user_arg_ptr argv, pos = bprm->p; str += len; bprm->p -= len; +if (bprm->p < bprm->argmin) + goto out;while (len >0 ) {int offset, bytes_to_copy;@@ -1789,12 +1790,8 @@ static int __do_execve_file(int fd, struct filename *filename,if (retval) goto out_unmark; - bprm->argc =count (argv,MAX_ARG_STRINGS ); -if ((retval = bprm->argc) <0 ) - goto out; - - bprm->envc =count (envp,MAX_ARG_STRINGS ); -if ((retval = bprm->envc) <0 ) + retval =prepare_arg_pages (bprm, argv, envp); +if (retval <0 ) goto out; retval =prepare_binprm (bprm);include/linux/binfmts.h +1
-1@@ -25,6 +25,7 @@ struct linux_binprm {#endif struct mm_struct *mm;unsigned long p;/* current top of mem */ +unsigned long argmin;/* rlimit marker for copy_strings() */ unsigned int /* * True after the bprm_set_creds hook has been called once -- 2.5.0
Signature · 1 line
Kees Cook
Hi,
Quoted 8 lines · Oleg Nesterov
get_arg_page()checksbprm->rlim_stack.rlim_curand re-calculates the "extra" size for argv/envp pointers every time, this is a bit ugly and even not strictly correct:acct_arg_size()must not account this size.Remove all the rlimit code in
get_arg_page(). Instead, addbprm->argmincalculated once at the start of__do_execve_file()and changecopy_stringsto checkbprm->p>=bprm->argmin.
Quoted 5 lines
The patch adds the new helper,
prepare_arg_pages()which initializes bprm->argc/envc andbprm->argmin.Signed-off-by Oleg Nesterov <[email protected]>Acked-by Kees Cook <[email protected]>
This patch results in various qemu boot failures in -next. Bisect logs are attached. It looks like all nommu boots are failing. I bisected an385 and m68k, but xtensa (kc705-nommu) fails with the same error.
[ 5.416926] Run /sbin/init as init process [ 5.419792] Failed to execute /sbin/init (error -7) [ 5.421128] Run /sbin/init as init process [ 5.423284] Starting init: /sbin/init exists but couldn't execute it (error -7) [ 5.424747] Run /etc/init as init process [ 5.428507] Run /bin/init as init process [ 5.430478] Run /bin/sh as init process [ 5.433179] Starting init: /bin/sh exists but couldn't execute it (error -7)
Guenter
---
an385:
# bad: [8c9733fd9806c71e7f2313a280f98cb3051f93df] Add linux-next specific files for 20181123
# good: [9ff01193a20d391e8dbce4403dd5ef87c7eaaca6] Linux 4.20-rc3
git bisect start 'HEAD' 'v4.20-rc3'
# good: [34c2101b4f765edf1b91c2837da9c60fbf9f6912] Merge remote-tracking branch 'spi-nor/spi-nor/next' git bisect good 34c2101b4f765edf1b91c2837da9c60fbf9f6912
# good: [15367a0657fc8027ff3466bf0202bde9f270259b] Merge remote-tracking branch 'kgdb/kgdb-next'
git bisect good 15367a0657fc8027ff3466bf0202bde9f270259b
# good: [d29686ab179c34c5dbaac067a9effbeeb6a8073e] Merge remote-tracking branch 'soundwire/next'
git bisect good d29686ab179c34c5dbaac067a9effbeeb6a8073e
# good: [3381311f9a261c9d2863f0d52e9499c88ccb1f44] Merge remote-tracking branch 'cgroup/for-next'
git bisect good 3381311f9a261c9d2863f0d52e9499c88ccb1f44
# good: [b5bf9c4c28a25b74475c3f3d3fe6d5d737629ab7] Merge remote-tracking branch 'pinctrl/for-next' git bisect good b5bf9c4c28a25b74475c3f3d3fe6d5d737629ab7
# good: [a75f6952444a401f5464e977caff2a84118b66c9] fs/epoll: deal with wait_queue only once
git bisect good a75f6952444a401f5464e977caff2a84118b66c9
# good: [d83de16290eefb4372520a61744b11bddb6e8f7e] Merge remote-tracking branch 'slimbus/for-next' git bisect good d83de16290eefb4372520a61744b11bddb6e8f7e
# good: [f2d2d58687c8300fe4ebf09d78b26cd68c825df7] Merge remote-tracking branch 'xarray/xarray'
git bisect good f2d2d58687c8300fe4ebf09d78b26cd68c825df7
# bad: [eac22eb65c3cc96d8cb2c7aac7dd816030a86955] ipc: allow boot time extension of IPCMNI from 32k to 8M git bisect bad eac22eb65c3cc96d8cb2c7aac7dd816030a86955
# good: [fb95dde314b136e9598a56350b3de3994d272486] exec: increase BINPRM_BUF_SIZE to 256
git bisect good fb95dde314b136e9598a56350b3de3994d272486
# bad: [fb704fe633a3ca80a68282d5c3c665c4382f500b] exec-separate-mm_anonpages-and-rlimit_stack-accounting-checkpatch-fixes git bisect bad fb704fe633a3ca80a68282d5c3c665c4382f500b
# bad: [eed684faf14610c2063c1f03e0a1f5ef56f23bb4] exec: separate MM_ANONPAGES and RLIMIT_STACK accounting git bisect bad eed684faf14610c2063c1f03e0a1f5ef56f23bb4
# first bad commit: [eed684faf14610c2063c1f03e0a1f5ef56f23bb4] exec: separate MM_ANONPAGES and RLIMIT_STACK accounting
---
m68k:
# bad: [8c9733fd9806c71e7f2313a280f98cb3051f93df] Add linux-next specific files for 20181123
# good: [9ff01193a20d391e8dbce4403dd5ef87c7eaaca6] Linux 4.20-rc3
git bisect start 'HEAD' 'v4.20-rc3'
# good: [34c2101b4f765edf1b91c2837da9c60fbf9f6912] Merge remote-tracking branch 'spi-nor/spi-nor/next' git bisect good 34c2101b4f765edf1b91c2837da9c60fbf9f6912
# good: [15367a0657fc8027ff3466bf0202bde9f270259b] Merge remote-tracking branch 'kgdb/kgdb-next'
git bisect good 15367a0657fc8027ff3466bf0202bde9f270259b
# good: [d29686ab179c34c5dbaac067a9effbeeb6a8073e] Merge remote-tracking branch 'soundwire/next'
git bisect good d29686ab179c34c5dbaac067a9effbeeb6a8073e
# good: [3381311f9a261c9d2863f0d52e9499c88ccb1f44] Merge remote-tracking branch 'cgroup/for-next'
git bisect good 3381311f9a261c9d2863f0d52e9499c88ccb1f44
# good: [b5bf9c4c28a25b74475c3f3d3fe6d5d737629ab7] Merge remote-tracking branch 'pinctrl/for-next' git bisect good b5bf9c4c28a25b74475c3f3d3fe6d5d737629ab7
# good: [a75f6952444a401f5464e977caff2a84118b66c9] fs/epoll: deal with wait_queue only once
git bisect good a75f6952444a401f5464e977caff2a84118b66c9
# good: [d83de16290eefb4372520a61744b11bddb6e8f7e] Merge remote-tracking branch 'slimbus/for-next' git bisect good d83de16290eefb4372520a61744b11bddb6e8f7e
# good: [f2d2d58687c8300fe4ebf09d78b26cd68c825df7] Merge remote-tracking branch 'xarray/xarray'
git bisect good f2d2d58687c8300fe4ebf09d78b26cd68c825df7
# bad: [eac22eb65c3cc96d8cb2c7aac7dd816030a86955] ipc: allow boot time extension of IPCMNI from 32k to 8M git bisect bad eac22eb65c3cc96d8cb2c7aac7dd816030a86955
# good: [fb95dde314b136e9598a56350b3de3994d272486] exec: increase BINPRM_BUF_SIZE to 256
git bisect good fb95dde314b136e9598a56350b3de3994d272486
# bad: [fb704fe633a3ca80a68282d5c3c665c4382f500b] exec-separate-mm_anonpages-and-rlimit_stack-accounting-checkpatch-fixes git bisect bad fb704fe633a3ca80a68282d5c3c665c4382f500b
# bad: [eed684faf14610c2063c1f03e0a1f5ef56f23bb4] exec: separate MM_ANONPAGES and RLIMIT_STACK accounting git bisect bad eed684faf14610c2063c1f03e0a1f5ef56f23bb4
# first bad commit: [eed684faf14610c2063c1f03e0a1f5ef56f23bb4] exec: separate MM_ANONPAGES and RLIMIT_STACK accounting
Hi Guenter,
Quoted 10 lines · Guenter Roeck
Quoted 8 lines · Oleg Nesterov
get_arg_page()checksbprm->rlim_stack.rlim_curand re-calculates the "extra" size for argv/envp pointers every time, this is a bit ugly and even not strictly correct:acct_arg_size()must not account this size.Remove all the rlimit code in
get_arg_page(). Instead, addbprm->argmincalculated once at the start of__do_execve_file()and changecopy_stringsto checkbprm->p>=bprm->argmin.
Quoted 8 lines
Quoted 5 lines
The patch adds the new helper,
prepare_arg_pages()which initializes bprm->argc/envc andbprm->argmin.Signed-off-by Oleg Nesterov <[email protected]>Acked-by Kees Cook <[email protected]>This patch results in various qemu boot failures in -next. Bisect logs are attached. It looks like all nommu boots are failing.
^^^^^
Ah, thanks.
Yes, I forgot about the !CONFIG_MMU version of get_arg_page() which doesn't check RLIMIT_STACK at all.
I'll send the trivial fix. Meanwile, could you test the patch below? to ensure this is the only problem.
Oleg.
fs/exec.c +2 -0
@@ -526,8 +526,10 @@ static int copy_strings(int argc, struct user_arg_ptr argv, pos = bprm->p; str += len; bprm->p -= len; +#ifdef CONFIG_MMU if (bprm->p < bprm->argmin) goto out; +#endif while (len >0 ) {int offset, bytes_to_copy;
Guenter Roeck
Tested-byHi Oleg,
Quoted 23 lines · Oleg Nesterov
Hi Guenter,
Quoted 10 lines · Guenter Roeck
Quoted 8 lines · Oleg Nesterov
get_arg_page()checksbprm->rlim_stack.rlim_curand re-calculates the "extra" size for argv/envp pointers every time, this is a bit ugly and even not strictly correct:acct_arg_size()must not account this size.Remove all the rlimit code in
get_arg_page(). Instead, addbprm->argmincalculated once at the start of__do_execve_file()and changecopy_stringsto checkbprm->p>=bprm->argmin.Quoted 8 lines
Quoted 5 lines
The patch adds the new helper,
prepare_arg_pages()which initializes bprm->argc/envc andbprm->argmin.Signed-off-by Oleg Nesterov <[email protected]>Acked-by Kees Cook <[email protected]>This patch results in various qemu boot failures in -next. Bisect logs are attached. It looks like all nommu boots are failing.
^^^^^
Quoted 8 lines
Ah, thanks.
Yes, I forgot about the !CONFIG_MMU version of
get_arg_page()which doesn't checkRLIMIT_STACKat all.I'll send the trivial fix. Meanwile, could you test the patch below? to ensure this is the only problem.
with arm:mps2-an385, xtensa:kc705-nommu, and m68k:mcf5208evb.
Thanks,
Guenter
Quoted 16 lines
Oleg.
fs/exec.c +2
-0@@ -526,8 +526,10 @@ static int copy_strings(int argc, struct user_arg_ptr argv, pos = bprm->p; str += len; bprm->p -= len; +#ifdef CONFIG_MMU if (bprm->p < bprm->argmin) goto out; +#endif while (len >0 ) {int offset, bytes_to_copy;