[FIX] Re: Argument list too long: out of environment space

From: Khimenko Victor (khim@sch57.msk.ru)
Date: Sat Mar 04 2000 - 15:34:11 EST


In <20000303013436.A16284@pcep-jamie.cern.ch> Jamie Lokier (lk@tantalophile.demon.co.uk) wrote:
> Stuart Inglis wrote:
>> /*
>> * MAX_ARG_PAGES defines the number of pages allocated for arguments
>> * and envelope for the new program. 32 should suffice, this gives
>> * a maximum env+arg of 128kB w/4KB pages!
>> */
>> #define MAX_ARG_PAGES 32
>>
>> This seems to be the cause of our problem. When the total size of the
>> command line is over 128kB we start encountering this problem.
>>
>> Can this value be safely increased? Does it need to be constant at all?

> Yes it can be increased. I had a project with 1MB command lines, so we
> increased MAX_ARG_PAGES and everything was fine. Until our command
> lines grew too long again... :-)

You can not make it arbitrary large since structure with array of
MAX_ARG_PAGES number of pointers is allocated on tiny kernel stack.

> We looked for a way to use smaller command lines. Not xargs -- that
> didn't do the right thing. We could change our commands to read the
> list of files, but we didn't find a way to convince Make to write the
> **ing list of files to a file. "echo $(FILES) > listfile" doesn't work
> for the obvious reason.

> I was really tempted to add a $(echo $(FILES)) function to GNU Make to
> get around this.

> But the quicker solution at the time was to increase MAX_ARG_PAGES and
> it was fine.

> Feel free to remove the limit altogether and make it dynamic. Something
> involving an extra struct mm which you are filling up ptrace-style might
> be appropriate. It must be pageable if it can be arbitrarily large.

Is it really needed ? This is HARD problem. Since you need to keep in one
address space both arguments passed to execve and new argv array. You can
try to scan source arguments and make clever rearrange of strings in memory
(this is not simple: think about something like
execl("bin/echo",plong_string,plong_string+10,0); for example) or you can
just clone current process and move agruments from one process to another...
Both ways looks EXTREMALLY hairy to me and not needed in most cases (of course
ability to sucessfully call execve with argv bigger then amount of RAM in
system is sexy but how often it's really needed?).

If you need just configurable limit here is the patch (do not make RLIMIT_ARG
bigger then RLIMIT_STACK : since arguments are placed on stack this will just
lead to strange process crashes instead of simple "Argument list too long"
message).

P.S. It's tested only on i386, perhaps other architectures will need additional
tweaks...

diff -uNr linux/fs/exec.c linux-2.3.49/fs/exec.c
--- linux/fs/exec.c Tue Feb 29 22:13:27 2000
+++ linux-2.3.49/fs/exec.c Sat Mar 4 20:45:53 2000
@@ -196,6 +196,10 @@
                 char *str;
                 int len;
                 unsigned long pos;
+ struct page *page1=0;
+ struct page *page2=0;
+ struct page **page1_addr;
+ struct page **page2_addr;
 
                 if (get_user(str, argv+argc) || !str || !(len = strnlen_user(str, bprm->p)))
                         return -EFAULT;
@@ -208,17 +212,74 @@
                 pos = bprm->p;
                 while (len > 0) {
                         char *kaddr;
- int i, new, err;
+ int i, j, new, err;
                         struct page *page;
                         int offset, bytes_to_copy;
 
                         offset = pos % PAGE_SIZE;
- i = pos/PAGE_SIZE;
- page = bprm->page[i];
+ i = (bprm->top - pos)/PAGE_SIZE;
+ if (i<FAST_ARG_PAGES)
+ j=-2;
+ else if (i<FAST_ARG_PAGES+PAGE_SIZE/sizeof(struct page *)) {
+ i=i-FAST_ARG_PAGES;
+ j=-1;
+ } else {
+ /* XXX In double indirection is not enough we
+ can crash. Only root can do this anyway */
+ j=(i-(FAST_ARG_PAGES-PAGE_SIZE/sizeof(struct page *)))/
+ (PAGE_SIZE/sizeof(struct page *));
+ i=(i-(FAST_ARG_PAGES-PAGE_SIZE/sizeof(struct page *)))%
+ (PAGE_SIZE/sizeof(struct page *));
+ }
+ if (j>=0 && !page2) {
+ page2 = bprm->page[FAST_ARG_PAGES+1];
+ new = 0;
+ if (!page2) {
+ page2 = alloc_page(GFP_HIGHUSER);
+ bprm->page[FAST_ARG_PAGES+1] = page2;
+ if (!bprm->page[FAST_ARG_PAGES+1])
+ return -ENOMEM;
+ new = 1;
+ }
+ page2_addr = (struct page **)kmap(page2);
+ if (new)
+ memset(page2_addr, 0, PAGE_SIZE);
+ }
+ if (j>=-1) {
+ page = (j == -1) ? bprm->page[FAST_ARG_PAGES] : page2_addr[j];
+ new = 0;
+ if (!page) {
+ if (page1) {
+ flush_page_to_ram(page1);
+ kunmap(page1);
+ }
+ page1 = alloc_page(GFP_HIGHUSER);
+ if (j == -1)
+ bprm->page[FAST_ARG_PAGES] = page1;
+ else
+ page2_addr[j] = page1;
+ if (!page1)
+ return -ENOMEM;
+ new = 1;
+ } else if (page != page1) {
+ if (page1) {
+ flush_page_to_ram(page1);
+ kunmap(page1);
+ }
+ page1 = page;
+ }
+ page1_addr = (struct page **)kmap(page1);
+ if (new)
+ memset(page1_addr, 0, PAGE_SIZE);
+ }
+ page = (j == -2) ? bprm->page[i] : page1_addr[i];
                         new = 0;
                         if (!page) {
                                 page = alloc_page(GFP_HIGHUSER);
- bprm->page[i] = page;
+ if (j == -2)
+ bprm->page[i] = page;
+ else
+ page1_addr[i] = page;
                                 if (!page)
                                         return -ENOMEM;
                                 new = 1;
@@ -236,7 +297,7 @@
                         err = copy_from_user(kaddr + offset, str, bytes_to_copy);
                         flush_page_to_ram(page);
                         kunmap(page);
-
+
                         if (err)
                                 return -EFAULT;
 
@@ -244,6 +305,14 @@
                         str += bytes_to_copy;
                         len -= bytes_to_copy;
                 }
+ if (page2) {
+ flush_page_to_ram(page2);
+ kunmap(page2);
+ }
+ if (page1) {
+ flush_page_to_ram(page1);
+ kunmap(page1);
+ }
         }
         return 0;
 }
@@ -298,11 +367,12 @@
 
 int setup_arg_pages(struct linux_binprm *bprm)
 {
- unsigned long stack_base;
+ unsigned long stack_base, pages_base;
         struct vm_area_struct *mpnt;
         int i;
 
- stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;
+ stack_base = (STACK_TOP - bprm->top - 1) & PAGE_MASK;
+ pages_base = (STACK_TOP - bprm->top + bprm->p - 1) & PAGE_MASK;
 
         bprm->p += stack_base;
         if (bprm->loader)
@@ -328,13 +398,51 @@
                 vmlist_modify_unlock(current->mm);
                 current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
         }
+
+ if (bprm->page[FAST_ARG_PAGES+1]) {
+ struct page **page2_addr = (struct page **)kmap(bprm->page[FAST_ARG_PAGES+1]);
+
+ for (i = PAGE_SIZE/sizeof(struct page *)-1; i >= 0 ; i--) {
+ if (page2_addr[i]) {
+ struct page **page1_addr = (struct page **)kmap(page2_addr[i]);
+ int j;
+
+ for (j = PAGE_SIZE/sizeof(struct page *) - 1; j >= 0 ; j--)
+ if (page1_addr[j]) {
+ current->mm->rss++;
+ put_dirty_page(current,page1_addr[j],pages_base);
+ pages_base += PAGE_SIZE;
+ }
 
- for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
+ kunmap(page2_addr[i]);
+ __free_page(page2_addr[i]);
+ }
+ }
+
+ kunmap(bprm->page[FAST_ARG_PAGES+1]);
+ __free_page(bprm->page[FAST_ARG_PAGES+1]);
+ }
+
+ if (bprm->page[FAST_ARG_PAGES]) {
+ struct page **page1_addr = (struct page **)kmap(bprm->page[FAST_ARG_PAGES]);
+
+ for (i = PAGE_SIZE/sizeof(struct page *)-1 ; i >= 0; i--)
+ if (page1_addr[i]) {
+ current->mm->rss++;
+ put_dirty_page(current,page1_addr[i],pages_base);
+ pages_base += PAGE_SIZE;
+ }
+
+ kunmap(bprm->page[FAST_ARG_PAGES]);
+ __free_page(bprm->page[FAST_ARG_PAGES]);
+ }
+
+ for (i = FAST_ARG_PAGES-1 ; i >= 0 ; i--) {
                 if (bprm->page[i]) {
                         current->mm->rss++;
- put_dirty_page(current,bprm->page[i],stack_base);
+ put_dirty_page(current,bprm->page[i],pages_base);
+ pages_base += PAGE_SIZE;
                 }
- stack_base += PAGE_SIZE;
         }
         
         return 0;
@@ -700,19 +808,51 @@
                 unsigned long offset;
                 char * kaddr;
                 struct page *page;
+ struct page *page1 = 0;
+ struct page **page1_addr;
+ struct page *page2 = 0;
+ struct page **page2_addr;
 
                 offset = bprm->p % PAGE_SIZE;
                 goto inside;
 
                 while (bprm->p++, *(kaddr+offset++)) {
+ int i;
                         if (offset != PAGE_SIZE)
                                 continue;
                         offset = 0;
                         kunmap(page);
 inside:
- page = bprm->page[bprm->p/PAGE_SIZE];
+ i = (bprm->top - bprm->p)/PAGE_SIZE;
+ if (i<FAST_ARG_PAGES) {
+ page = bprm->page[i];
+ } else if (i<FAST_ARG_PAGES+PAGE_SIZE/sizeof(struct page *)) {
+ if (!page1 || page1 != bprm->page[FAST_ARG_PAGES])
+ kunmap(page1);
+ page1 = bprm->page[FAST_ARG_PAGES];
+ page1_addr = (struct page **)kmap(page1);
+ page = page1_addr[i-FAST_ARG_PAGES];
+ } else {
+ int j;
+ /* XXX In double indirection is not enough we
+ can crash. Only root can do this anyway */
+ page2 = bprm->page[FAST_ARG_PAGES];
+ page2_addr = (struct page **)kmap(page2);
+ j=(i-(FAST_ARG_PAGES-PAGE_SIZE/sizeof(struct page *)))/
+ (PAGE_SIZE/sizeof(struct page *));
+ if (!page1 || page1 != page2_addr[j])
+ kunmap(page1);
+ page1 = page2_addr[j];
+ page1_addr = (struct page **)kmap(page1);
+ page = page1_addr[(i-(FAST_ARG_PAGES-PAGE_SIZE/sizeof(struct page *)))%
+ (PAGE_SIZE/sizeof(struct page *))];
+ }
                         kaddr = (char *)kmap(page);
                 }
+ if (page2)
+ kunmap(page2);
+ if (page1)
+ kunmap(page1);
                 kunmap(page);
                 bprm->argc--;
         }
@@ -814,8 +954,9 @@
         int retval;
         int i;
 
- bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
- memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0]));
+ bprm.top = ( (current->rlim[RLIMIT_ARG].rlim_cur + PAGE_SIZE - 1) & PAGE_MASK ) - 1; /* Not exactly top but one byte lower */
+ bprm.p = ( (current->rlim[RLIMIT_ARG].rlim_cur + PAGE_SIZE - 1) & PAGE_MASK ) - sizeof(void *);
+ memset(bprm.page, 0, (FAST_ARG_PAGES + 2) * sizeof ( bprm.page[0] ) );
 
         lock_kernel();
         dentry = open_namei(filename, 0, 0);
@@ -874,12 +1015,43 @@
                 unlock_kernel();
         }
 
- /* Assumes that free_page() can take a NULL argument. */
- /* I hope this is ok for all architectures */
- for (i = 0 ; i < MAX_ARG_PAGES ; i++)
+ for (i = 0 ; i < FAST_ARG_PAGES ; i++)
                 if (bprm.page[i])
                         __free_page(bprm.page[i]);
 
+ if (bprm.page[FAST_ARG_PAGES]) {
+ struct page **page1_addr =
+ (struct page **)kmap(bprm.page[FAST_ARG_PAGES]);
+
+ for (i = 0; i < PAGE_SIZE/sizeof(struct page *); i++)
+ if (page1_addr[i])
+ __free_page(page1_addr[i]);
+
+ kunmap(bprm.page[FAST_ARG_PAGES]);
+ __free_page(bprm.page[FAST_ARG_PAGES]);
+ }
+
+ if (bprm.page[FAST_ARG_PAGES+1]) {
+ struct page **page2_addr = (struct page **)kmap(bprm.page[FAST_ARG_PAGES+1]);
+
+ for (i = 0; i < PAGE_SIZE/sizeof(struct page *); i++) {
+ if (page2_addr[i]) {
+ struct page **page1_addr = (struct page **)kmap(page2_addr[i]);
+ int j;
+
+ for (j = 0; j < PAGE_SIZE/sizeof(struct page *); j++)
+ if (page1_addr[j])
+ __free_page(page1_addr[j]);
+
+ kunmap(page2_addr[i]);
+ __free_page(page2_addr[i]);
+ }
+ }
+
+ kunmap(bprm.page[FAST_ARG_PAGES+1]);
+ __free_page(bprm.page[FAST_ARG_PAGES+1]);
+ }
+
         return retval;
 }
 
diff -uNr linux/include/asm-i386/resource.h linux-2.3.49/include/asm-i386/resource.h
--- linux/include/asm-i386/resource.h Thu Feb 17 20:35:07 2000
+++ linux-2.3.49/include/asm-i386/resource.h Sat Mar 4 20:45:54 2000
@@ -15,8 +15,9 @@
 #define RLIMIT_NOFILE 7 /* max number of open files */
 #define RLIMIT_MEMLOCK 8 /* max locked-in-memory address space */
 #define RLIMIT_AS 9 /* address space limit */
+#define RLIMIT_ARG 10 /* max # bytes of args + environ for exec() */
 
-#define RLIM_NLIMITS 10
+#define RLIM_NLIMITS 11
 
 /*
  * SuS says limits have to be unsigned.
@@ -38,6 +39,7 @@
         { INR_OPEN, INR_OPEN }, \
         { RLIM_INFINITY, RLIM_INFINITY }, \
         { RLIM_INFINITY, RLIM_INFINITY }, \
+ { _ARG_LIM, RLIM_INFINITY }, \
 }
 
 #endif /* __KERNEL__ */
diff -uNr linux/include/linux/binfmts.h linux-2.3.49/include/linux/binfmts.h
--- linux/include/linux/binfmts.h Fri Mar 3 01:01:28 2000
+++ linux-2.3.49/include/linux/binfmts.h Sat Mar 4 20:45:54 2000
@@ -5,11 +5,12 @@
 #include <linux/capability.h>
 
 /*
- * MAX_ARG_PAGES defines the number of pages allocated for arguments
+ * FAST_ARG_PAGES defines the number of pages allocated for arguments
  * and envelope for the new program. 32 should suffice, this gives
- * a maximum env+arg of 128kB w/4KB pages!
+ * a maximum env+arg of 128kB w/4KB pages! If it's not enough then
+ * additional pages will be allocated to store that information...
  */
-#define MAX_ARG_PAGES 32
+#define FAST_ARG_PAGES 32
 
 #ifdef __KERNEL__
 
@@ -18,8 +19,9 @@
  */
 struct linux_binprm{
         char buf[128];
- struct page *page[MAX_ARG_PAGES];
+ struct page *page[FAST_ARG_PAGES+2];
         unsigned long p; /* current top of mem */
+ unsigned long top; /* real top of mem */
         int sh_bang;
         struct dentry * dentry;
         int e_uid, e_gid;
diff -uNr linux/include/linux/limits.h linux-2.3.49/include/linux/limits.h
--- linux/include/linux/limits.h Wed Jul 28 21:30:10 1999
+++ linux-2.3.49/include/linux/limits.h Sat Mar 4 20:45:54 2000
@@ -4,7 +4,7 @@
 #define NR_OPEN 1024
 
 #define NGROUPS_MAX 32 /* supplemental group IDs are available */
-#define ARG_MAX 131072 /* # bytes of args + environ for exec() */
+#define ARG_MAX 1048576 /* # bytes of args + environ for exec() */
 #define CHILD_MAX 999 /* no limit :-) */
 #define OPEN_MAX 256 /* # open files a process may have */
 #define LINK_MAX 127 /* # links a file may have */
diff -uNr linux/include/linux/sched.h linux-2.3.49/include/linux/sched.h
--- linux/include/linux/sched.h Fri Mar 3 01:01:33 2000
+++ linux-2.3.49/include/linux/sched.h Sat Mar 4 20:45:54 2000
@@ -378,11 +378,13 @@
 #define PF_DTRACE 0x00200000 /* delayed trace (used on m68k, i386) */
 
 /*
- * Limit the stack by to some sane default: root can always
+ * Limit the stack & args by to some sane default: root can always
  * increase this limit if needed.. 8MB seems reasonable.
  */
 #define _STK_LIM (8*1024*1024)
 
+#define _ARG_LIM (1024*1024)
+
 #define DEF_PRIORITY (20*HZ/100) /* 200 ms time slices */
 
 /*

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Tue Mar 07 2000 - 21:00:17 EST