[2/3] elf: Simplify handling of lists of audit strings

Message ID 044c8e2c7af16bf2f48bc3d740fc5b5b14df8009.1585914979.git.fweimer@redhat.com
State Committed
Headers
Series Add DT_AUDIT support [BZ #24943] |

Commit Message

Florian Weimer April 3, 2020, 12:02 p.m. UTC
  All list elements are colon-separated strings, and there is a hard
upper limit for the number of audit modules, so it is possible to
pre-allocate a fixed-size array of strings to which the LD_AUDIT
environment variable and --audit arguments are added.

Also eliminate the global variables for the audit list because
the list is only needed briefly during startup.

There is a slight behavior change: All duplicate LD_AUDIT environment
variables are now processed, not just the last one as before.  However,
such environment vectors are invalid anyway.
---
 elf/rtld.c | 230 ++++++++++++++++++++++++++---------------------------
 1 file changed, 113 insertions(+), 117 deletions(-)
  

Comments

Carlos O'Donell April 3, 2020, 12:49 p.m. UTC | #1
On 4/3/20 8:02 AM, Florian Weimer via Libc-alpha wrote:
> All list elements are colon-separated strings, and there is a hard
> upper limit for the number of audit modules, so it is possible to
> pre-allocate a fixed-size array of strings to which the LD_AUDIT
> environment variable and --audit arguments are added.
> 
> Also eliminate the global variables for the audit list because
> the list is only needed briefly during startup.
> 
> There is a slight behavior change: All duplicate LD_AUDIT environment
> variables are now processed, not just the last one as before.  However,
> such environment vectors are invalid anyway.

Agreed.

OK for master.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> ---
>  elf/rtld.c | 230 ++++++++++++++++++++++++++---------------------------
>  1 file changed, 113 insertions(+), 117 deletions(-)
> 
> diff --git a/elf/rtld.c b/elf/rtld.c
> index 51dfaf966a..fe4dfbec67 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -45,6 +45,7 @@
>  #include <stap-probe.h>
>  #include <stackinfo.h>
>  #include <not-cancel.h>
> +#include <array_length.h>
>  
>  #include <assert.h>
>  
> @@ -109,8 +110,53 @@ static void print_missing_version (int errcode, const char *objname,
>  /* Print the various times we collected.  */
>  static void print_statistics (const hp_timing_t *total_timep);
>  
> -/* Add audit objects.  */
> -static void process_dl_audit (char *str);
> +/* Length limits for names and paths, to protect the dynamic linker,
> +   particularly when __libc_enable_secure is active.  */
> +#ifdef NAME_MAX
> +# define SECURE_NAME_LIMIT NAME_MAX
> +#else
> +# define SECURE_NAME_LIMIT 255
> +#endif
> +#ifdef PATH_MAX
> +# define SECURE_PATH_LIMIT PATH_MAX
> +#else
> +# define SECURE_PATH_LIMIT 1024
> +#endif

OK.

> +
> +/* Strings containing colon-separated lists of audit modules.  */
> +struct audit_list
> +{
> +  /* Array of strings containing colon-separated path lists.  Each
> +     audit module needs its own namespace, so pre-allocate the largest
> +     possible list.  */
> +  const char *audit_strings[DL_NNS];
> +
> +  /* Number of entries added to audit_strings.  */
> +  size_t length;
> +
> +  /* Index into the audit_strings array (for the iteration phase).  */
> +  size_t current_index;
> +
> +  /* Tail of audit_strings[current_index] which still needs
> +     processing.  */
> +  const char *current_tail;
> +
> +  /* Scratch buffer for returning a name which is part of the strings
> +     in audit_strings.  */
> +  char fname[SECURE_NAME_LIMIT];
> +};
> +
> +/* Creates an empty audit list.  */
> +static void audit_list_init (struct audit_list *);
> +
> +/* Add a string to the end of the audit list, for later parsing.  Must
> +   not be called after audit_list_next.  */
> +static void audit_list_add_string (struct audit_list *, const char *);
> +
> +/* Extract the next audit module from the audit list.  Only modules
> +   for which dso_name_valid_for_suid is true are returned.  Must be
> +   called after all the audit_list_add_string calls.  */
> +static const char *audit_list_next (struct audit_list *);
>  
>  /* This is a list of all the modes the dynamic loader can be in.  */
>  enum mode { normal, list, verify, trace };
> @@ -118,7 +164,7 @@ enum mode { normal, list, verify, trace };
>  /* Process all environments variables the dynamic linker must recognize.
>     Since all of them start with `LD_' we are a bit smarter while finding
>     all the entries.  */
> -static void process_envvars (enum mode *modep);
> +static void process_envvars (enum mode *modep, struct audit_list *);
>  
>  #ifdef DL_ARGV_NOT_RELRO
>  int _dl_argc attribute_hidden;
> @@ -146,19 +192,6 @@ uintptr_t __pointer_chk_guard_local
>  strong_alias (__pointer_chk_guard_local, __pointer_chk_guard)
>  #endif
>  
> -/* Length limits for names and paths, to protect the dynamic linker,
> -   particularly when __libc_enable_secure is active.  */
> -#ifdef NAME_MAX
> -# define SECURE_NAME_LIMIT NAME_MAX
> -#else
> -# define SECURE_NAME_LIMIT 255
> -#endif
> -#ifdef PATH_MAX
> -# define SECURE_PATH_LIMIT PATH_MAX
> -#else
> -# define SECURE_PATH_LIMIT 1024
> -#endif
> -
>  /* Check that AT_SECURE=0, or that the passed name does not contain
>     directories and is not overly long.  Reject empty names
>     unconditionally.  */
> @@ -176,89 +209,76 @@ dso_name_valid_for_suid (const char *p)
>    return *p != '\0';
>  }
>  
> -/* LD_AUDIT variable contents.  Must be processed before the
> -   audit_list below.  */
> -const char *audit_list_string;
> -
> -/* Cyclic list of auditing DSOs.  audit_list->next is the first
> -   element.  */
> -static struct audit_list
> +static void
> +audit_list_init (struct audit_list *list)
>  {
> -  const char *name;
> -  struct audit_list *next;
> -} *audit_list;
> +  list->length = 0;
> +  list->current_index = 0;
> +  list->current_tail = NULL;
> +}

OK.

>  
> -/* Iterator for audit_list_string followed by audit_list.  */
> -struct audit_list_iter
> +static void
> +audit_list_add_string (struct audit_list *list, const char *string)
>  {
> -  /* Tail of audit_list_string still needing processing, or NULL.  */
> -  const char *audit_list_tail;
> +  /* Empty strings do not load anything.  */
> +  if (*string == '\0')
> +    return;
>  
> -  /* The list element returned in the previous iteration.  NULL before
> -     the first element.  */
> -  struct audit_list *previous;
> +  /* Grow the array if necessary.  */
> +  if (list->length == array_length (list->audit_strings))
> +    _dl_fatal_printf ("Too many audit modules requested\n");
>  
> -  /* Scratch buffer for returning a name which is part of
> -     audit_list_string.  */
> -  char fname[SECURE_NAME_LIMIT];
> -};
> +  list->audit_strings[list->length++] = string;
>  
> -/* Initialize an audit list iterator.  */
> -static void
> -audit_list_iter_init (struct audit_list_iter *iter)
> -{
> -  iter->audit_list_tail = audit_list_string;
> -  iter->previous = NULL;
> +  /* Initialize processing of the first string for
> +     audit_list_next.  */
> +  if (list->length == 1)
> +    list->current_tail = string;
>  }
>  
> -/* Iterate through both audit_list_string and audit_list.  */
>  static const char *
> -audit_list_iter_next (struct audit_list_iter *iter)
> +audit_list_next (struct audit_list *list)
>  {
> -  if (iter->audit_list_tail != NULL)
> +  if (list->current_tail == NULL)
> +    return NULL;
> +
> +  while (true)
>      {
> -      /* First iterate over audit_list_string.  */
> -      while (*iter->audit_list_tail != '\0')
> +      /* Advance to the next string in audit_strings if the current
> +	 string has been exhausted.  */
> +      while (*list->current_tail == '\0')
>  	{
> -	  /* Split audit list at colon.  */
> -	  size_t len = strcspn (iter->audit_list_tail, ":");
> -	  if (len > 0 && len < sizeof (iter->fname))
> +	  ++list->current_index;
> +	  if (list->current_index == list->length)
>  	    {
> -	      memcpy (iter->fname, iter->audit_list_tail, len);
> -	      iter->fname[len] = '\0';
> +	      list->current_tail = NULL;
> +	      return NULL;
>  	    }
> -	  else
> -	    /* Do not return this name to the caller.  */
> -	    iter->fname[0] = '\0';
> -
> -	  /* Skip over the substring and the following delimiter.  */
> -	  iter->audit_list_tail += len;
> -	  if (*iter->audit_list_tail == ':')
> -	    ++iter->audit_list_tail;
> -
> -	  /* If the name is valid, return it.  */
> -	  if (dso_name_valid_for_suid (iter->fname))
> -	    return iter->fname;
> -	  /* Otherwise, wrap around and try the next name.  */
> +	  list->current_tail = list->audit_strings[list->current_index];
>  	}
> -      /* Fall through to the procesing of audit_list.  */
> -    }
>  
> -  if (iter->previous == NULL)
> -    {
> -      if (audit_list == NULL)
> -	/* No pre-parsed audit list.  */
> -	return NULL;
> -      /* Start of audit list.  The first list element is at
> -	 audit_list->next (cyclic list).  */
> -      iter->previous = audit_list->next;
> -      return iter->previous->name;
> +      /* Split the in-string audit list at the next colon colon.  */
> +      size_t len = strcspn (list->current_tail, ":");
> +      if (len > 0 && len < sizeof (list->fname))
> +	{
> +	  memcpy (list->fname, list->current_tail, len);
> +	  list->fname[len] = '\0';
> +	}
> +      else
> +	/* Mark the name as unusable for dso_name_valid_for_suid.  */
> +	list->fname[0] = '\0';
> +
> +      /* Skip over the substring and the following delimiter.  */
> +      list->current_tail += len;
> +      if (*list->current_tail == ':')
> +	++list->current_tail;
> +
> +      /* If the name is valid, return it.  */
> +      if (dso_name_valid_for_suid (list->fname))
> +	return list->fname;
> +
> +      /* Otherwise wrap around to find the next list element. .  */
>      }
> -  if (iter->previous == audit_list)
> -    /* Cyclic list wrap-around.  */
> -    return NULL;
> -  iter->previous = iter->previous->next;
> -  return iter->previous->name;
>  }
>  
>  #ifndef HAVE_INLINED_SYSCALLS
> @@ -1062,15 +1082,13 @@ notify_audit_modules_of_loaded_object (struct link_map *map)
>  
>  /* Load all audit modules.  */
>  static void
> -load_audit_modules (struct link_map *main_map)
> +load_audit_modules (struct link_map *main_map, struct audit_list *audit_list)
>  {
>    struct audit_ifaces *last_audit = NULL;
> -  struct audit_list_iter al_iter;
> -  audit_list_iter_init (&al_iter);
>  
>    while (true)
>      {
> -      const char *name = audit_list_iter_next (&al_iter);
> +      const char *name = audit_list_next (audit_list);
>        if (name == NULL)
>  	break;
>        load_audit_module (name, &last_audit);
> @@ -1102,6 +1120,9 @@ dl_main (const ElfW(Phdr) *phdr,
>    bool rtld_is_main = false;
>    void *tcbp = NULL;
>  
> +  struct audit_list audit_list;
> +  audit_list_init (&audit_list);
> +
>    GL(dl_init_static_tls) = &_dl_nothread_init_static_tls;
>  
>  #if defined SHARED && defined _LIBC_REENTRANT \
> @@ -1115,7 +1136,7 @@ dl_main (const ElfW(Phdr) *phdr,
>    GL(dl_make_stack_executable_hook) = &_dl_make_stack_executable;
>  
>    /* Process the environment variable which control the behaviour.  */
> -  process_envvars (&mode);
> +  process_envvars (&mode, &audit_list);
>  
>  #ifndef HAVE_INLINED_SYSCALLS
>    /* Set up a flag which tells we are just starting.  */
> @@ -1189,7 +1210,7 @@ dl_main (const ElfW(Phdr) *phdr,
>  	  }
>  	else if (! strcmp (_dl_argv[1], "--audit") && _dl_argc > 2)
>  	  {
> -	    process_dl_audit (_dl_argv[2]);
> +	    audit_list_add_string (&audit_list, _dl_argv[2]);
>  
>  	    _dl_skip_args += 2;
>  	    _dl_argc -= 2;
> @@ -1619,8 +1640,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
>  
>    /* If we have auditing DSOs to load, do it now.  */
>    bool need_security_init = true;
> -  if (__glibc_unlikely (audit_list != NULL)
> -      || __glibc_unlikely (audit_list_string != NULL))
> +  if (audit_list.length > 0)
>      {
>        /* Since we start using the auditing DSOs right away we need to
>  	 initialize the data structures now.  */
> @@ -1633,7 +1653,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
>        security_init ();
>        need_security_init = false;
>  
> -      load_audit_modules (main_map);
> +      load_audit_modules (main_map, &audit_list);
>      }
>  
>    /* Keep track of the currently loaded modules to count how many
> @@ -2507,30 +2527,6 @@ a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
>      }
>  }
>  
> -static void
> -process_dl_audit (char *str)
> -{
> -  /* The parameter is a colon separated list of DSO names.  */
> -  char *p;
> -
> -  while ((p = (strsep) (&str, ":")) != NULL)
> -    if (dso_name_valid_for_suid (p))
> -      {
> -	/* This is using the local malloc, not the system malloc.  The
> -	   memory can never be freed.  */
> -	struct audit_list *newp = malloc (sizeof (*newp));
> -	newp->name = p;
> -
> -	if (audit_list == NULL)
> -	  audit_list = newp->next = newp;
> -	else
> -	  {
> -	    newp->next = audit_list->next;
> -	    audit_list = audit_list->next = newp;
> -	  }
> -      }
> -}
> -
>  /* Process all environments variables the dynamic linker must recognize.
>     Since all of them start with `LD_' we are a bit smarter while finding
>     all the entries.  */
> @@ -2538,7 +2534,7 @@ extern char **_environ attribute_hidden;
>  
>  
>  static void
> -process_envvars (enum mode *modep)
> +process_envvars (enum mode *modep, struct audit_list *audit_list)
>  {
>    char **runp = _environ;
>    char *envline;
> @@ -2578,7 +2574,7 @@ process_envvars (enum mode *modep)
>  	      break;
>  	    }
>  	  if (memcmp (envline, "AUDIT", 5) == 0)
> -	    audit_list_string = &envline[6];
> +	    audit_list_add_string (audit_list, &envline[6]);

OK.

>  	  break;
>  
>  	case 7:
>
  
Florian Weimer April 3, 2020, 12:58 p.m. UTC | #2
* Carlos O'Donell:

> On 4/3/20 8:02 AM, Florian Weimer via Libc-alpha wrote:
>> All list elements are colon-separated strings, and there is a hard
>> upper limit for the number of audit modules, so it is possible to
>> pre-allocate a fixed-size array of strings to which the LD_AUDIT
>> environment variable and --audit arguments are added.
>> 
>> Also eliminate the global variables for the audit list because
>> the list is only needed briefly during startup.
>> 
>> There is a slight behavior change: All duplicate LD_AUDIT environment
>> variables are now processed, not just the last one as before.  However,
>> such environment vectors are invalid anyway.
>
> Agreed.
>
> OK for master.
>
> Reviewed-by: Carlos O'Donell <carlos@redhat.com>

Thanks.

>> +  /* Grow the array if necessary.  */
>> +  if (list->length == array_length (list->audit_strings))
>> +    _dl_fatal_printf ("Too many audit modules requested\n");

I'm going to change this to:

  Fatal glibc error: Too many audit modules requested

Thanks,
Florian
  
Carlos O'Donell April 3, 2020, 1:01 p.m. UTC | #3
On 4/3/20 8:58 AM, Florian Weimer wrote:
> * Carlos O'Donell:
> 
>> On 4/3/20 8:02 AM, Florian Weimer via Libc-alpha wrote:
>>> All list elements are colon-separated strings, and there is a hard
>>> upper limit for the number of audit modules, so it is possible to
>>> pre-allocate a fixed-size array of strings to which the LD_AUDIT
>>> environment variable and --audit arguments are added.
>>>
>>> Also eliminate the global variables for the audit list because
>>> the list is only needed briefly during startup.
>>>
>>> There is a slight behavior change: All duplicate LD_AUDIT environment
>>> variables are now processed, not just the last one as before.  However,
>>> such environment vectors are invalid anyway.
>>
>> Agreed.
>>
>> OK for master.
>>
>> Reviewed-by: Carlos O'Donell <carlos@redhat.com>
> 
> Thanks.
> 
>>> +  /* Grow the array if necessary.  */
>>> +  if (list->length == array_length (list->audit_strings))
>>> +    _dl_fatal_printf ("Too many audit modules requested\n");
> 
> I'm going to change this to:
> 
>   Fatal glibc error: Too many audit modules requested

That seems fine to me.
  
Andreas Schwab April 3, 2020, 1:30 p.m. UTC | #4
On Apr 03 2020, Florian Weimer via Libc-alpha wrote:

> +  /* Grow the array if necessary.  */
> +  if (list->length == array_length (list->audit_strings))
> +    _dl_fatal_printf ("Too many audit modules requested\n");

That comment doesn't make sense.  You are definitely not growing
anything here.

Andreas.
  
Florian Weimer April 3, 2020, 1:48 p.m. UTC | #5
* Andreas Schwab:

> On Apr 03 2020, Florian Weimer via Libc-alpha wrote:
>
>> +  /* Grow the array if necessary.  */
>> +  if (list->length == array_length (list->audit_strings))
>> +    _dl_fatal_printf ("Too many audit modules requested\n");
>
> That comment doesn't make sense.  You are definitely not growing
> anything here.

Right, it's a leftover from previous versions.  I will fix it.  Thanks.

Florian
  

Patch

diff --git a/elf/rtld.c b/elf/rtld.c
index 51dfaf966a..fe4dfbec67 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -45,6 +45,7 @@ 
 #include <stap-probe.h>
 #include <stackinfo.h>
 #include <not-cancel.h>
+#include <array_length.h>
 
 #include <assert.h>
 
@@ -109,8 +110,53 @@  static void print_missing_version (int errcode, const char *objname,
 /* Print the various times we collected.  */
 static void print_statistics (const hp_timing_t *total_timep);
 
-/* Add audit objects.  */
-static void process_dl_audit (char *str);
+/* Length limits for names and paths, to protect the dynamic linker,
+   particularly when __libc_enable_secure is active.  */
+#ifdef NAME_MAX
+# define SECURE_NAME_LIMIT NAME_MAX
+#else
+# define SECURE_NAME_LIMIT 255
+#endif
+#ifdef PATH_MAX
+# define SECURE_PATH_LIMIT PATH_MAX
+#else
+# define SECURE_PATH_LIMIT 1024
+#endif
+
+/* Strings containing colon-separated lists of audit modules.  */
+struct audit_list
+{
+  /* Array of strings containing colon-separated path lists.  Each
+     audit module needs its own namespace, so pre-allocate the largest
+     possible list.  */
+  const char *audit_strings[DL_NNS];
+
+  /* Number of entries added to audit_strings.  */
+  size_t length;
+
+  /* Index into the audit_strings array (for the iteration phase).  */
+  size_t current_index;
+
+  /* Tail of audit_strings[current_index] which still needs
+     processing.  */
+  const char *current_tail;
+
+  /* Scratch buffer for returning a name which is part of the strings
+     in audit_strings.  */
+  char fname[SECURE_NAME_LIMIT];
+};
+
+/* Creates an empty audit list.  */
+static void audit_list_init (struct audit_list *);
+
+/* Add a string to the end of the audit list, for later parsing.  Must
+   not be called after audit_list_next.  */
+static void audit_list_add_string (struct audit_list *, const char *);
+
+/* Extract the next audit module from the audit list.  Only modules
+   for which dso_name_valid_for_suid is true are returned.  Must be
+   called after all the audit_list_add_string calls.  */
+static const char *audit_list_next (struct audit_list *);
 
 /* This is a list of all the modes the dynamic loader can be in.  */
 enum mode { normal, list, verify, trace };
@@ -118,7 +164,7 @@  enum mode { normal, list, verify, trace };
 /* Process all environments variables the dynamic linker must recognize.
    Since all of them start with `LD_' we are a bit smarter while finding
    all the entries.  */
-static void process_envvars (enum mode *modep);
+static void process_envvars (enum mode *modep, struct audit_list *);
 
 #ifdef DL_ARGV_NOT_RELRO
 int _dl_argc attribute_hidden;
@@ -146,19 +192,6 @@  uintptr_t __pointer_chk_guard_local
 strong_alias (__pointer_chk_guard_local, __pointer_chk_guard)
 #endif
 
-/* Length limits for names and paths, to protect the dynamic linker,
-   particularly when __libc_enable_secure is active.  */
-#ifdef NAME_MAX
-# define SECURE_NAME_LIMIT NAME_MAX
-#else
-# define SECURE_NAME_LIMIT 255
-#endif
-#ifdef PATH_MAX
-# define SECURE_PATH_LIMIT PATH_MAX
-#else
-# define SECURE_PATH_LIMIT 1024
-#endif
-
 /* Check that AT_SECURE=0, or that the passed name does not contain
    directories and is not overly long.  Reject empty names
    unconditionally.  */
@@ -176,89 +209,76 @@  dso_name_valid_for_suid (const char *p)
   return *p != '\0';
 }
 
-/* LD_AUDIT variable contents.  Must be processed before the
-   audit_list below.  */
-const char *audit_list_string;
-
-/* Cyclic list of auditing DSOs.  audit_list->next is the first
-   element.  */
-static struct audit_list
+static void
+audit_list_init (struct audit_list *list)
 {
-  const char *name;
-  struct audit_list *next;
-} *audit_list;
+  list->length = 0;
+  list->current_index = 0;
+  list->current_tail = NULL;
+}
 
-/* Iterator for audit_list_string followed by audit_list.  */
-struct audit_list_iter
+static void
+audit_list_add_string (struct audit_list *list, const char *string)
 {
-  /* Tail of audit_list_string still needing processing, or NULL.  */
-  const char *audit_list_tail;
+  /* Empty strings do not load anything.  */
+  if (*string == '\0')
+    return;
 
-  /* The list element returned in the previous iteration.  NULL before
-     the first element.  */
-  struct audit_list *previous;
+  /* Grow the array if necessary.  */
+  if (list->length == array_length (list->audit_strings))
+    _dl_fatal_printf ("Too many audit modules requested\n");
 
-  /* Scratch buffer for returning a name which is part of
-     audit_list_string.  */
-  char fname[SECURE_NAME_LIMIT];
-};
+  list->audit_strings[list->length++] = string;
 
-/* Initialize an audit list iterator.  */
-static void
-audit_list_iter_init (struct audit_list_iter *iter)
-{
-  iter->audit_list_tail = audit_list_string;
-  iter->previous = NULL;
+  /* Initialize processing of the first string for
+     audit_list_next.  */
+  if (list->length == 1)
+    list->current_tail = string;
 }
 
-/* Iterate through both audit_list_string and audit_list.  */
 static const char *
-audit_list_iter_next (struct audit_list_iter *iter)
+audit_list_next (struct audit_list *list)
 {
-  if (iter->audit_list_tail != NULL)
+  if (list->current_tail == NULL)
+    return NULL;
+
+  while (true)
     {
-      /* First iterate over audit_list_string.  */
-      while (*iter->audit_list_tail != '\0')
+      /* Advance to the next string in audit_strings if the current
+	 string has been exhausted.  */
+      while (*list->current_tail == '\0')
 	{
-	  /* Split audit list at colon.  */
-	  size_t len = strcspn (iter->audit_list_tail, ":");
-	  if (len > 0 && len < sizeof (iter->fname))
+	  ++list->current_index;
+	  if (list->current_index == list->length)
 	    {
-	      memcpy (iter->fname, iter->audit_list_tail, len);
-	      iter->fname[len] = '\0';
+	      list->current_tail = NULL;
+	      return NULL;
 	    }
-	  else
-	    /* Do not return this name to the caller.  */
-	    iter->fname[0] = '\0';
-
-	  /* Skip over the substring and the following delimiter.  */
-	  iter->audit_list_tail += len;
-	  if (*iter->audit_list_tail == ':')
-	    ++iter->audit_list_tail;
-
-	  /* If the name is valid, return it.  */
-	  if (dso_name_valid_for_suid (iter->fname))
-	    return iter->fname;
-	  /* Otherwise, wrap around and try the next name.  */
+	  list->current_tail = list->audit_strings[list->current_index];
 	}
-      /* Fall through to the procesing of audit_list.  */
-    }
 
-  if (iter->previous == NULL)
-    {
-      if (audit_list == NULL)
-	/* No pre-parsed audit list.  */
-	return NULL;
-      /* Start of audit list.  The first list element is at
-	 audit_list->next (cyclic list).  */
-      iter->previous = audit_list->next;
-      return iter->previous->name;
+      /* Split the in-string audit list at the next colon colon.  */
+      size_t len = strcspn (list->current_tail, ":");
+      if (len > 0 && len < sizeof (list->fname))
+	{
+	  memcpy (list->fname, list->current_tail, len);
+	  list->fname[len] = '\0';
+	}
+      else
+	/* Mark the name as unusable for dso_name_valid_for_suid.  */
+	list->fname[0] = '\0';
+
+      /* Skip over the substring and the following delimiter.  */
+      list->current_tail += len;
+      if (*list->current_tail == ':')
+	++list->current_tail;
+
+      /* If the name is valid, return it.  */
+      if (dso_name_valid_for_suid (list->fname))
+	return list->fname;
+
+      /* Otherwise wrap around to find the next list element. .  */
     }
-  if (iter->previous == audit_list)
-    /* Cyclic list wrap-around.  */
-    return NULL;
-  iter->previous = iter->previous->next;
-  return iter->previous->name;
 }
 
 #ifndef HAVE_INLINED_SYSCALLS
@@ -1062,15 +1082,13 @@  notify_audit_modules_of_loaded_object (struct link_map *map)
 
 /* Load all audit modules.  */
 static void
-load_audit_modules (struct link_map *main_map)
+load_audit_modules (struct link_map *main_map, struct audit_list *audit_list)
 {
   struct audit_ifaces *last_audit = NULL;
-  struct audit_list_iter al_iter;
-  audit_list_iter_init (&al_iter);
 
   while (true)
     {
-      const char *name = audit_list_iter_next (&al_iter);
+      const char *name = audit_list_next (audit_list);
       if (name == NULL)
 	break;
       load_audit_module (name, &last_audit);
@@ -1102,6 +1120,9 @@  dl_main (const ElfW(Phdr) *phdr,
   bool rtld_is_main = false;
   void *tcbp = NULL;
 
+  struct audit_list audit_list;
+  audit_list_init (&audit_list);
+
   GL(dl_init_static_tls) = &_dl_nothread_init_static_tls;
 
 #if defined SHARED && defined _LIBC_REENTRANT \
@@ -1115,7 +1136,7 @@  dl_main (const ElfW(Phdr) *phdr,
   GL(dl_make_stack_executable_hook) = &_dl_make_stack_executable;
 
   /* Process the environment variable which control the behaviour.  */
-  process_envvars (&mode);
+  process_envvars (&mode, &audit_list);
 
 #ifndef HAVE_INLINED_SYSCALLS
   /* Set up a flag which tells we are just starting.  */
@@ -1189,7 +1210,7 @@  dl_main (const ElfW(Phdr) *phdr,
 	  }
 	else if (! strcmp (_dl_argv[1], "--audit") && _dl_argc > 2)
 	  {
-	    process_dl_audit (_dl_argv[2]);
+	    audit_list_add_string (&audit_list, _dl_argv[2]);
 
 	    _dl_skip_args += 2;
 	    _dl_argc -= 2;
@@ -1619,8 +1640,7 @@  ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
 
   /* If we have auditing DSOs to load, do it now.  */
   bool need_security_init = true;
-  if (__glibc_unlikely (audit_list != NULL)
-      || __glibc_unlikely (audit_list_string != NULL))
+  if (audit_list.length > 0)
     {
       /* Since we start using the auditing DSOs right away we need to
 	 initialize the data structures now.  */
@@ -1633,7 +1653,7 @@  ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
       security_init ();
       need_security_init = false;
 
-      load_audit_modules (main_map);
+      load_audit_modules (main_map, &audit_list);
     }
 
   /* Keep track of the currently loaded modules to count how many
@@ -2507,30 +2527,6 @@  a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
     }
 }
 
-static void
-process_dl_audit (char *str)
-{
-  /* The parameter is a colon separated list of DSO names.  */
-  char *p;
-
-  while ((p = (strsep) (&str, ":")) != NULL)
-    if (dso_name_valid_for_suid (p))
-      {
-	/* This is using the local malloc, not the system malloc.  The
-	   memory can never be freed.  */
-	struct audit_list *newp = malloc (sizeof (*newp));
-	newp->name = p;
-
-	if (audit_list == NULL)
-	  audit_list = newp->next = newp;
-	else
-	  {
-	    newp->next = audit_list->next;
-	    audit_list = audit_list->next = newp;
-	  }
-      }
-}
-
 /* Process all environments variables the dynamic linker must recognize.
    Since all of them start with `LD_' we are a bit smarter while finding
    all the entries.  */
@@ -2538,7 +2534,7 @@  extern char **_environ attribute_hidden;
 
 
 static void
-process_envvars (enum mode *modep)
+process_envvars (enum mode *modep, struct audit_list *audit_list)
 {
   char **runp = _environ;
   char *envline;
@@ -2578,7 +2574,7 @@  process_envvars (enum mode *modep)
 	      break;
 	    }
 	  if (memcmp (envline, "AUDIT", 5) == 0)
-	    audit_list_string = &envline[6];
+	    audit_list_add_string (audit_list, &envline[6]);
 	  break;
 
 	case 7: