]> git.ipfire.org Git - thirdparty/make.git/commitdiff
[SV 66037] Avoid hang/crash from MAKEFLAGS=... on command line
authorDmitry Goncharov <dgoncharov@users.sf.net>
Sun, 4 Aug 2024 19:13:44 +0000 (15:13 -0400)
committerPaul Smith <psmith@gnu.org>
Sun, 4 Aug 2024 20:00:48 +0000 (16:00 -0400)
Make enters an infinite loop when some option and MAKEFLAGS=<value>
are specified on the command line.  For example,
    $ make -r MAKEFLAGS=hello=world

If decode_switches() runs handle_non_switch_argument() from within
the getopt() loop, it would recursively call decode_switches() to
enter a new getopt() loop, corrupting the state of the outer loop.

* src/main.c (decode_switches): Save up non-option arguments and run
handle_non_switch_argument() only after we're done with getopt().
* tests/scripts/variables/MAKEFLAGS: Add tests.

src/main.c
tests/scripts/variables/MAKEFLAGS

index bf3829a0fc74457498f6f81a9713bcc34c26e817..0367f08b8de9a99c1cb0f52d62c0e95485f715a7 100644 (file)
@@ -3108,8 +3108,20 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
   int bad = 0;
   struct command_switch *cs;
   struct stringlist *sl;
+  struct stringlist targets;
   int c;
   unsigned int found_wait = 0;
+  const char **a;
+
+  /* This is for safety/double-checking.  */
+  static int using_getopt = 0;
+  assert (using_getopt == 0);
+  using_getopt = 1;
+
+  /* Get enough space for all the arguments, just in case.  */
+  targets.max = argc + 1;
+  targets.list = alloca (targets.max * sizeof (const char **));
+  targets.idx = 0;
 
   /* getopt does most of the parsing for us.
      First, get its vectors set up.  */
@@ -3138,14 +3150,9 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
            see all he did wrong.  */
         bad = 1;
       else if (c == 1)
-        {
-          /* An argument not starting with a dash.  */
-          const unsigned int prior_found_wait = found_wait;
-          found_wait = handle_non_switch_argument (coptarg, origin);
-          if (prior_found_wait && lastgoal)
-            /* If the argument before this was .WAIT, wait here.  */
-            lastgoal->wait_here = 1;
-        }
+        /* An argument not starting with a dash.  Defer handling until later,
+           since handle_non_switch_argument() might corrupt getopt().  */
+        targets.list[targets.idx++] = coptarg;
       else
         /* An option starting with a dash.  */
         for (cs = switches; cs->c != '\0'; ++cs)
@@ -3339,9 +3346,19 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
      to be returned in order, this only happens when there is a "--"
      argument to prevent later arguments from being options.  */
   while (optind < argc)
+    targets.list[targets.idx++] = argv[optind++];
+  targets.list[targets.idx] = NULL;
+
+  /* Cannot call getopt below this line.  */
+  using_getopt = 0;
+
+  /* handle_non_switch_argument() can only be called after getopt is done;
+     if one of the arguments is MAKEFLAGS=<value> then it will recurse here
+     and call getopt() again, corrupting the state if the outer method.  */
+  for (a = targets.list; *a; ++a)
     {
       const int prior_found_wait = found_wait;
-      found_wait = handle_non_switch_argument (argv[optind++], origin);
+      found_wait = handle_non_switch_argument (*a, origin);
       if (prior_found_wait && lastgoal)
         lastgoal->wait_here = 1;
     }
index aab4a0e10797735685f4bc00e34887095d072a03..36b145fca2b1b167c8164a1c806cd4c89ac0d5e4 100644 (file)
@@ -902,5 +902,28 @@ unlink('hello');
 
 rmdir('localtmp');
 
+# sv 66037. An infinite loop when MAKEFLAGS is specified on the command line.
+my @cli= ('-r MAKEFLAGS=-k hello=world',
+'-r MAKEFLAGS=-k hello=world MAKEFLAGS=-R',
+'-r MAKEFLAGS="-R -- hello=world MAKEFLAGS=-k"');
+for my $c (@cli) {
+run_make_test(q!
+$(info hello=$(hello))
+all:;
+!, $c, "hello=world\n#MAKE#: 'all' is up to date.\n");
+}
+
+run_make_test(q!
+$(info hello=$(hello))
+all:;
+!, '-r MAKEFLAGS="-R -- hello=world MAKEFLAGS=hello=bye"',
+"hello=bye\n#MAKE#: 'all' is up to date.\n");
+
+run_make_test(q!
+$(info hello=$(hello))
+all:;
+!, '-r MAKEFLAGS="-R -- hello=world MAKEFLAGS=-s"',
+"hello=world\n");
+
 # This tells the test driver that the perl test script executed properly.
 1;