Skip to contentSkip to conversation

exec: separate MM_ANONPAGES and RLIMIT_STACK accounting

PATCH In review Oleg Nesterov 3 participants 5 messages Nov 12, 2018 – Nov 26, 2018

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

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
index fc281b7..61a5460 100644
@@ -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
index e9f5fe6..03200a8 100644
@@ -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
Acked-by on v1 1/1
#1 lore raw
Quoted 4 lines · Oleg Nesterov

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.

Quoted 8 lines

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.

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
index fc281b7..61a5460 100644
@@ -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
index e9f5fe6..03200a8 100644
@@ -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
#2 lore raw
In reply to Oleg Nesterov

Hi,

Quoted 8 lines · Oleg Nesterov

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.

Quoted 5 lines

The patch adds the new helper, prepare_arg_pages() which initializes bprm->argc/envc and bprm->argmin.

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
#3 lore raw

Hi Guenter,

Quoted 10 lines · Guenter Roeck
Quoted 8 lines · Oleg Nesterov

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.

Quoted 8 lines
Quoted 5 lines

The patch adds the new helper, prepare_arg_pages() which initializes bprm->argc/envc and bprm->argmin.

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;
Tested-by
#4 lore raw

Hi Oleg,

Quoted 23 lines · Oleg Nesterov

Hi Guenter,

Quoted 10 lines · Guenter Roeck
Quoted 8 lines · Oleg Nesterov

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.

Quoted 8 lines
Quoted 5 lines

The patch adds the new helper, prepare_arg_pages() which initializes bprm->argc/envc and bprm->argmin.

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 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.

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;