b1dca6
commit 4c6e0415ef206a595c62d5d37e3b9a821782c533
b1dca6
Author: Florian Weimer <fweimer@redhat.com>
b1dca6
Date:   Fri Apr 3 13:17:48 2020 +0200
b1dca6
b1dca6
    elf: Simplify handling of lists of audit strings
b1dca6
    
b1dca6
    All list elements are colon-separated strings, and there is a hard
b1dca6
    upper limit for the number of audit modules, so it is possible to
b1dca6
    pre-allocate a fixed-size array of strings to which the LD_AUDIT
b1dca6
    environment variable and --audit arguments are added.
b1dca6
    
b1dca6
    Also eliminate the global variables for the audit list because
b1dca6
    the list is only needed briefly during startup.
b1dca6
    
b1dca6
    There is a slight behavior change: All duplicate LD_AUDIT environment
b1dca6
    variables are now processed, not just the last one as before.  However,
b1dca6
    such environment vectors are invalid anyway.
b1dca6
    
b1dca6
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
b1dca6
b1dca6
diff --git a/elf/rtld.c b/elf/rtld.c
b1dca6
index f755dc30331f799f..c39cb8f2cd4bb1cc 100644
b1dca6
--- a/elf/rtld.c
b1dca6
+++ b/elf/rtld.c
b1dca6
@@ -43,6 +43,7 @@
b1dca6
 #include <stap-probe.h>
b1dca6
 #include <stackinfo.h>
b1dca6
 #include <not-cancel.h>
b1dca6
+#include <array_length.h>
b1dca6
 
b1dca6
 #include <assert.h>
b1dca6
 
b1dca6
@@ -107,8 +108,53 @@ static void print_missing_version (int errcode, const char *objname,
b1dca6
 /* Print the various times we collected.  */
b1dca6
 static void print_statistics (const hp_timing_t *total_timep);
b1dca6
 
b1dca6
-/* Add audit objects.  */
b1dca6
-static void process_dl_audit (char *str);
b1dca6
+/* Length limits for names and paths, to protect the dynamic linker,
b1dca6
+   particularly when __libc_enable_secure is active.  */
b1dca6
+#ifdef NAME_MAX
b1dca6
+# define SECURE_NAME_LIMIT NAME_MAX
b1dca6
+#else
b1dca6
+# define SECURE_NAME_LIMIT 255
b1dca6
+#endif
b1dca6
+#ifdef PATH_MAX
b1dca6
+# define SECURE_PATH_LIMIT PATH_MAX
b1dca6
+#else
b1dca6
+# define SECURE_PATH_LIMIT 1024
b1dca6
+#endif
b1dca6
+
b1dca6
+/* Strings containing colon-separated lists of audit modules.  */
b1dca6
+struct audit_list
b1dca6
+{
b1dca6
+  /* Array of strings containing colon-separated path lists.  Each
b1dca6
+     audit module needs its own namespace, so pre-allocate the largest
b1dca6
+     possible list.  */
b1dca6
+  const char *audit_strings[DL_NNS];
b1dca6
+
b1dca6
+  /* Number of entries added to audit_strings.  */
b1dca6
+  size_t length;
b1dca6
+
b1dca6
+  /* Index into the audit_strings array (for the iteration phase).  */
b1dca6
+  size_t current_index;
b1dca6
+
b1dca6
+  /* Tail of audit_strings[current_index] which still needs
b1dca6
+     processing.  */
b1dca6
+  const char *current_tail;
b1dca6
+
b1dca6
+  /* Scratch buffer for returning a name which is part of the strings
b1dca6
+     in audit_strings.  */
b1dca6
+  char fname[SECURE_NAME_LIMIT];
b1dca6
+};
b1dca6
+
b1dca6
+/* Creates an empty audit list.  */
b1dca6
+static void audit_list_init (struct audit_list *);
b1dca6
+
b1dca6
+/* Add a string to the end of the audit list, for later parsing.  Must
b1dca6
+   not be called after audit_list_next.  */
b1dca6
+static void audit_list_add_string (struct audit_list *, const char *);
b1dca6
+
b1dca6
+/* Extract the next audit module from the audit list.  Only modules
b1dca6
+   for which dso_name_valid_for_suid is true are returned.  Must be
b1dca6
+   called after all the audit_list_add_string calls.  */
b1dca6
+static const char *audit_list_next (struct audit_list *);
b1dca6
 
b1dca6
 /* This is a list of all the modes the dynamic loader can be in.  */
b1dca6
 enum mode { normal, list, verify, trace };
b1dca6
@@ -116,7 +162,7 @@ enum mode { normal, list, verify, trace };
b1dca6
 /* Process all environments variables the dynamic linker must recognize.
b1dca6
    Since all of them start with `LD_' we are a bit smarter while finding
b1dca6
    all the entries.  */
b1dca6
-static void process_envvars (enum mode *modep);
b1dca6
+static void process_envvars (enum mode *modep, struct audit_list *);
b1dca6
 
b1dca6
 #ifdef DL_ARGV_NOT_RELRO
b1dca6
 int _dl_argc attribute_hidden;
b1dca6
@@ -144,19 +190,6 @@ uintptr_t __pointer_chk_guard_local
b1dca6
 strong_alias (__pointer_chk_guard_local, __pointer_chk_guard)
b1dca6
 #endif
b1dca6
 
b1dca6
-/* Length limits for names and paths, to protect the dynamic linker,
b1dca6
-   particularly when __libc_enable_secure is active.  */
b1dca6
-#ifdef NAME_MAX
b1dca6
-# define SECURE_NAME_LIMIT NAME_MAX
b1dca6
-#else
b1dca6
-# define SECURE_NAME_LIMIT 255
b1dca6
-#endif
b1dca6
-#ifdef PATH_MAX
b1dca6
-# define SECURE_PATH_LIMIT PATH_MAX
b1dca6
-#else
b1dca6
-# define SECURE_PATH_LIMIT 1024
b1dca6
-#endif
b1dca6
-
b1dca6
 /* Check that AT_SECURE=0, or that the passed name does not contain
b1dca6
    directories and is not overly long.  Reject empty names
b1dca6
    unconditionally.  */
b1dca6
@@ -174,89 +207,75 @@ dso_name_valid_for_suid (const char *p)
b1dca6
   return *p != '\0';
b1dca6
 }
b1dca6
 
b1dca6
-/* LD_AUDIT variable contents.  Must be processed before the
b1dca6
-   audit_list below.  */
b1dca6
-const char *audit_list_string;
b1dca6
-
b1dca6
-/* Cyclic list of auditing DSOs.  audit_list->next is the first
b1dca6
-   element.  */
b1dca6
-static struct audit_list
b1dca6
+static void
b1dca6
+audit_list_init (struct audit_list *list)
b1dca6
 {
b1dca6
-  const char *name;
b1dca6
-  struct audit_list *next;
b1dca6
-} *audit_list;
b1dca6
+  list->length = 0;
b1dca6
+  list->current_index = 0;
b1dca6
+  list->current_tail = NULL;
b1dca6
+}
b1dca6
 
b1dca6
-/* Iterator for audit_list_string followed by audit_list.  */
b1dca6
-struct audit_list_iter
b1dca6
+static void
b1dca6
+audit_list_add_string (struct audit_list *list, const char *string)
b1dca6
 {
b1dca6
-  /* Tail of audit_list_string still needing processing, or NULL.  */
b1dca6
-  const char *audit_list_tail;
b1dca6
+  /* Empty strings do not load anything.  */
b1dca6
+  if (*string == '\0')
b1dca6
+    return;
b1dca6
 
b1dca6
-  /* The list element returned in the previous iteration.  NULL before
b1dca6
-     the first element.  */
b1dca6
-  struct audit_list *previous;
b1dca6
+  if (list->length == array_length (list->audit_strings))
b1dca6
+    _dl_fatal_printf ("Fatal glibc error: Too many audit modules requested\n");
b1dca6
 
b1dca6
-  /* Scratch buffer for returning a name which is part of
b1dca6
-     audit_list_string.  */
b1dca6
-  char fname[SECURE_NAME_LIMIT];
b1dca6
-};
b1dca6
+  list->audit_strings[list->length++] = string;
b1dca6
 
b1dca6
-/* Initialize an audit list iterator.  */
b1dca6
-static void
b1dca6
-audit_list_iter_init (struct audit_list_iter *iter)
b1dca6
-{
b1dca6
-  iter->audit_list_tail = audit_list_string;
b1dca6
-  iter->previous = NULL;
b1dca6
+  /* Initialize processing of the first string for
b1dca6
+     audit_list_next.  */
b1dca6
+  if (list->length == 1)
b1dca6
+    list->current_tail = string;
b1dca6
 }
b1dca6
 
b1dca6
-/* Iterate through both audit_list_string and audit_list.  */
b1dca6
 static const char *
b1dca6
-audit_list_iter_next (struct audit_list_iter *iter)
b1dca6
+audit_list_next (struct audit_list *list)
b1dca6
 {
b1dca6
-  if (iter->audit_list_tail != NULL)
b1dca6
+  if (list->current_tail == NULL)
b1dca6
+    return NULL;
b1dca6
+
b1dca6
+  while (true)
b1dca6
     {
b1dca6
-      /* First iterate over audit_list_string.  */
b1dca6
-      while (*iter->audit_list_tail != '\0')
b1dca6
+      /* Advance to the next string in audit_strings if the current
b1dca6
+	 string has been exhausted.  */
b1dca6
+      while (*list->current_tail == '\0')
b1dca6
 	{
b1dca6
-	  /* Split audit list at colon.  */
b1dca6
-	  size_t len = strcspn (iter->audit_list_tail, ":");
b1dca6
-	  if (len > 0 && len < sizeof (iter->fname))
b1dca6
+	  ++list->current_index;
b1dca6
+	  if (list->current_index == list->length)
b1dca6
 	    {
b1dca6
-	      memcpy (iter->fname, iter->audit_list_tail, len);
b1dca6
-	      iter->fname[len] = '\0';
b1dca6
+	      list->current_tail = NULL;
b1dca6
+	      return NULL;
b1dca6
 	    }
b1dca6
-	  else
b1dca6
-	    /* Do not return this name to the caller.  */
b1dca6
-	    iter->fname[0] = '\0';
b1dca6
-
b1dca6
-	  /* Skip over the substring and the following delimiter.  */
b1dca6
-	  iter->audit_list_tail += len;
b1dca6
-	  if (*iter->audit_list_tail == ':')
b1dca6
-	    ++iter->audit_list_tail;
b1dca6
-
b1dca6
-	  /* If the name is valid, return it.  */
b1dca6
-	  if (dso_name_valid_for_suid (iter->fname))
b1dca6
-	    return iter->fname;
b1dca6
-	  /* Otherwise, wrap around and try the next name.  */
b1dca6
+	  list->current_tail = list->audit_strings[list->current_index];
b1dca6
 	}
b1dca6
-      /* Fall through to the procesing of audit_list.  */
b1dca6
-    }
b1dca6
 
b1dca6
-  if (iter->previous == NULL)
b1dca6
-    {
b1dca6
-      if (audit_list == NULL)
b1dca6
-	/* No pre-parsed audit list.  */
b1dca6
-	return NULL;
b1dca6
-      /* Start of audit list.  The first list element is at
b1dca6
-	 audit_list->next (cyclic list).  */
b1dca6
-      iter->previous = audit_list->next;
b1dca6
-      return iter->previous->name;
b1dca6
+      /* Split the in-string audit list at the next colon colon.  */
b1dca6
+      size_t len = strcspn (list->current_tail, ":");
b1dca6
+      if (len > 0 && len < sizeof (list->fname))
b1dca6
+	{
b1dca6
+	  memcpy (list->fname, list->current_tail, len);
b1dca6
+	  list->fname[len] = '\0';
b1dca6
+	}
b1dca6
+      else
b1dca6
+	/* Mark the name as unusable for dso_name_valid_for_suid.  */
b1dca6
+	list->fname[0] = '\0';
b1dca6
+
b1dca6
+      /* Skip over the substring and the following delimiter.  */
b1dca6
+      list->current_tail += len;
b1dca6
+      if (*list->current_tail == ':')
b1dca6
+	++list->current_tail;
b1dca6
+
b1dca6
+      /* If the name is valid, return it.  */
b1dca6
+      if (dso_name_valid_for_suid (list->fname))
b1dca6
+	return list->fname;
b1dca6
+
b1dca6
+      /* Otherwise wrap around to find the next list element. .  */
b1dca6
     }
b1dca6
-  if (iter->previous == audit_list)
b1dca6
-    /* Cyclic list wrap-around.  */
b1dca6
-    return NULL;
b1dca6
-  iter->previous = iter->previous->next;
b1dca6
-  return iter->previous->name;
b1dca6
 }
b1dca6
 
b1dca6
 /* Set nonzero during loading and initialization of executable and
b1dca6
@@ -1060,15 +1079,13 @@ notify_audit_modules_of_loaded_object (struct link_map *map)
b1dca6
 
b1dca6
 /* Load all audit modules.  */
b1dca6
 static void
b1dca6
-load_audit_modules (struct link_map *main_map)
b1dca6
+load_audit_modules (struct link_map *main_map, struct audit_list *audit_list)
b1dca6
 {
b1dca6
   struct audit_ifaces *last_audit = NULL;
b1dca6
-  struct audit_list_iter al_iter;
b1dca6
-  audit_list_iter_init (&al_iter);
b1dca6
 
b1dca6
   while (true)
b1dca6
     {
b1dca6
-      const char *name = audit_list_iter_next (&al_iter);
b1dca6
+      const char *name = audit_list_next (audit_list);
b1dca6
       if (name == NULL)
b1dca6
 	break;
b1dca6
       load_audit_module (name, &last_audit);
b1dca6
@@ -1100,6 +1117,9 @@ dl_main (const ElfW(Phdr) *phdr,
b1dca6
   bool rtld_is_main = false;
b1dca6
   void *tcbp = NULL;
b1dca6
 
b1dca6
+  struct audit_list audit_list;
b1dca6
+  audit_list_init (&audit_list);
b1dca6
+
b1dca6
   GL(dl_init_static_tls) = &_dl_nothread_init_static_tls;
b1dca6
 
b1dca6
 #if defined SHARED && defined _LIBC_REENTRANT \
b1dca6
@@ -1113,7 +1133,7 @@ dl_main (const ElfW(Phdr) *phdr,
b1dca6
   GL(dl_make_stack_executable_hook) = &_dl_make_stack_executable;
b1dca6
 
b1dca6
   /* Process the environment variable which control the behaviour.  */
b1dca6
-  process_envvars (&mode);
b1dca6
+  process_envvars (&mode, &audit_list);
b1dca6
 
b1dca6
   /* Set up a flag which tells we are just starting.  */
b1dca6
   _dl_starting_up = 1;
b1dca6
@@ -1185,7 +1205,7 @@ dl_main (const ElfW(Phdr) *phdr,
b1dca6
 	  }
b1dca6
 	else if (! strcmp (_dl_argv[1], "--audit") && _dl_argc > 2)
b1dca6
 	  {
b1dca6
-	    process_dl_audit (_dl_argv[2]);
b1dca6
+	    audit_list_add_string (&audit_list, _dl_argv[2]);
b1dca6
 
b1dca6
 	    _dl_skip_args += 2;
b1dca6
 	    _dl_argc -= 2;
b1dca6
@@ -1612,8 +1632,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
b1dca6
 
b1dca6
   /* If we have auditing DSOs to load, do it now.  */
b1dca6
   bool need_security_init = true;
b1dca6
-  if (__glibc_unlikely (audit_list != NULL)
b1dca6
-      || __glibc_unlikely (audit_list_string != NULL))
b1dca6
+  if (audit_list.length > 0)
b1dca6
     {
b1dca6
       /* Since we start using the auditing DSOs right away we need to
b1dca6
 	 initialize the data structures now.  */
b1dca6
@@ -1626,7 +1645,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
b1dca6
       security_init ();
b1dca6
       need_security_init = false;
b1dca6
 
b1dca6
-      load_audit_modules (main_map);
b1dca6
+      load_audit_modules (main_map, &audit_list);
b1dca6
     }
b1dca6
 
b1dca6
   /* Keep track of the currently loaded modules to count how many
b1dca6
@@ -2500,30 +2519,6 @@ a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
b1dca6
     }
b1dca6
 }
b1dca6
 
b1dca6
-static void
b1dca6
-process_dl_audit (char *str)
b1dca6
-{
b1dca6
-  /* The parameter is a colon separated list of DSO names.  */
b1dca6
-  char *p;
b1dca6
-
b1dca6
-  while ((p = (strsep) (&str, ":")) != NULL)
b1dca6
-    if (dso_name_valid_for_suid (p))
b1dca6
-      {
b1dca6
-	/* This is using the local malloc, not the system malloc.  The
b1dca6
-	   memory can never be freed.  */
b1dca6
-	struct audit_list *newp = malloc (sizeof (*newp));
b1dca6
-	newp->name = p;
b1dca6
-
b1dca6
-	if (audit_list == NULL)
b1dca6
-	  audit_list = newp->next = newp;
b1dca6
-	else
b1dca6
-	  {
b1dca6
-	    newp->next = audit_list->next;
b1dca6
-	    audit_list = audit_list->next = newp;
b1dca6
-	  }
b1dca6
-      }
b1dca6
-}
b1dca6
-
b1dca6
 /* Process all environments variables the dynamic linker must recognize.
b1dca6
    Since all of them start with `LD_' we are a bit smarter while finding
b1dca6
    all the entries.  */
b1dca6
@@ -2531,7 +2526,7 @@ extern char **_environ attribute_hidden;
b1dca6
 
b1dca6
 
b1dca6
 static void
b1dca6
-process_envvars (enum mode *modep)
b1dca6
+process_envvars (enum mode *modep, struct audit_list *audit_list)
b1dca6
 {
b1dca6
   char **runp = _environ;
b1dca6
   char *envline;
b1dca6
@@ -2571,7 +2566,7 @@ process_envvars (enum mode *modep)
b1dca6
 	      break;
b1dca6
 	    }
b1dca6
 	  if (memcmp (envline, "AUDIT", 5) == 0)
b1dca6
-	    audit_list_string = &envline[6];
b1dca6
+	    audit_list_add_string (audit_list, &envline[6]);
b1dca6
 	  break;
b1dca6
 
b1dca6
 	case 7: