]> git.ipfire.org Git - thirdparty/make.git/commitdiff
Disable the jobserver in non-recursive children
authorPaul Smith <psmith@gnu.org>
Sun, 24 Jul 2022 18:14:32 +0000 (14:14 -0400)
committerPaul Smith <psmith@gnu.org>
Sat, 30 Jul 2022 22:40:28 +0000 (18:40 -0400)
Savannah issues such as SV 57242 and SV 62397 show how passing
references to closed file descriptors via the --jobserver-auth option
in MAKEFLAGS can lead to problematic outcomes.

When computing the child environment for a non-recursive shell, add
an extra option to MAKEFLAGS to disable the file descriptors for the
jobserver.

Unfortunately this doesn't modify the value of the make variable
MAKEFLAGS, it only modifies the value of the sub-shell environment
variable MAKEFLAGS.  This can lead to confusion if the user is not
considering the distinction.

* src/makeint.h: Publish the jobserver-auth value.  Add a global
definition of the name of the command line option.
* src/os.h (jobserver_get_invalid_auth): New function to return a
string invalidating the jobserver-auth option.
* src/w32/w32os.c (jobserver_get_invaid_auth): Implement it.  On
Windows we use a semaphore so there's no need to invalidate.
* src/posixos.c (jobserver_parse_auth): If we parse the invalid
auth string, don't set up the jobserver.
(jobserver_get_invalid_auth): Return an invalid option.
* src/variable.h (target_environment): Specify if the target
environment is for a recursive shell or non-recursive shell.
* src/variable.c (target_environment): Move checking for MAKELEVEL
into the loop rather than doing it at the end.
Along with this, check for MAKEFLAGS and MFLAGS, and update them
based on whether we're invoking a recursive or non-recursive child,
and also on whether it's necessary to invalidate the jobserver.
* src/function.c (func_shell_base): Shell functions can never be
recursive to pass 0 to target_environment().
* src/job.c (start_job_command): Specify whether the child is
recursive when calling target_environment().
* src/main.c: Export jobserver_auth.  sync_mutex doesn't need to
be exported.  Use the global definition for the option name.
* tests/scripts/variables/MAKEFLAGS: Add tests for $MAKEFLAGS.

NEWS
src/function.c
src/job.c
src/main.c
src/makeint.h
src/os.h
src/posixos.c
src/variable.c
src/variable.h
src/w32/w32os.c
tests/scripts/variables/MAKEFLAGS

diff --git a/NEWS b/NEWS
index 8c1e271b9f8f940d3cab85d3f5388ede26b2814a..752de7d0ee2061526f55634924f3b7554f0df8ad 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -107,6 +107,13 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=109&se
 * Special targets like .POSIX are detected upon definition, ensuring that any
   change in behavior takes effect immediately, before the next line is parsed.
 
+* When the jobserver is enabled and GNU make decides it is invoking a non-make
+  sub-process and closes the jobserver pipes, it will now add a new option to
+  the MAKEFLAGS environment variable that disables the jobserver.
+  This prevents sub-processes that invoke make from accidentally using other
+  open file descriptors as jobserver pipes.  For more information see
+  https://savannah.gnu.org/bugs/?57242 and https://savannah.gnu.org/bugs/?62397
+
 * A long-standing issue with the directory cache has been resolved: changes
   made as a side-effect of some other target's recipe are now noticed as
   expected.
index 1f490fa267d647ae44c5e61d20ae4f96b42b82c6..a2fd90a7fca5f05a2369e6c972f916255e89942c 100644 (file)
@@ -1876,7 +1876,7 @@ func_shell_base (char *o, char **argv, int trim_newlines)
   errfd = (output_context && output_context->err >= 0
            ? output_context->err : FD_STDERR);
 
-  child.environment = target_environment (NULL);
+  child.environment = target_environment (NULL, 0);
 
 #if defined(__MSDOS__)
   fpipe = msdos_openpipe (pipedes, &pid, argv[0]);
index 8837b744a6b9612e441971a1190636a60f52c1df..c1e53de7cebc6ba26f345c52c6ccdd3b660c9c72 100644 (file)
--- a/src/job.c
+++ b/src/job.c
@@ -1430,7 +1430,7 @@ start_job_command (struct child *child)
 #ifndef _AMIGA
   /* Set up the environment for the child.  */
   if (child->environment == 0)
-    child->environment = target_environment (child->file);
+    child->environment = target_environment (child->file, child->recursive);
 #endif
 
 #if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32)
index 656eaec688c33c13df7744f53417990c0dd85440..e3658f052c67a01f75dfe969ae07db06a471ccbd 100644 (file)
@@ -232,7 +232,7 @@ static const int inf_jobs = 0;
 
 /* Authorization for the jobserver.  */
 
-static char *jobserver_auth = NULL;
+char *jobserver_auth = NULL;
 
 /* Shuffle mode for goals and prerequisites.  */
 
@@ -241,7 +241,7 @@ static char *shuffle_mode = NULL;
 /* Handle for the mutex used on Windows to synchronize output of our
    children under -O.  */
 
-char *sync_mutex = NULL;
+static char *sync_mutex = NULL;
 
 /* Maximum load average at which multiple jobs will be run.
    Negative values mean unlimited, while zero means limit to
@@ -475,7 +475,7 @@ static const struct command_switch switches[] =
 
     /* These are long-style options.  */
     { CHAR_MAX+1, strlist, &db_flags, 1, 1, 0, "basic", 0, "debug" },
-    { CHAR_MAX+2, string, &jobserver_auth, 1, 1, 0, 0, 0, "jobserver-auth" },
+    { CHAR_MAX+2, string, &jobserver_auth, 1, 1, 0, 0, 0, JOBSERVER_AUTH_OPT },
     { CHAR_MAX+3, flag, &trace_flag, 1, 1, 0, 0, 0, "trace" },
     { CHAR_MAX+4, flag_off, &print_directory_flag, 1, 1, 0, 0,
       &default_print_directory_flag, "no-print-directory" },
index e355051dbf90f1a1e85da977a0c1daefeb5b8440..e711a3c883c34835f90aceff7da16923fb43aca9 100644 (file)
@@ -710,6 +710,9 @@ extern int batch_mode_shell;
 #define RECIPEPREFIX_DEFAULT    '\t'
 extern char cmd_prefix;
 
+#define JOBSERVER_AUTH_OPT      "jobserver-auth"
+
+extern char *jobserver_auth;
 extern unsigned int job_slots;
 extern double max_load_average;
 
index ae73da8003f05263cb460d17038d9f9b6b592409..19f61c1920eeba19fd8b18c0a770fdee4e4f5b83 100644 (file)
--- a/src/os.h
+++ b/src/os.h
@@ -25,11 +25,16 @@ unsigned int jobserver_enabled (void);
 /* Called in the master instance to set up the jobserver initially.  */
 unsigned int jobserver_setup (int job_slots);
 
-/* Called in a child instance to connect to the jobserver.  */
+/* Called in a child instance to connect to the jobserver.
+   Return 1 if we got a valid auth, else 0.  */
 unsigned int jobserver_parse_auth (const char* auth);
 
 /* Returns an allocated buffer used to pass to child instances.  */
-char *jobserver_get_auth (void);
+char *jobserver_get_auth ();
+
+/* Returns a pointer to a static string used to indicate that the child
+   cannot access the jobserver, or NULL if it always can.  */
+const char *jobserver_get_invalid_auth ();
 
 /* Clear this instance's jobserver configuration.  */
 void jobserver_clear (void);
index 9eecfcde287f60e8c7e7f8a2c78e0afef874c207..de19c8897fcf6124ff0d91ce13b9239a1bd15fb3 100644 (file)
@@ -121,6 +121,9 @@ jobserver_parse_auth (const char *auth)
   DB (DB_JOBS,
       (_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1]));
 
+  if (job_fds[0] == -2 || job_fds[1] == -2)
+    return 0;
+
 #ifdef HAVE_FCNTL_H
 # define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
 #else
@@ -154,13 +157,21 @@ jobserver_parse_auth (const char *auth)
 }
 
 char *
-jobserver_get_auth (void)
+jobserver_get_auth ()
 {
   char *auth = xmalloc ((INTSTR_LENGTH * 2) + 2);
   sprintf (auth, "%d,%d", job_fds[0], job_fds[1]);
   return auth;
 }
 
+const char *
+jobserver_get_invalid_auth ()
+{
+  /* It's not really great that we are assuming the command line option
+     here but other alternatives are also gross.  */
+  return " --" JOBSERVER_AUTH_OPT "=-2,-2";
+}
+
 unsigned int
 jobserver_enabled (void)
 {
index c51f41dcb2ebfc8459833e2b1006a65b65366daf..30f4424af668ce112816e97e739a984c5dff2203 100644 (file)
@@ -24,6 +24,7 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "job.h"
 #include "commands.h"
 #include "variable.h"
+#include "os.h"
 #include "rule.h"
 #ifdef WINDOWS32
 #include "pathstuff.h"
@@ -379,7 +380,6 @@ lookup_special_var (struct variable *var)
 {
   static unsigned long last_changenum = 0;
 
-
   /* This one actually turns out to be very hard, due to the way the parser
      records targets.  The way it works is that target information is collected
      internally until make knows the target is completely specified.  Only when
@@ -1018,21 +1018,30 @@ should_export (const struct variable *v)
 
 /* Create a new environment for FILE's commands.
    If FILE is nil, this is for the 'shell' function.
-   The child's MAKELEVEL variable is incremented.  */
+   The child's MAKELEVEL variable is incremented.
+   If recursive is true then we're running a recursive make, else not.  */
 
 char **
-target_environment (struct file *file)
+target_environment (struct file *file, int recursive)
 {
   struct variable_set_list *set_list;
   struct variable_set_list *s;
   struct hash_table table;
   struct variable **v_slot;
   struct variable **v_end;
-  struct variable makelevel_key;
   char **result_0;
   char **result;
+  const char *invalid = NULL;
   /* If we got no value from the environment then never add the default.  */
   int added_SHELL = shell_var.value == 0;
+  int found_makelevel = 0;
+  int found_mflags = 0;
+  int found_makeflags = 0;
+
+  /* We need to update makeflags if (a) we're not recurive, (b) jobserver_auth
+     is enabled, and (c) we need to add invalidation.  */
+  if (!recursive && jobserver_auth)
+    invalid = jobserver_get_invalid_auth ();
 
   if (file == 0)
     set_list = current_variable_set_list;
@@ -1067,17 +1076,11 @@ target_environment (struct file *file)
                   hash_insert_at (&table, v, evslot);
               }
             else if ((*evslot)->export == v_default)
-              {
-                /* We already have a variable but we don't know its status.  */
-                (*evslot)->export = v->export;
-              }
+              /* We already have a variable but we don't know its status.  */
+              (*evslot)->export = v->export;
           }
     }
 
-  makelevel_key.name = (char *)MAKELEVEL_NAME;
-  makelevel_key.length = MAKELEVEL_LENGTH;
-  hash_delete (&table, &makelevel_key);
-
   result = result_0 = xmalloc ((table.ht_fill + 3) * sizeof (char *));
 
   v_slot = (struct variable **) table.ht_vec;
@@ -1086,16 +1089,14 @@ target_environment (struct file *file)
     if (! HASH_VACANT (*v_slot))
       {
         struct variable *v = *v_slot;
+        char *value = v->value;
+        char *cp = NULL;
 
         /* This might be here because it was a target-specific variable that
            we didn't know the status of when we added it.  */
         if (! should_export (v))
           continue;
 
-        /* If this is the SHELL variable remember we already added it.  */
-        if (!added_SHELL && streq (v->name, "SHELL"))
-          added_SHELL = 1;
-
         /* If V is recursively expanded and didn't come from the environment,
            expand its value.  If it came from the environment, it should
            go back into the environment unchanged.  */
@@ -1104,37 +1105,109 @@ target_environment (struct file *file)
             /* If V is being recursively expanded and this is for a shell
                function, just skip it.  */
             if (v->expanding && file == NULL)
-              DB (DB_VERBOSE, (_("%s:%lu: Skipping export of %s to shell function due to recursive expansion"),
-                               v->fileinfo.filenm, v->fileinfo.lineno, v->name));
-            else
               {
-                char *value = recursively_expand_for_file (v, file);
-#ifdef WINDOWS32
-                if (strcmp (v->name, "Path") == 0 ||
-                    strcmp (v->name, "PATH") == 0)
-                  convert_Path_to_windows32 (value, ';');
-#endif
-                *result++ = xstrdup (concat (3, v->name, "=", value));
-                free (value);
+                DB (DB_VERBOSE, (_("%s:%lu: Skipping export of %s to shell function due to recursive expansion"),
+                                 v->fileinfo.filenm, v->fileinfo.lineno, v->name));
+                continue;
               }
+
+            value = cp = recursively_expand_for_file (v, file);
           }
-        else
+
+        /* If this is the SHELL variable remember we already added it.  */
+        if (!added_SHELL && streq (v->name, "SHELL"))
+          {
+            added_SHELL = 1;
+            goto setit;
+          }
+
+        /* If this is MAKELEVEL, update it.  */
+        if (!found_makelevel && streq (v->name, MAKELEVEL_NAME))
+          {
+            char val[INTSTR_LENGTH + 1];
+            sprintf (val, "%u", makelevel + 1);
+            free (cp);
+            value = cp = xstrdup (val);
+            found_makelevel = 1;
+            goto setit;
+          }
+
+        /* If we need to reset jobserver, check for MAKEFLAGS / MFLAGS.  */
+        if (invalid)
           {
+            if (!found_makeflags && streq (v->name, MAKEFLAGS_NAME))
+              {
+                char *mf;
+                char *vars;
+                found_makeflags = 1;
+
+                if (!strstr (value, " --" JOBSERVER_AUTH_OPT "="))
+                  goto setit;
+
+                /* The invalid option must come before variable overrides.  */
+                vars = strstr (value, " -- ");
+                if (!vars)
+                  mf = xstrdup (concat (2, value, invalid));
+                else
+                  {
+                    size_t lf = vars - value;
+                    size_t li = strlen (invalid);
+                    mf = xmalloc (strlen (value) + li + 1);
+                    strcpy (mempcpy (mempcpy (mf, value, lf), invalid, li),
+                            vars);
+                  }
+                free (cp);
+                value = cp = mf;
+                if (found_mflags)
+                  invalid = NULL;
+                goto setit;
+              }
+
+            if (!found_mflags && streq (v->name, "MFLAGS"))
+              {
+                const char *mf;
+                found_mflags = 1;
+
+                if (!strstr (value, " --" JOBSERVER_AUTH_OPT "="))
+                  goto setit;
+
+                if (v->origin != o_env)
+                  goto setit;
+                mf = concat (2, value, invalid);
+                free (cp);
+                value = cp = xstrdup (mf);
+                if (found_makeflags)
+                  invalid = NULL;
+                goto setit;
+              }
+          }
+
 #ifdef WINDOWS32
-            if (strcmp (v->name, "Path") == 0 ||
-                strcmp (v->name, "PATH") == 0)
-              convert_Path_to_windows32 (v->value, ';');
-#endif
-            *result++ = xstrdup (concat (3, v->name, "=", v->value));
+        if (streq (v->name, "Path") || streq (v->name, "PATH"))
+          {
+            if (!cp)
+              cp = xstrdup (value);
+            value = convert_Path_to_windows32 (cp, ';');
+            goto setit;
           }
+#endif
+
+      setit:
+        *result++ = xstrdup (concat (3, v->name, "=", value));
+        free (cp);
       }
 
   if (!added_SHELL)
     *result++ = xstrdup (concat (3, shell_var.name, "=", shell_var.value));
 
-  *result = xmalloc (100);
-  sprintf (*result, "%s=%u", MAKELEVEL_NAME, makelevel + 1);
-  *++result = 0;
+  if (!found_makelevel)
+    {
+      char val[MAKELEVEL_LENGTH + 1 + INTSTR_LENGTH + 1];
+      sprintf (val, "%s=%u", MAKELEVEL_NAME, makelevel + 1);
+      *result++ = xstrdup (val);
+    }
+
+  *result = NULL;
 
   hash_free (&table, 0);
 
index 22e2d68708a3499a6e7160d88625eef1368ace1b..a28749c60747d76ac2fc4ebcb1320abe6de0ba43 100644 (file)
@@ -16,6 +16,8 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "hash.h"
 
+struct file;
+
 /* Codes in a variable definition saying where the definition came from.
    Increasing numeric values signify less-overridable definitions.  */
 enum variable_origin
@@ -235,7 +237,7 @@ void undefine_variable_in_set (const char *name, size_t length,
                                        (int)(l), (n));                  \
                               }while(0)
 
-char **target_environment (struct file *file);
+char **target_environment (struct file *file, int recursive);
 
 struct pattern_var *create_pattern_var (const char *target,
                                         const char *suffix);
index 54265c8b4e655f4509d4cf97de67542fa42a26ef..56d6972aacdd900fb175e995b10f2b888b08ed06 100644 (file)
@@ -90,6 +90,13 @@ jobserver_get_auth ()
   return xstrdup (jobserver_semaphore_name);
 }
 
+const char *
+jobserver_get_invalid_auth ()
+{
+  /* Because we're using a semaphore we don't need to invalidate.  */
+  return NULL;
+}
+
 unsigned int
 jobserver_enabled ()
 {
index d5fa0d2863ea3e9145ce525fd343e2fe60b213d2..a41f1cf0db2784a3975cb74976544e034c653167 100644 (file)
@@ -6,17 +6,17 @@ $details = "DETAILS";
 
 # Normal flags aren't prefixed with "-"
 run_make_test(q!
-all: ; @echo $(MAKEFLAGS)
+all: ; @echo /$(MAKEFLAGS)/
 !,
-              '-e -r -R', 'erR');
+              '-e -r -R', '/erR/');
 
 # Long arguments mean everything is prefixed with "-"
 run_make_test(q!
-all: ; @echo $(MAKEFLAGS)
+all: ; @echo /$(MAKEFLAGS)/
 !,
               '--no-print-directory -e -r -R --trace', "#MAKEFILE#:2: update target 'all' due to: target does not exist
-echo erR --trace --no-print-directory
-erR --trace --no-print-directory");
+echo /erR --trace --no-print-directory/
+/erR --trace --no-print-directory/");
 
 
 # Recursive invocations of make should accumulate MAKEFLAGS values.
@@ -139,4 +139,20 @@ all:; $(info makeflags='$(XX)')
     '-Onone -l2.5 -l2.5 -Onone -I/tmp -iknqrs -i -n -s -k -I/tmp',
     "makeflags='iknqrsw -I/tmp -I/tmp -Onone -Onone -l2.5 -l2.5 --no-print-directory'");
 
+# Verify that command line arguments are included in MAKEFLAGS
+run_make_test(q!
+all: ; @echo $(MAKEFLAGS)
+!,
+              '-e FOO=bar -r -R', 'erR -- FOO=bar');
+
+# Long arguments mean everything is prefixed with "-"
+run_make_test(q!
+all: ; @echo /$(MAKEFLAGS)/
+!,
+              '--no-print-directory -e -r -R --trace FOO=bar',
+              "#MAKEFILE#:2: update target 'all' due to: target does not exist
+echo /erR --trace --no-print-directory -- FOO=bar/
+/erR --trace --no-print-directory -- FOO=bar/");
+
+
 1;