[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
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
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))
@@ -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))