public inbox for cygwin-patches@cygwin.com
 help / color / mirror / Atom feed
* [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
@ 2023-08-28  9:46 Takashi Yano
  2023-08-28 10:57 ` Corinna Vinschen
  0 siblings, 1 reply; 10+ messages in thread
From: Takashi Yano @ 2023-08-28  9:46 UTC (permalink / raw)
  To: cygwin-patches; +Cc: Takashi Yano, Ed Morton

Previously, the number of command line args was not checked for
cygwin process. Due to this, segmentation fault was caused if too
many command line args are specified.
https://cygwin.com/pipermail/cygwin/2023-August/254333.html

Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
STATUS_STACK_OVERFLOW occurs if the stack does not have enough
space.

With this patch, the total length of the arguments and the size of
argv[] is restricted to 1/4 of total stack size for the process, and
spawnve() returns E2BIG if the size exceeds the limit.

Reported-by: Ed Morton <mortoneccc@comcast.net>
Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
---
 winsup/cygwin/release/3.4.9 |  3 +++
 winsup/cygwin/spawn.cc      | 43 ++++++++++++++++++++++++++++++++++++-
 winsup/cygwin/sysconf.cc    |  9 +++++++-
 3 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/winsup/cygwin/release/3.4.9 b/winsup/cygwin/release/3.4.9
index 2f2da9e13..53c4e5fc8 100644
--- a/winsup/cygwin/release/3.4.9
+++ b/winsup/cygwin/release/3.4.9
@@ -8,3 +8,6 @@ Bug Fixes
 - For the time being, disable creating special files using mknod/mkfifo
   on NFS.
   Addresses: https://cygwin.com/pipermail/cygwin/2023-August/254266.html
+
+- Fix segfault when too many command line args are specified.
+  Addresses: https://cygwin.com/pipermail/cygwin/2023-August/254333.html
diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc
index 32ba5b377..06b62cd42 100644
--- a/winsup/cygwin/spawn.cc
+++ b/winsup/cygwin/spawn.cc
@@ -273,6 +273,29 @@ extern "C" void __posix_spawn_sem_release (void *sem, int error);
 
 extern DWORD mutex_timeout; /* defined in fhandler_termios.cc */
 
+static size_t
+get_stack_size (const WCHAR *filename)
+{
+  HANDLE h;
+  h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ,
+		   NULL, OPEN_EXISTING, 0, NULL);
+  char buf[1024];
+  DWORD n;
+  ReadFile (h, buf, sizeof (buf), &n, 0);
+  CloseHandle (h);
+  IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4);
+  if (!p)
+    return 0;
+  if ((char *) &p->OptionalHeader.SizeOfStackCommit > buf + n)
+    return 0; /* buf[] is not enough */
+  if (p->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+    return p->OptionalHeader.SizeOfStackReserve;
+  IMAGE_NT_HEADERS64 *p64 = (IMAGE_NT_HEADERS64 *) p;
+  if ((char *) &p64->OptionalHeader.SizeOfStackCommit > buf + n)
+    return 0; /* buf[] is not enough */
+  return p64->OptionalHeader.SizeOfStackReserve;
+}
+
 int
 child_info_spawn::worker (const char *prog_arg, const char *const *argv,
 			  const char *const envp[], int mode,
@@ -340,8 +363,9 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv,
 	 We need to quote any argument that has whitespace or embedded "'s.  */
 
       int ac;
+      size_t arg_len = 0;
       for (ac = 0; argv[ac]; ac++)
-	/* nothing */;
+	arg_len += strlen (argv[ac]) + 1;
 
       int err;
       const char *ext;
@@ -610,6 +634,23 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv,
 	    }
 	}
 
+      if (iscygwin ())
+	{
+	  size_t child_stack_size = get_stack_size (runpath);
+	  /* char *argv[] will be placed on the stack in dll_crt0_1(), so
+	     restrict total argument length and the size of argv[] to 1/4
+	     of total stack size. */
+	  arg_len = max (arg_len, sizeof (char *) * ac);
+	  bool too_many_args = child_stack_size ?
+	    arg_len > child_stack_size / 4 : ac >= MAXWINCMDLEN;
+	  if (too_many_args)
+	    {
+	      set_errno (E2BIG);
+	      res = -1;
+	      __leave;
+	    }
+	}
+
       bool no_pcon = mode != _P_OVERLAY && mode != _P_WAIT;
       term_spawn_worker.setup (iscygwin (), handle (fileno_stdin, false),
 			       runpath, no_pcon, reset_sendsig, envblock);
diff --git a/winsup/cygwin/sysconf.cc b/winsup/cygwin/sysconf.cc
index 2db92e4de..6cb2aecd0 100644
--- a/winsup/cygwin/sysconf.cc
+++ b/winsup/cygwin/sysconf.cc
@@ -21,6 +21,13 @@ details. */
 #include "cpuid.h"
 #include "clock.h"
 
+#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
+static long
+get_arg_max (int in)
+{
+  return (long) (get_rlimit_stack () + DEFAULT_STACKGUARD) / 4;
+}
+
 static long
 get_page_size (int in)
 {
@@ -485,7 +492,7 @@ static struct
     };
 } sca[] =
 {
-  {cons, {c:ARG_MAX}},			/*   0, _SC_ARG_MAX */
+  {func, {f:get_arg_max}},		/*   0, _SC_ARG_MAX */
   {cons, {c:CHILD_MAX}},		/*   1, _SC_CHILD_MAX */
   {cons, {c:CLOCKS_PER_SEC}},		/*   2, _SC_CLK_TCK */
   {cons, {c:NGROUPS_MAX}},		/*   3, _SC_NGROUPS_MAX */
-- 
2.39.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28  9:46 [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified Takashi Yano
@ 2023-08-28 10:57 ` Corinna Vinschen
  2023-08-28 11:09   ` Corinna Vinschen
                     ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Corinna Vinschen @ 2023-08-28 10:57 UTC (permalink / raw)
  To: cygwin-patches

On Aug 28 18:46, Takashi Yano wrote:
> Previously, the number of command line args was not checked for
> cygwin process. Due to this, segmentation fault was caused if too
> many command line args are specified.
> https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> 
> Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> space.
> 
> With this patch, the total length of the arguments and the size of
> argv[] is restricted to 1/4 of total stack size for the process, and
> spawnve() returns E2BIG if the size exceeds the limit.
> [...]
> +static size_t
> +get_stack_size (const WCHAR *filename)
> +{
> +  HANDLE h;
> +  h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ,
> +		   NULL, OPEN_EXISTING, 0, NULL);
> +  char buf[1024];
> +  DWORD n;
> +  ReadFile (h, buf, sizeof (buf), &n, 0);
> +  CloseHandle (h);
> +  IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4);
> +  if (!p)
> +    return 0;
> +  if ((char *) &p->OptionalHeader.SizeOfStackCommit > buf + n)
> +    return 0; /* buf[] is not enough */
> +  if (p->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
> +    return p->OptionalHeader.SizeOfStackReserve;
> +  IMAGE_NT_HEADERS64 *p64 = (IMAGE_NT_HEADERS64 *) p;
> +  if ((char *) &p64->OptionalHeader.SizeOfStackCommit > buf + n)
> +    return 0; /* buf[] is not enough */
> +  return p64->OptionalHeader.SizeOfStackReserve;
> +}

Sorry, but this proposal is too complicated, IMHO.

Checking the stacksize in the file header for each single execve
is quite a bit time consuming, isn't it?

The question is rather, why storing argv on the stack at all?  I guess
the original idea was that argv is always a rather overseeable number.
But it doesn't have to stay on the stack.

I tried this simple patch:

diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
index 49b7a44aeb15..961dea4ab993 100644
--- a/winsup/cygwin/dcrt0.cc
+++ b/winsup/cygwin/dcrt0.cc
@@ -978,11 +978,8 @@ dll_crt0_1 (void *)
 	 a change to an element of argv[] it does not affect Cygwin's argv.
 	 Changing the the contents of what argv[n] points to will still
 	 affect Cygwin.  This is similar (but not exactly like) Linux. */
-      char *newargv[__argc + 1];
-      char **nav = newargv;
-      char **oav = __argv;
-      while ((*nav++ = *oav++) != NULL)
-	continue;
+      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
+      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
       /* Handle any signals which may have arrived */
       sig_dispatch_pending (false);
       _my_tls.call_signal_handler ();

and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
as desired.  Combined with a bit of error checking...

> diff --git a/winsup/cygwin/sysconf.cc b/winsup/cygwin/sysconf.cc
> index 2db92e4de..6cb2aecd0 100644
> --- a/winsup/cygwin/sysconf.cc
> +++ b/winsup/cygwin/sysconf.cc
> @@ -21,6 +21,13 @@ details. */
>  #include "cpuid.h"
>  #include "clock.h"
>  
> +#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
> +static long
> +get_arg_max (int in)
> +{
> +  return (long) (get_rlimit_stack () + DEFAULT_STACKGUARD) / 4;
> +}
> +
>  static long
>  get_page_size (int in)
>  {
> @@ -485,7 +492,7 @@ static struct
>      };
>  } sca[] =
>  {
> -  {cons, {c:ARG_MAX}},			/*   0, _SC_ARG_MAX */
> +  {func, {f:get_arg_max}},		/*   0, _SC_ARG_MAX */
>    {cons, {c:CHILD_MAX}},		/*   1, _SC_CHILD_MAX */
>    {cons, {c:CLOCKS_PER_SEC}},		/*   2, _SC_CLK_TCK */
>    {cons, {c:NGROUPS_MAX}},		/*   3, _SC_NGROUPS_MAX */
> -- 
> 2.39.0

Along these lines, there's no reason to couple SC_ARG_MAX to the
size of the stack.  I'd propose to return the value 2097152.  It's
the default value returned by getconf ARG_MAX on LInx as well.

Corinna

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 10:57 ` Corinna Vinschen
@ 2023-08-28 11:09   ` Corinna Vinschen
  2023-08-28 11:18     ` Corinna Vinschen
  2023-08-28 12:14     ` Takashi Yano
  2023-08-28 12:09   ` Takashi Yano
                     ` (2 subsequent siblings)
  3 siblings, 2 replies; 10+ messages in thread
From: Corinna Vinschen @ 2023-08-28 11:09 UTC (permalink / raw)
  To: cygwin-patches

On Aug 28 12:57, Corinna Vinschen wrote:
> On Aug 28 18:46, Takashi Yano wrote:
> > Previously, the number of command line args was not checked for
> > cygwin process. Due to this, segmentation fault was caused if too
> > many command line args are specified.
> > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > 
> > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > space.
> > 
> > With this patch, the total length of the arguments and the size of
> > argv[] is restricted to 1/4 of total stack size for the process, and
> > spawnve() returns E2BIG if the size exceeds the limit.
> > [...]
> I tried this simple patch:
> 
> diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> index 49b7a44aeb15..961dea4ab993 100644
> --- a/winsup/cygwin/dcrt0.cc
> +++ b/winsup/cygwin/dcrt0.cc
> @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
>  	 a change to an element of argv[] it does not affect Cygwin's argv.
>  	 Changing the the contents of what argv[n] points to will still
>  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> -      char *newargv[__argc + 1];
> -      char **nav = newargv;
> -      char **oav = __argv;
> -      while ((*nav++ = *oav++) != NULL)
> -	continue;
> +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
>        /* Handle any signals which may have arrived */
>        sig_dispatch_pending (false);
>        _my_tls.call_signal_handler ();
> 
> and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> as desired.  Combined with a bit of error checking...

We may also get away with storing it in the Windows heap, but I didn't
test this .

> Along these lines, there's no reason to couple SC_ARG_MAX to the
> size of the stack.  I'd propose to return the value 2097152.  It's
> the default value returned by getconf ARG_MAX on LInx as well.

Oh, and we can carefully check in child_info_spawn::worker that
the args are not taking more space than the value returned by
SC_ARG_MAX and return E2BIG if so.  We do this when checking the
args for non-Cygwin apps anyway.


Thanks,
Corinna

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 11:09   ` Corinna Vinschen
@ 2023-08-28 11:18     ` Corinna Vinschen
  2023-08-28 12:14     ` Takashi Yano
  1 sibling, 0 replies; 10+ messages in thread
From: Corinna Vinschen @ 2023-08-28 11:18 UTC (permalink / raw)
  To: cygwin-patches

On Aug 28 13:09, Corinna Vinschen wrote:
> On Aug 28 12:57, Corinna Vinschen wrote:
> > On Aug 28 18:46, Takashi Yano wrote:
> > > Previously, the number of command line args was not checked for
> > > cygwin process. Due to this, segmentation fault was caused if too
> > > many command line args are specified.
> > > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > > 
> > > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > > space.
> > > 
> > > With this patch, the total length of the arguments and the size of
> > > argv[] is restricted to 1/4 of total stack size for the process, and
> > > spawnve() returns E2BIG if the size exceeds the limit.
> > > [...]
> > I tried this simple patch:
> > 
> > diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> > index 49b7a44aeb15..961dea4ab993 100644
> > --- a/winsup/cygwin/dcrt0.cc
> > +++ b/winsup/cygwin/dcrt0.cc
> > @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
> >  	 a change to an element of argv[] it does not affect Cygwin's argv.
> >  	 Changing the the contents of what argv[n] points to will still
> >  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> > -      char *newargv[__argc + 1];
> > -      char **nav = newargv;
> > -      char **oav = __argv;
> > -      while ((*nav++ = *oav++) != NULL)
> > -	continue;
> > +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> > +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
> >        /* Handle any signals which may have arrived */
> >        sig_dispatch_pending (false);
> >        _my_tls.call_signal_handler ();
> > 
> > and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> > as desired.  Combined with a bit of error checking...
> 
> We may also get away with storing it in the Windows heap, but I didn't
> test this .

No, we don't. The Windows heap would not be inherited by a forked
child and argv would be lost.  Sounds not great.


Corinna

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 10:57 ` Corinna Vinschen
  2023-08-28 11:09   ` Corinna Vinschen
@ 2023-08-28 12:09   ` Takashi Yano
  2023-08-28 12:20     ` Takashi Yano
  2023-08-28 12:27   ` Takashi Yano
  2023-08-28 12:51   ` Takashi Yano
  3 siblings, 1 reply; 10+ messages in thread
From: Takashi Yano @ 2023-08-28 12:09 UTC (permalink / raw)
  To: cygwin-patches

On Mon, 28 Aug 2023 12:57:19 +0200
Corinna Vinschen wrote:
> On Aug 28 18:46, Takashi Yano wrote:
> > Previously, the number of command line args was not checked for
> > cygwin process. Due to this, segmentation fault was caused if too
> > many command line args are specified.
> > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > 
> > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > space.
> > 
> > With this patch, the total length of the arguments and the size of
> > argv[] is restricted to 1/4 of total stack size for the process, and
> > spawnve() returns E2BIG if the size exceeds the limit.
> > [...]
> > +static size_t
> > +get_stack_size (const WCHAR *filename)
> > +{
> > +  HANDLE h;
> > +  h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ,
> > +		   NULL, OPEN_EXISTING, 0, NULL);
> > +  char buf[1024];
> > +  DWORD n;
> > +  ReadFile (h, buf, sizeof (buf), &n, 0);
> > +  CloseHandle (h);
> > +  IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4);
> > +  if (!p)
> > +    return 0;
> > +  if ((char *) &p->OptionalHeader.SizeOfStackCommit > buf + n)
> > +    return 0; /* buf[] is not enough */
> > +  if (p->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
> > +    return p->OptionalHeader.SizeOfStackReserve;
> > +  IMAGE_NT_HEADERS64 *p64 = (IMAGE_NT_HEADERS64 *) p;
> > +  if ((char *) &p64->OptionalHeader.SizeOfStackCommit > buf + n)
> > +    return 0; /* buf[] is not enough */
> > +  return p64->OptionalHeader.SizeOfStackReserve;
> > +}
> 
> Sorry, but this proposal is too complicated, IMHO.
> 
> Checking the stacksize in the file header for each single execve
> is quite a bit time consuming, isn't it?

I did this because the stack size of an executable can vary by
peflags -x command or -Wl,--stack=xxxxx option.

> The question is rather, why storing argv on the stack at all?  I guess
> the original idea was that argv is always a rather overseeable number.
> But it doesn't have to stay on the stack.
> 
> I tried this simple patch:
> 
> diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> index 49b7a44aeb15..961dea4ab993 100644
> --- a/winsup/cygwin/dcrt0.cc
> +++ b/winsup/cygwin/dcrt0.cc
> @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
>  	 a change to an element of argv[] it does not affect Cygwin's argv.
>  	 Changing the the contents of what argv[n] points to will still
>  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> -      char *newargv[__argc + 1];
> -      char **nav = newargv;
> -      char **oav = __argv;
> -      while ((*nav++ = *oav++) != NULL)
> -	continue;
> +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
>        /* Handle any signals which may have arrived */
>        sig_dispatch_pending (false);
>        _my_tls.call_signal_handler ();
> 
> and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> as desired.  Combined with a bit of error checking...
> 
> > diff --git a/winsup/cygwin/sysconf.cc b/winsup/cygwin/sysconf.cc
> > index 2db92e4de..6cb2aecd0 100644
> > --- a/winsup/cygwin/sysconf.cc
> > +++ b/winsup/cygwin/sysconf.cc
> > @@ -21,6 +21,13 @@ details. */
> >  #include "cpuid.h"
> >  #include "clock.h"
> >  
> > +#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
> > +static long
> > +get_arg_max (int in)
> > +{
> > +  return (long) (get_rlimit_stack () + DEFAULT_STACKGUARD) / 4;
> > +}
> > +
> >  static long
> >  get_page_size (int in)
> >  {
> > @@ -485,7 +492,7 @@ static struct
> >      };
> >  } sca[] =
> >  {
> > -  {cons, {c:ARG_MAX}},			/*   0, _SC_ARG_MAX */
> > +  {func, {f:get_arg_max}},		/*   0, _SC_ARG_MAX */
> >    {cons, {c:CHILD_MAX}},		/*   1, _SC_CHILD_MAX */
> >    {cons, {c:CLOCKS_PER_SEC}},		/*   2, _SC_CLK_TCK */
> >    {cons, {c:NGROUPS_MAX}},		/*   3, _SC_NGROUPS_MAX */
> > -- 
> > 2.39.0
> 
> Along these lines, there's no reason to couple SC_ARG_MAX to the
> size of the stack.  I'd propose to return the value 2097152.  It's
> the default value returned by getconf ARG_MAX on LInx as well.

The default value of 2097152 in Linux comes from 1/4 of the stack
size. It varies by ulimit -s command. So I tried to imitate it.

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 11:09   ` Corinna Vinschen
  2023-08-28 11:18     ` Corinna Vinschen
@ 2023-08-28 12:14     ` Takashi Yano
  1 sibling, 0 replies; 10+ messages in thread
From: Takashi Yano @ 2023-08-28 12:14 UTC (permalink / raw)
  To: cygwin-patches

On Mon, 28 Aug 2023 13:09:18 +0200
Corinna Vinschen wrote:
> On Aug 28 12:57, Corinna Vinschen wrote:
> > On Aug 28 18:46, Takashi Yano wrote:
> > > Previously, the number of command line args was not checked for
> > > cygwin process. Due to this, segmentation fault was caused if too
> > > many command line args are specified.
> > > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > > 
> > > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > > space.
> > > 
> > > With this patch, the total length of the arguments and the size of
> > > argv[] is restricted to 1/4 of total stack size for the process, and
> > > spawnve() returns E2BIG if the size exceeds the limit.
> > > [...]
> > I tried this simple patch:
> > 
> > diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> > index 49b7a44aeb15..961dea4ab993 100644
> > --- a/winsup/cygwin/dcrt0.cc
> > +++ b/winsup/cygwin/dcrt0.cc
> > @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
> >  	 a change to an element of argv[] it does not affect Cygwin's argv.
> >  	 Changing the the contents of what argv[n] points to will still
> >  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> > -      char *newargv[__argc + 1];
> > -      char **nav = newargv;
> > -      char **oav = __argv;
> > -      while ((*nav++ = *oav++) != NULL)
> > -	continue;
> > +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> > +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
> >        /* Handle any signals which may have arrived */
> >        sig_dispatch_pending (false);
> >        _my_tls.call_signal_handler ();
> > 
> > and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> > as desired.  Combined with a bit of error checking...
> 
> We may also get away with storing it in the Windows heap, but I didn't
> test this .
> 
> > Along these lines, there's no reason to couple SC_ARG_MAX to the
> > size of the stack.  I'd propose to return the value 2097152.  It's
> > the default value returned by getconf ARG_MAX on LInx as well.
> 
> Oh, and we can carefully check in child_info_spawn::worker that
> the args are not taking more space than the value returned by
> SC_ARG_MAX and return E2BIG if so.  We do this when checking the
> args for non-Cygwin apps anyway.

As for non-cygwin apps, the check seems to be done in linebuf::fromargv()
in winf.cc. Further, dcr0.cc code does not affect to non-cygwin app.
So my patch check only for cygwin apps case.

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 12:09   ` Takashi Yano
@ 2023-08-28 12:20     ` Takashi Yano
  0 siblings, 0 replies; 10+ messages in thread
From: Takashi Yano @ 2023-08-28 12:20 UTC (permalink / raw)
  To: cygwin-patches

On Mon, 28 Aug 2023 21:09:53 +0900
Takashi Yano wrote:
> On Mon, 28 Aug 2023 12:57:19 +0200
> Corinna Vinschen wrote:
> > On Aug 28 18:46, Takashi Yano wrote:
> > > Previously, the number of command line args was not checked for
> > > cygwin process. Due to this, segmentation fault was caused if too
> > > many command line args are specified.
> > > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > > 
> > > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > > space.
> > > 
> > > With this patch, the total length of the arguments and the size of
> > > argv[] is restricted to 1/4 of total stack size for the process, and
> > > spawnve() returns E2BIG if the size exceeds the limit.
> > > [...]
> > > +static size_t
> > > +get_stack_size (const WCHAR *filename)
> > > +{
> > > +  HANDLE h;
> > > +  h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ,
> > > +		   NULL, OPEN_EXISTING, 0, NULL);
> > > +  char buf[1024];
> > > +  DWORD n;
> > > +  ReadFile (h, buf, sizeof (buf), &n, 0);
> > > +  CloseHandle (h);
> > > +  IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4);
> > > +  if (!p)
> > > +    return 0;
> > > +  if ((char *) &p->OptionalHeader.SizeOfStackCommit > buf + n)
> > > +    return 0; /* buf[] is not enough */
> > > +  if (p->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
> > > +    return p->OptionalHeader.SizeOfStackReserve;
> > > +  IMAGE_NT_HEADERS64 *p64 = (IMAGE_NT_HEADERS64 *) p;
> > > +  if ((char *) &p64->OptionalHeader.SizeOfStackCommit > buf + n)
> > > +    return 0; /* buf[] is not enough */
> > > +  return p64->OptionalHeader.SizeOfStackReserve;
> > > +}
> > 
> > Sorry, but this proposal is too complicated, IMHO.
> > 
> > Checking the stacksize in the file header for each single execve
> > is quite a bit time consuming, isn't it?
> 
> I did this because the stack size of an executable can vary by
> peflags -x command or -Wl,--stack=xxxxx option.
> 
> > The question is rather, why storing argv on the stack at all?  I guess
> > the original idea was that argv is always a rather overseeable number.
> > But it doesn't have to stay on the stack.
> > 
> > I tried this simple patch:
> > 
> > diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> > index 49b7a44aeb15..961dea4ab993 100644
> > --- a/winsup/cygwin/dcrt0.cc
> > +++ b/winsup/cygwin/dcrt0.cc
> > @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
> >  	 a change to an element of argv[] it does not affect Cygwin's argv.
> >  	 Changing the the contents of what argv[n] points to will still
> >  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> > -      char *newargv[__argc + 1];
> > -      char **nav = newargv;
> > -      char **oav = __argv;
> > -      while ((*nav++ = *oav++) != NULL)
> > -	continue;
> > +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> > +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
> >        /* Handle any signals which may have arrived */
> >        sig_dispatch_pending (false);
> >        _my_tls.call_signal_handler ();
> > 
> > and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> > as desired.  Combined with a bit of error checking...
> > 
> > > diff --git a/winsup/cygwin/sysconf.cc b/winsup/cygwin/sysconf.cc
> > > index 2db92e4de..6cb2aecd0 100644
> > > --- a/winsup/cygwin/sysconf.cc
> > > +++ b/winsup/cygwin/sysconf.cc
> > > @@ -21,6 +21,13 @@ details. */
> > >  #include "cpuid.h"
> > >  #include "clock.h"
> > >  
> > > +#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
> > > +static long
> > > +get_arg_max (int in)
> > > +{
> > > +  return (long) (get_rlimit_stack () + DEFAULT_STACKGUARD) / 4;
> > > +}
> > > +
> > >  static long
> > >  get_page_size (int in)
> > >  {
> > > @@ -485,7 +492,7 @@ static struct
> > >      };
> > >  } sca[] =
> > >  {
> > > -  {cons, {c:ARG_MAX}},			/*   0, _SC_ARG_MAX */
> > > +  {func, {f:get_arg_max}},		/*   0, _SC_ARG_MAX */
> > >    {cons, {c:CHILD_MAX}},		/*   1, _SC_CHILD_MAX */
> > >    {cons, {c:CLOCKS_PER_SEC}},		/*   2, _SC_CLK_TCK */
> > >    {cons, {c:NGROUPS_MAX}},		/*   3, _SC_NGROUPS_MAX */
> > > -- 
> > > 2.39.0
> > 
> > Along these lines, there's no reason to couple SC_ARG_MAX to the
> > size of the stack.  I'd propose to return the value 2097152.  It's
> > the default value returned by getconf ARG_MAX on LInx as well.
> 
> The default value of 2097152 in Linux comes from 1/4 of the stack
> size. It varies by ulimit -s command. So I tried to imitate it.

The value 2097152 is too large for default stack size of cygwin.
It is whole of the stack size.

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 10:57 ` Corinna Vinschen
  2023-08-28 11:09   ` Corinna Vinschen
  2023-08-28 12:09   ` Takashi Yano
@ 2023-08-28 12:27   ` Takashi Yano
  2023-08-28 13:23     ` Takashi Yano
  2023-08-28 12:51   ` Takashi Yano
  3 siblings, 1 reply; 10+ messages in thread
From: Takashi Yano @ 2023-08-28 12:27 UTC (permalink / raw)
  To: cygwin-patches

On Mon, 28 Aug 2023 12:57:19 +0200
Corinna Vinschen wrote:
> On Aug 28 18:46, Takashi Yano wrote:
> > Previously, the number of command line args was not checked for
> > cygwin process. Due to this, segmentation fault was caused if too
> > many command line args are specified.
> > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > 
> > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > space.
> > 
> > With this patch, the total length of the arguments and the size of
> > argv[] is restricted to 1/4 of total stack size for the process, and
> > spawnve() returns E2BIG if the size exceeds the limit.
> > [...]
> > +static size_t
> > +get_stack_size (const WCHAR *filename)
> > +{
> > +  HANDLE h;
> > +  h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ,
> > +		   NULL, OPEN_EXISTING, 0, NULL);
> > +  char buf[1024];
> > +  DWORD n;
> > +  ReadFile (h, buf, sizeof (buf), &n, 0);
> > +  CloseHandle (h);
> > +  IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4);
> > +  if (!p)
> > +    return 0;
> > +  if ((char *) &p->OptionalHeader.SizeOfStackCommit > buf + n)
> > +    return 0; /* buf[] is not enough */
> > +  if (p->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
> > +    return p->OptionalHeader.SizeOfStackReserve;
> > +  IMAGE_NT_HEADERS64 *p64 = (IMAGE_NT_HEADERS64 *) p;
> > +  if ((char *) &p64->OptionalHeader.SizeOfStackCommit > buf + n)
> > +    return 0; /* buf[] is not enough */
> > +  return p64->OptionalHeader.SizeOfStackReserve;
> > +}
> 
> Sorry, but this proposal is too complicated, IMHO.
> 
> Checking the stacksize in the file header for each single execve
> is quite a bit time consuming, isn't it?
> 
> The question is rather, why storing argv on the stack at all?  I guess
> the original idea was that argv is always a rather overseeable number.
> But it doesn't have to stay on the stack.
> 
> I tried this simple patch:
> 
> diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> index 49b7a44aeb15..961dea4ab993 100644
> --- a/winsup/cygwin/dcrt0.cc
> +++ b/winsup/cygwin/dcrt0.cc
> @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
>  	 a change to an element of argv[] it does not affect Cygwin's argv.
>  	 Changing the the contents of what argv[n] points to will still
>  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> -      char *newargv[__argc + 1];
> -      char **nav = newargv;
> -      char **oav = __argv;
> -      while ((*nav++ = *oav++) != NULL)
> -	continue;
> +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
>        /* Handle any signals which may have arrived */
>        sig_dispatch_pending (false);
>        _my_tls.call_signal_handler ();
> 
> and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> as desired.  Combined with a bit of error checking...
> 
> > diff --git a/winsup/cygwin/sysconf.cc b/winsup/cygwin/sysconf.cc
> > index 2db92e4de..6cb2aecd0 100644
> > --- a/winsup/cygwin/sysconf.cc
> > +++ b/winsup/cygwin/sysconf.cc
> > @@ -21,6 +21,13 @@ details. */
> >  #include "cpuid.h"
> >  #include "clock.h"
> >  
> > +#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
> > +static long
> > +get_arg_max (int in)
> > +{
> > +  return (long) (get_rlimit_stack () + DEFAULT_STACKGUARD) / 4;
> > +}
> > +
> >  static long
> >  get_page_size (int in)
> >  {
> > @@ -485,7 +492,7 @@ static struct
> >      };
> >  } sca[] =
> >  {
> > -  {cons, {c:ARG_MAX}},			/*   0, _SC_ARG_MAX */
> > +  {func, {f:get_arg_max}},		/*   0, _SC_ARG_MAX */
> >    {cons, {c:CHILD_MAX}},		/*   1, _SC_CHILD_MAX */
> >    {cons, {c:CLOCKS_PER_SEC}},		/*   2, _SC_CLK_TCK */
> >    {cons, {c:NGROUPS_MAX}},		/*   3, _SC_NGROUPS_MAX */
> > -- 
> > 2.39.0
> 
> Along these lines, there's no reason to couple SC_ARG_MAX to the
> size of the stack.  I'd propose to return the value 2097152.  It's
> the default value returned by getconf ARG_MAX on LInx as well.

What happens if user allocate stack whose size is not enough for
2097152?

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 10:57 ` Corinna Vinschen
                     ` (2 preceding siblings ...)
  2023-08-28 12:27   ` Takashi Yano
@ 2023-08-28 12:51   ` Takashi Yano
  3 siblings, 0 replies; 10+ messages in thread
From: Takashi Yano @ 2023-08-28 12:51 UTC (permalink / raw)
  To: cygwin-patches

On Mon, 28 Aug 2023 12:57:19 +0200
Corinna Vinschen wrote:
> -      char *newargv[__argc + 1];
> -      char **nav = newargv;
> -      char **oav = __argv;
> -      while ((*nav++ = *oav++) != NULL)
> -	continue;
> +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
>        /* Handle any signals which may have arrived */
>        sig_dispatch_pending (false);
>        _my_tls.call_signal_handler ();

Shouldn't this be sizeof (char *), not sizeof (char **)?

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified.
  2023-08-28 12:27   ` Takashi Yano
@ 2023-08-28 13:23     ` Takashi Yano
  0 siblings, 0 replies; 10+ messages in thread
From: Takashi Yano @ 2023-08-28 13:23 UTC (permalink / raw)
  To: cygwin-patches

Hi Corinna,

On Mon, 28 Aug 2023 21:27:09 +0900
Takashi Yano wrote:
> On Mon, 28 Aug 2023 12:57:19 +0200
> Corinna Vinschen wrote:
> > On Aug 28 18:46, Takashi Yano wrote:
> > > Previously, the number of command line args was not checked for
> > > cygwin process. Due to this, segmentation fault was caused if too
> > > many command line args are specified.
> > > https://cygwin.com/pipermail/cygwin/2023-August/254333.html
> > > 
> > > Since char *argv[argc + 1] is placed on the stack in dll_crt0_1(),
> > > STATUS_STACK_OVERFLOW occurs if the stack does not have enough
> > > space.
> > > 
> > > With this patch, the total length of the arguments and the size of
> > > argv[] is restricted to 1/4 of total stack size for the process, and
> > > spawnve() returns E2BIG if the size exceeds the limit.
> > > [...]
> > > +static size_t
> > > +get_stack_size (const WCHAR *filename)
> > > +{
> > > +  HANDLE h;
> > > +  h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ,
> > > +		   NULL, OPEN_EXISTING, 0, NULL);
> > > +  char buf[1024];
> > > +  DWORD n;
> > > +  ReadFile (h, buf, sizeof (buf), &n, 0);
> > > +  CloseHandle (h);
> > > +  IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4);
> > > +  if (!p)
> > > +    return 0;
> > > +  if ((char *) &p->OptionalHeader.SizeOfStackCommit > buf + n)
> > > +    return 0; /* buf[] is not enough */
> > > +  if (p->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
> > > +    return p->OptionalHeader.SizeOfStackReserve;
> > > +  IMAGE_NT_HEADERS64 *p64 = (IMAGE_NT_HEADERS64 *) p;
> > > +  if ((char *) &p64->OptionalHeader.SizeOfStackCommit > buf + n)
> > > +    return 0; /* buf[] is not enough */
> > > +  return p64->OptionalHeader.SizeOfStackReserve;
> > > +}
> > 
> > Sorry, but this proposal is too complicated, IMHO.
> > 
> > Checking the stacksize in the file header for each single execve
> > is quite a bit time consuming, isn't it?
> > 
> > The question is rather, why storing argv on the stack at all?  I guess
> > the original idea was that argv is always a rather overseeable number.
> > But it doesn't have to stay on the stack.
> > 
> > I tried this simple patch:
> > 
> > diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
> > index 49b7a44aeb15..961dea4ab993 100644
> > --- a/winsup/cygwin/dcrt0.cc
> > +++ b/winsup/cygwin/dcrt0.cc
> > @@ -978,11 +978,8 @@ dll_crt0_1 (void *)
> >  	 a change to an element of argv[] it does not affect Cygwin's argv.
> >  	 Changing the the contents of what argv[n] points to will still
> >  	 affect Cygwin.  This is similar (but not exactly like) Linux. */
> > -      char *newargv[__argc + 1];
> > -      char **nav = newargv;
> > -      char **oav = __argv;
> > -      while ((*nav++ = *oav++) != NULL)
> > -	continue;
> > +      char **newargv = (char **) malloc ((__argc + 1) * sizeof (char **));
> > +      memcpy (newargv, __argv, (__argc + 1) * sizeof (char **));
> >        /* Handle any signals which may have arrived */
> >        sig_dispatch_pending (false);
> >        _my_tls.call_signal_handler ();
> > 
> > and the testcase `LC_ALL=C sed 's/x/y/' $(seq 1000000)' simply ran
> > as desired.  Combined with a bit of error checking...
> > 
> > > diff --git a/winsup/cygwin/sysconf.cc b/winsup/cygwin/sysconf.cc
> > > index 2db92e4de..6cb2aecd0 100644
> > > --- a/winsup/cygwin/sysconf.cc
> > > +++ b/winsup/cygwin/sysconf.cc
> > > @@ -21,6 +21,13 @@ details. */
> > >  #include "cpuid.h"
> > >  #include "clock.h"
> > >  
> > > +#define DEFAULT_STACKGUARD (wincap.def_guard_page_size() + wincap.page_size ())
> > > +static long
> > > +get_arg_max (int in)
> > > +{
> > > +  return (long) (get_rlimit_stack () + DEFAULT_STACKGUARD) / 4;
> > > +}
> > > +
> > >  static long
> > >  get_page_size (int in)
> > >  {
> > > @@ -485,7 +492,7 @@ static struct
> > >      };
> > >  } sca[] =
> > >  {
> > > -  {cons, {c:ARG_MAX}},			/*   0, _SC_ARG_MAX */
> > > +  {func, {f:get_arg_max}},		/*   0, _SC_ARG_MAX */
> > >    {cons, {c:CHILD_MAX}},		/*   1, _SC_CHILD_MAX */
> > >    {cons, {c:CLOCKS_PER_SEC}},		/*   2, _SC_CLK_TCK */
> > >    {cons, {c:NGROUPS_MAX}},		/*   3, _SC_NGROUPS_MAX */
> > > -- 
> > > 2.39.0
> > 
> > Along these lines, there's no reason to couple SC_ARG_MAX to the
> > size of the stack.  I'd propose to return the value 2097152.  It's
> > the default value returned by getconf ARG_MAX on LInx as well.
> 
> What happens if user allocate stack whose size is not enough for
> 2097152?

I think I got your point. If char *newargv[__argc+1] is placed
in heap, we don't care of stack size for the argument length.
Right?

I'll revise the patch accordingly. Please look v3 patch.

-- 
Takashi Yano <takashi.yano@nifty.ne.jp>

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2023-08-28 13:23 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-28  9:46 [PATCH v2] Cygwin: spawn: Fix segfalt when too many command line args are specified Takashi Yano
2023-08-28 10:57 ` Corinna Vinschen
2023-08-28 11:09   ` Corinna Vinschen
2023-08-28 11:18     ` Corinna Vinschen
2023-08-28 12:14     ` Takashi Yano
2023-08-28 12:09   ` Takashi Yano
2023-08-28 12:20     ` Takashi Yano
2023-08-28 12:27   ` Takashi Yano
2023-08-28 13:23     ` Takashi Yano
2023-08-28 12:51   ` Takashi Yano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).