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

Message ID 20260929115012.95840-1-stlman@poczta.fm (mailing list archive)
State Superseded
Headers
Series 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
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Łukasz Stelmach Sept. 29, 2026, 11:50 a.m. UTC
  From: Łukasz Stelmach <l.stelmach@samsung.com>

Enable reading a list of libraries to preload from files in
/etc/ld.so.preload.d directory. The files 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>
---
 elf/rtld.c | 161 ++++++++++++++++++++++++++++++++---------------------
 1 file changed, 97 insertions(+), 64 deletions(-)
  

Comments

Gabriel Ravier Sept. 29, 2026, 10:53 p.m. UTC | #1
On 9/29/26 1:50 PM, Łukasz Stelmach wrote:
> From: Łukasz Stelmach <l.stelmach@samsung.com>
>
> Enable reading a list of libraries to preload from files in
> /etc/ld.so.preload.d directory. The files 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>
> ---
>   elf/rtld.c | 161 ++++++++++++++++++++++++++++++++---------------------
>   1 file changed, 97 insertions(+), 64 deletions(-)
>
> diff --git a/elf/rtld.c b/elf/rtld.c
> index 324393d606..74387c2663 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>
>   
> @@ -1841,80 +1843,111 @@ 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";
> +  const size_t preload_dir_len = sizeof("/etc/ld.so.preload.d") - 1;
> +  char preload_dir[sizeof("/etc/ld.so.preload.d") + 1 + sizeof(((struct dirent*)0)->d_name)] = "/etc/ld.so.preload.d";
> +  const char *p_file = NULL;
> +  DIR *p_dir = NULL;
> +
>     if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
> +    p_file = preload_file;
> +
> +  if (__glibc_unlikely (__access (preload_dir, R_OK | X_OK) == 0))
> +    p_dir = opendir(preload_dir);
> +
> +  if (__glibc_unlikely (p_file != NULL || p_dir != NULL))
>       {
> -      /* 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;
> +      struct dirent* de = NULL;
> +      do {
> +	if (de != NULL)
> +	  {
> +	    int l = strlen(de->d_name);
> +	    if (__glibc_unlikely ((l < sizeof(".conf") - 1)
> +				  || (strncmp(".conf", &de->d_name[l - 5], 5) != 0)))
> +	      continue;
> +	    preload_dir[preload_dir_len] = '/';
> +	    strncpy(&preload_dir[preload_dir_len + 1], de->d_name, sizeof(preload_dir) - preload_dir_len - 1);
> +	    preload_dir[preload_dir_len + 1 + l] = '\0';
> +	    p_file = preload_dir;
> +	  }
>   
> -	      rest -= comment - runp;
> -	      do
> -		*comment = ' ';
> -	      while (--rest > 0 && *++comment != '\n');
> -	    }
> +        if (p_file == NULL)
> +	  continue;
>   
> -	  /* 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';
> -	    }
> +	/* Read the contents of the file.  */
> +	file = _dl_sysdep_read_whole_file (p_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');
> +	      }
>   
> -	  RTLD_TIMING_VAR (start);
> -	  rtld_timer_start (&start);
> +	    /* 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';
> +	      }
>   
> -	  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);
> -	    }
> +	    RTLD_TIMING_VAR (start);
> +	    rtld_timer_start (&start);
>   
> -	  if (problem != NULL)
> -	    {
> -	      char *p = strndupa (problem, file_size - (problem - file));
> +	    if (file != problem)
> +	      {
> +		char *p;
> +		runp = file;
> +		while ((p = strsep (&runp, ": \t\n")) != NULL)
> +		  if (p[0] != '\0')
> +		    npreloads += do_preload (p, main_map, p_file);
> +	      }
>   
> -	      npreloads += do_preload (p, main_map, preload_file);
> -	    }
> +	    if (problem != NULL)
> +	      {
> +		char *p = strndupa (problem, file_size - (problem - file));
>   
> -	  rtld_timer_accum (&load_time, start);
> +		npreloads += do_preload (p, main_map, p_file);
> +	      }
>   
> -	  /* We don't need the file anymore.  */
> -	  __munmap (file, file_size);
> -	}
> +	    rtld_timer_accum (&load_time, start);
> +
> +	    /* We don't need the file anymore.  */
> +	    __munmap (file, file_size);
> +	  }
> +      } while (p_dir && (de = readdir(p_dir)));
> +      if (p_dir != NULL)
> +	closedir(p_dir);
>       }
>   
>     if (__glibc_unlikely (*first_preload != NULL))


...How does the ordering work? If it's just readdir, isn't that 
effectively unspecified? AIUI all the other .d directory conventions in 
most tools sort entries, which would matter here for e.g. symbol 
interposition order, I'd think...
  
Łukasz Stelmach Sept. 30, 2026, 8:24 a.m. UTC | #2
It was 2026-09-30 śro 00:53, when Gabriel Ravier wrote:
> On 9/29/26 1:50 PM, Łukasz Stelmach wrote:
>> From: Łukasz Stelmach <l.stelmach@samsung.com>
>>
>> Enable reading a list of libraries to preload from files in
>> /etc/ld.so.preload.d directory. The files 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>
>> ---
>>   elf/rtld.c | 161 ++++++++++++++++++++++++++++++++---------------------
>>   1 file changed, 97 insertions(+), 64 deletions(-)
>>
>> diff --git a/elf/rtld.c b/elf/rtld.c
>> index 324393d606..74387c2663 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>
>>   @@ -1841,80 +1843,111 @@ 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";
>> +  const size_t preload_dir_len = sizeof("/etc/ld.so.preload.d") - 1;
>> +  char preload_dir[sizeof("/etc/ld.so.preload.d") + 1 + sizeof(((struct dirent*)0)->d_name)] = "/etc/ld.so.preload.d";
>> +  const char *p_file = NULL;
>> +  DIR *p_dir = NULL;
>> +
>>     if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
>> +    p_file = preload_file;
>> +
>> +  if (__glibc_unlikely (__access (preload_dir, R_OK | X_OK) == 0))
>> +    p_dir = opendir(preload_dir);
>> +
>> +  if (__glibc_unlikely (p_file != NULL || p_dir != NULL))
>>       {
>> -      /* 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;
>> +      struct dirent* de = NULL;
>> +      do {
>> +	if (de != NULL)

[...]


>> +	    /* We don't need the file anymore.  */
>> +	    __munmap (file, file_size);
>> +	  }
>> +      } while (p_dir && (de = readdir(p_dir)));
>> +      if (p_dir != NULL)
>> +	closedir(p_dir);
>>       }
>>       if (__glibc_unlikely (*first_preload != NULL))
>
>
> ...How does the ordering work? If it's just readdir, isn't that
> effectively unspecified? AIUI all the other .d directory conventions
> in most tools sort entries, which would matter here for e.g. symbol
> interposition order, I'd think...

Indeed there is no ordering, yet. TODO. Thanks.
  

Patch

diff --git a/elf/rtld.c b/elf/rtld.c
index 324393d606..74387c2663 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>
 
@@ -1841,80 +1843,111 @@  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";
+  const size_t preload_dir_len = sizeof("/etc/ld.so.preload.d") - 1;
+  char preload_dir[sizeof("/etc/ld.so.preload.d") + 1 + sizeof(((struct dirent*)0)->d_name)] = "/etc/ld.so.preload.d";
+  const char *p_file = NULL;
+  DIR *p_dir = NULL;
+
   if (__glibc_unlikely (__access (preload_file, R_OK) == 0))
+    p_file = preload_file;
+
+  if (__glibc_unlikely (__access (preload_dir, R_OK | X_OK) == 0))
+    p_dir = opendir(preload_dir);
+
+  if (__glibc_unlikely (p_file != NULL || p_dir != NULL))
     {
-      /* 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;
+      struct dirent* de = NULL;
+      do {
+	if (de != NULL)
+	  {
+	    int l = strlen(de->d_name);
+	    if (__glibc_unlikely ((l < sizeof(".conf") - 1)
+				  || (strncmp(".conf", &de->d_name[l - 5], 5) != 0)))
+	      continue;
+	    preload_dir[preload_dir_len] = '/';
+	    strncpy(&preload_dir[preload_dir_len + 1], de->d_name, sizeof(preload_dir) - preload_dir_len - 1);
+	    preload_dir[preload_dir_len + 1 + l] = '\0';
+	    p_file = preload_dir;
+	  }
 
-	      rest -= comment - runp;
-	      do
-		*comment = ' ';
-	      while (--rest > 0 && *++comment != '\n');
-	    }
+        if (p_file == NULL)
+	  continue;
 
-	  /* 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';
-	    }
+	/* Read the contents of the file.  */
+	file = _dl_sysdep_read_whole_file (p_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');
+	      }
 
-	  RTLD_TIMING_VAR (start);
-	  rtld_timer_start (&start);
+	    /* 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';
+	      }
 
-	  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);
-	    }
+	    RTLD_TIMING_VAR (start);
+	    rtld_timer_start (&start);
 
-	  if (problem != NULL)
-	    {
-	      char *p = strndupa (problem, file_size - (problem - file));
+	    if (file != problem)
+	      {
+		char *p;
+		runp = file;
+		while ((p = strsep (&runp, ": \t\n")) != NULL)
+		  if (p[0] != '\0')
+		    npreloads += do_preload (p, main_map, p_file);
+	      }
 
-	      npreloads += do_preload (p, main_map, preload_file);
-	    }
+	    if (problem != NULL)
+	      {
+		char *p = strndupa (problem, file_size - (problem - file));
 
-	  rtld_timer_accum (&load_time, start);
+		npreloads += do_preload (p, main_map, p_file);
+	      }
 
-	  /* We don't need the file anymore.  */
-	  __munmap (file, file_size);
-	}
+	    rtld_timer_accum (&load_time, start);
+
+	    /* We don't need the file anymore.  */
+	    __munmap (file, file_size);
+	  }
+      } while (p_dir && (de = readdir(p_dir)));
+      if (p_dir != NULL)
+	closedir(p_dir);
     }
 
   if (__glibc_unlikely (*first_preload != NULL))