[v2] elf: Load files from /etc/ld.so.preload.d

Message ID 20261002140558.130572-1-l.stelmach@samsung.com (mailing list archive)
State Changes Requested
Headers
Series [v2] elf: Load files from /etc/ld.so.preload.d |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
redhat-pt-bot/TryBot-32bit fail Patch series failed to build
linaro-tcwg-bot/tcwg_glibc_build--master-arm fail Build failed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 fail Build failed

Commit Message

Łukasz Stelmach Oct. 2, 2026, 2:05 p.m. UTC
  Enable reading a list of libraries to preload from files in
/etc/ld.so.preload.d directory. The files are read in alphabetical order
and must end in `.conf` to be read.

Fixes: https://sourceware.org/bugzilla/show_bug.cgi?id=24913
Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
---
Changelog:
 
v2
  - use scandir(3) to sort files from /etc/ld.so.preload.d alphabetically
 
 elf/rtld.c | 185 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 114 insertions(+), 71 deletions(-)
  

Comments

Adhemerval Zanella Oct. 3, 2026, 3:58 p.m. UTC | #1
On 02/10/26 16:05, Łukasz Stelmach wrote:
> Enable reading a list of libraries to preload from files in
> /etc/ld.so.preload.d directory. The files are read in alphabetical order
> and must end in `.conf` to be read.
> 
> Fixes: https://sourceware.org/bugzilla/show_bug.cgi?id=24913
> Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>

Now that we always load the ld.so.cache, I think we can use a more robust 
approach by moving the ld.so.preload support as a cache extension instead.
This would remove the requirement of accessing/parsing the ld.so.preload
during process execution, plus this extra loader code.

So I would recommend to:

* Add a new cache section cache_extension_tag_preload at sysdeps/generic/dl-cache.h:
  it will hold a count followed by that many offsets into the existing string table,
  and the loader rejects the section if its size doesn't match the count.  Old glibc
  that don't know the tag skip it.

* ldconfig: a new module, elf/preloadconf.c that reads SYSCONFDIR/ld.so.preload
  (overridable with a new -P FILE option, and honouring -r). Entries are stored
  exactly as written, so $LIB and bare sonames still expand and resolve at load
  time. The ldconfig -p now lists the section.

* Writer refactor: write_extensions in elf/cache.c now builds a list of sections
  and lays them out with per-section alignment, replacing the hand-computed offsets.
  Any future section is a one-line addition.

* rtld no longer reads /etc/ld.so.preload for USE_LDCONFIG (we still need to keep
  the old parser for Hurd). A new _dl_load_cache_preload() in elf/dl-cache.c returns
  a copy of the list, because each do_preload can trigger a cache reload
  (_dl_maybe_load_ldsocache, protects against a race with a concurrent ldconfig).
  It should also checks that every offset and string stays inside the mapping.
  The --inhibit-cache has no effect on preloads, and secure-mode handling is the
  same as before (__RTLD_SECURE).

* We gain recursing support from reusing ldconfig_parse_config, and we use the same
  priority as used for ld.so.conf.

* Editing ld.so.preload now takes effect only after ldconfig runs, and it also covers
  systems upgrading with an old cache and containers or images that ship without
  running ldconfig.

You will also need tests, manual description, and a NEWS test for this. 

> ---
> Changelog:
>  
> v2
>   - use scandir(3) to sort files from /etc/ld.so.preload.d alphabetically
>  
>  elf/rtld.c | 185 +++++++++++++++++++++++++++++++++--------------------
>  1 file changed, 114 insertions(+), 71 deletions(-)
> 
> diff --git a/elf/rtld.c b/elf/rtld.c
> index 324393d606..6b26620759 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -54,6 +54,8 @@
>  #include <dl-audit-check.h>
>  #include <dl-call_tls_init_tp.h>
>  #include <dl-exec-post.h>
> +#include <sys/types.h>
> +#include <dirent.h>
>  
>  #include <assert.h>
>  
> @@ -872,6 +874,89 @@ handle_preload_list (const char *preloadlist, struct link_map *main_map,
>    return npreloads;
>  }
>  
> +unsigned int
> +handle_preload_file (const char *preload_file, struct link_map *main_map)
> +{
> +  size_t file_size;
> +  char *file;
> +  unsigned int npreloads = 0;
> +
> +  /* Read the contents of the file.  */
> +  file = _dl_sysdep_read_whole_file (preload_file, &file_size,
> +				     PROT_READ | PROT_WRITE);
> +  if (__glibc_unlikely (file != MAP_FAILED))
> +    {
> +      /* Parse the file.  It contains names of libraries to be loaded,
> +	 separated by white spaces or `:'.  It may also contain
> +	 comments introduced by `#'.  */
> +      char *problem;
> +      char *runp;
> +      size_t rest;
> +
> +      /* Eliminate comments.  */
> +      runp = file;
> +      rest = file_size;
> +      while (rest > 0)
> +	{
> +	  char *comment = memchr (runp, '#', rest);
> +	  if (comment == NULL)
> +	    break;
> +
> +	  rest -= comment - runp;
> +	  do
> +	    *comment = ' ';
> +	  while (--rest > 0 && *++comment != '\n');
> +	}
> +
> +      /* We have one problematic case: if we have a name at the end of
> +	 the file without a trailing terminating characters, we cannot
> +	 place the \0.  Handle the case separately.  */
> +      if (file[file_size - 1] != ' ' && file[file_size - 1] != '\t'
> +	  && file[file_size - 1] != '\n' && file[file_size - 1] != ':')
> +	{
> +	  problem = &file[file_size];
> +	  while (problem > file && problem[-1] != ' '
> +		 && problem[-1] != '\t'
> +		 && problem[-1] != '\n' && problem[-1] != ':')
> +	    --problem;
> +
> +	  if (problem > file)
> +	    problem[-1] = '\0';
> +	}
> +      else
> +	{
> +	  problem = NULL;
> +	  file[file_size - 1] = '\0';
> +	}
> +
> +      RTLD_TIMING_VAR (start);
> +      rtld_timer_start (&start);
> +
> +      if (file != problem)
> +	{
> +	  char *p;
> +	  runp = file;
> +	  while ((p = strsep (&runp, ": \t\n")) != NULL)
> +	    if (p[0] != '\0')
> +	      npreloads += do_preload (p, main_map, preload_file);
> +	}
> +
> +      if (problem != NULL)
> +	{
> +	  char *p = strndupa (problem, file_size - (problem - file));
> +
> +	  npreloads += do_preload (p, main_map, preload_file);
> +	}
> +
> +      rtld_timer_accum (&load_time, start);
> +
> +      /* We don't need the file anymore.  */
> +      __munmap (file, file_size);
> +    }
> +
> +  return npreloads;
> +}
> +
>  /* Called if the audit DSO cannot be used: if it does not have the
>     appropriate interfaces, or it expects a more recent version library
>     version than what the dynamic linker provides.  */
> @@ -1338,6 +1423,20 @@ _dl_start_args_adjust (int skip_args, int skip_env)
>  #endif
>  }
>  
> +static int
> +conf_filter(const struct dirent *d)
> +{
> +  int len;
> +  if (d == NULL)
> +    return 0;
> +  len = strlen(d->d_name);
> +  if (len < (sizeof(".conf")))
> +    return 0;
> +  if (strncmp(".conf", &d->d_name[len - 5], 5) != 0)
> +    return 0;
> +  return 1;
> +}
> +
>  static void
>  dl_main (const ElfW(Phdr) *phdr,
>  	 ElfW(Word) phnum,
> @@ -1345,8 +1444,6 @@ dl_main (const ElfW(Phdr) *phdr,
>  	 ElfW(auxv_t) *auxv)
>  {
>    struct link_map *main_map;
> -  size_t file_size;
> -  char *file;
>    unsigned int i;
>    bool rtld_is_main = false;
>    void *tcbp = NULL;
> @@ -1841,80 +1938,26 @@ dl_main (const ElfW(Phdr) *phdr,
>       the work but this does not matter, since it is not for production
>       use.  */
>    static const char preload_file[] = "/etc/ld.so.preload";
> -  if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
> -    {
> -      /* Read the contents of the file.  */
> -      file = _dl_sysdep_read_whole_file (preload_file, &file_size,
> -					 PROT_READ | PROT_WRITE);
> -      if (__glibc_unlikely (file != MAP_FAILED))
> -	{
> -	  /* Parse the file.  It contains names of libraries to be loaded,
> -	     separated by white spaces or `:'.  It may also contain
> -	     comments introduced by `#'.  */
> -	  char *problem;
> -	  char *runp;
> -	  size_t rest;
> -
> -	  /* Eliminate comments.  */
> -	  runp = file;
> -	  rest = file_size;
> -	  while (rest > 0)
> -	    {
> -	      char *comment = memchr (runp, '#', rest);
> -	      if (comment == NULL)
> -		break;
> -
> -	      rest -= comment - runp;
> -	      do
> -		*comment = ' ';
> -	      while (--rest > 0 && *++comment != '\n');
> -	    }
> +  static const char preload_dir[] = "/etc/ld.so.preload.d";
>  
> -	  /* We have one problematic case: if we have a name at the end of
> -	     the file without a trailing terminating characters, we cannot
> -	     place the \0.  Handle the case separately.  */
> -	  if (file[file_size - 1] != ' ' && file[file_size - 1] != '\t'
> -	      && file[file_size - 1] != '\n' && file[file_size - 1] != ':')
> -	    {
> -	      problem = &file[file_size];
> -	      while (problem > file && problem[-1] != ' '
> -		     && problem[-1] != '\t'
> -		     && problem[-1] != '\n' && problem[-1] != ':')
> -		--problem;
> -
> -	      if (problem > file)
> -		problem[-1] = '\0';
> -	    }
> -	  else
> -	    {
> -	      problem = NULL;
> -	      file[file_size - 1] = '\0';
> -	    }
> -
> -	  RTLD_TIMING_VAR (start);
> -	  rtld_timer_start (&start);
> -
> -	  if (file != problem)
> -	    {
> -	      char *p;
> -	      runp = file;
> -	      while ((p = strsep (&runp, ": \t\n")) != NULL)
> -		if (p[0] != '\0')
> -		  npreloads += do_preload (p, main_map, preload_file);
> -	    }
> +  if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
> +      npreloads += handle_preload_file (preload_file, main_map);
>  
> -	  if (problem != NULL)
> -	    {
> -	      char *p = strndupa (problem, file_size - (problem - file));
> +  if (__glibc_unlikely (__access (preload_dir, R_OK | X_OK) == 0))
> +    {
> +      struct dirent **conffiles = NULL;
> +      int i, n = 0;
>  
> -	      npreloads += do_preload (p, main_map, preload_file);
> -	    }
> +      n = scandir (preload_dir, &conffiles, conf_filter, alphasort);
> +      if (n < 0)
> +	n = 0;
>  
> -	  rtld_timer_accum (&load_time, start);
> +      for (i = 0; i < n; i++) {
> +            npreloads += handle_preload_file (conffiles[i]->d_name, main_map);
> +	    free(conffiles[i]);
> +      }
>  
> -	  /* We don't need the file anymore.  */
> -	  __munmap (file, file_size);
> -	}
> +      free(conffiles);
>      }
>  
>    if (__glibc_unlikely (*first_preload != NULL))
  

Patch

diff --git a/elf/rtld.c b/elf/rtld.c
index 324393d606..6b26620759 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -54,6 +54,8 @@ 
 #include <dl-audit-check.h>
 #include <dl-call_tls_init_tp.h>
 #include <dl-exec-post.h>
+#include <sys/types.h>
+#include <dirent.h>
 
 #include <assert.h>
 
@@ -872,6 +874,89 @@  handle_preload_list (const char *preloadlist, struct link_map *main_map,
   return npreloads;
 }
 
+unsigned int
+handle_preload_file (const char *preload_file, struct link_map *main_map)
+{
+  size_t file_size;
+  char *file;
+  unsigned int npreloads = 0;
+
+  /* Read the contents of the file.  */
+  file = _dl_sysdep_read_whole_file (preload_file, &file_size,
+				     PROT_READ | PROT_WRITE);
+  if (__glibc_unlikely (file != MAP_FAILED))
+    {
+      /* Parse the file.  It contains names of libraries to be loaded,
+	 separated by white spaces or `:'.  It may also contain
+	 comments introduced by `#'.  */
+      char *problem;
+      char *runp;
+      size_t rest;
+
+      /* Eliminate comments.  */
+      runp = file;
+      rest = file_size;
+      while (rest > 0)
+	{
+	  char *comment = memchr (runp, '#', rest);
+	  if (comment == NULL)
+	    break;
+
+	  rest -= comment - runp;
+	  do
+	    *comment = ' ';
+	  while (--rest > 0 && *++comment != '\n');
+	}
+
+      /* We have one problematic case: if we have a name at the end of
+	 the file without a trailing terminating characters, we cannot
+	 place the \0.  Handle the case separately.  */
+      if (file[file_size - 1] != ' ' && file[file_size - 1] != '\t'
+	  && file[file_size - 1] != '\n' && file[file_size - 1] != ':')
+	{
+	  problem = &file[file_size];
+	  while (problem > file && problem[-1] != ' '
+		 && problem[-1] != '\t'
+		 && problem[-1] != '\n' && problem[-1] != ':')
+	    --problem;
+
+	  if (problem > file)
+	    problem[-1] = '\0';
+	}
+      else
+	{
+	  problem = NULL;
+	  file[file_size - 1] = '\0';
+	}
+
+      RTLD_TIMING_VAR (start);
+      rtld_timer_start (&start);
+
+      if (file != problem)
+	{
+	  char *p;
+	  runp = file;
+	  while ((p = strsep (&runp, ": \t\n")) != NULL)
+	    if (p[0] != '\0')
+	      npreloads += do_preload (p, main_map, preload_file);
+	}
+
+      if (problem != NULL)
+	{
+	  char *p = strndupa (problem, file_size - (problem - file));
+
+	  npreloads += do_preload (p, main_map, preload_file);
+	}
+
+      rtld_timer_accum (&load_time, start);
+
+      /* We don't need the file anymore.  */
+      __munmap (file, file_size);
+    }
+
+  return npreloads;
+}
+
 /* Called if the audit DSO cannot be used: if it does not have the
    appropriate interfaces, or it expects a more recent version library
    version than what the dynamic linker provides.  */
@@ -1338,6 +1423,20 @@  _dl_start_args_adjust (int skip_args, int skip_env)
 #endif
 }
 
+static int
+conf_filter(const struct dirent *d)
+{
+  int len;
+  if (d == NULL)
+    return 0;
+  len = strlen(d->d_name);
+  if (len < (sizeof(".conf")))
+    return 0;
+  if (strncmp(".conf", &d->d_name[len - 5], 5) != 0)
+    return 0;
+  return 1;
+}
+
 static void
 dl_main (const ElfW(Phdr) *phdr,
 	 ElfW(Word) phnum,
@@ -1345,8 +1444,6 @@  dl_main (const ElfW(Phdr) *phdr,
 	 ElfW(auxv_t) *auxv)
 {
   struct link_map *main_map;
-  size_t file_size;
-  char *file;
   unsigned int i;
   bool rtld_is_main = false;
   void *tcbp = NULL;
@@ -1841,80 +1938,26 @@  dl_main (const ElfW(Phdr) *phdr,
      the work but this does not matter, since it is not for production
      use.  */
   static const char preload_file[] = "/etc/ld.so.preload";
-  if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
-    {
-      /* Read the contents of the file.  */
-      file = _dl_sysdep_read_whole_file (preload_file, &file_size,
-					 PROT_READ | PROT_WRITE);
-      if (__glibc_unlikely (file != MAP_FAILED))
-	{
-	  /* Parse the file.  It contains names of libraries to be loaded,
-	     separated by white spaces or `:'.  It may also contain
-	     comments introduced by `#'.  */
-	  char *problem;
-	  char *runp;
-	  size_t rest;
-
-	  /* Eliminate comments.  */
-	  runp = file;
-	  rest = file_size;
-	  while (rest > 0)
-	    {
-	      char *comment = memchr (runp, '#', rest);
-	      if (comment == NULL)
-		break;
-
-	      rest -= comment - runp;
-	      do
-		*comment = ' ';
-	      while (--rest > 0 && *++comment != '\n');
-	    }
+  static const char preload_dir[] = "/etc/ld.so.preload.d";
 
-	  /* We have one problematic case: if we have a name at the end of
-	     the file without a trailing terminating characters, we cannot
-	     place the \0.  Handle the case separately.  */
-	  if (file[file_size - 1] != ' ' && file[file_size - 1] != '\t'
-	      && file[file_size - 1] != '\n' && file[file_size - 1] != ':')
-	    {
-	      problem = &file[file_size];
-	      while (problem > file && problem[-1] != ' '
-		     && problem[-1] != '\t'
-		     && problem[-1] != '\n' && problem[-1] != ':')
-		--problem;
-
-	      if (problem > file)
-		problem[-1] = '\0';
-	    }
-	  else
-	    {
-	      problem = NULL;
-	      file[file_size - 1] = '\0';
-	    }
-
-	  RTLD_TIMING_VAR (start);
-	  rtld_timer_start (&start);
-
-	  if (file != problem)
-	    {
-	      char *p;
-	      runp = file;
-	      while ((p = strsep (&runp, ": \t\n")) != NULL)
-		if (p[0] != '\0')
-		  npreloads += do_preload (p, main_map, preload_file);
-	    }
+  if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
+      npreloads += handle_preload_file (preload_file, main_map);
 
-	  if (problem != NULL)
-	    {
-	      char *p = strndupa (problem, file_size - (problem - file));
+  if (__glibc_unlikely (__access (preload_dir, R_OK | X_OK) == 0))
+    {
+      struct dirent **conffiles = NULL;
+      int i, n = 0;
 
-	      npreloads += do_preload (p, main_map, preload_file);
-	    }
+      n = scandir (preload_dir, &conffiles, conf_filter, alphasort);
+      if (n < 0)
+	n = 0;
 
-	  rtld_timer_accum (&load_time, start);
+      for (i = 0; i < n; i++) {
+            npreloads += handle_preload_file (conffiles[i]->d_name, main_map);
+	    free(conffiles[i]);
+      }
 
-	  /* We don't need the file anymore.  */
-	  __munmap (file, file_size);
-	}
+      free(conffiles);
     }
 
   if (__glibc_unlikely (*first_preload != NULL))