@@ -123,7 +123,7 @@ func (l *Ldconfig) UpdateLDCache() error {
123123 // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
124124 // be configured to use a different config file by default.
125125 const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
126- filteredDirectories , ldconfigDirs , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
126+ filteredDirectories , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
127127 if err != nil {
128128 return err
129129 }
@@ -133,24 +133,21 @@ func (l *Ldconfig) UpdateLDCache() error {
133133 "-f" , topLevelLdsoconfFilePath ,
134134 "-C" , "/etc/ld.so.cache" ,
135135 }
136- // If we are running in a non-debian container on a debian host we also
137- // need to add the system directories for non-debian hosts to the list of
138- // folders processed by ldconfig.
139- // We only do this if they are not already tracked, since the folders on
140- // on the command line have a higher priority than folders in ld.so.conf.
141- if l .isDebianLikeHost && ! l .isDebianLikeContainer {
142- for _ , systemSearchPath := range l .getSystemSearchPaths () {
143- if _ , ok := ldconfigDirs [systemSearchPath ]; ok {
144- continue
145- }
146- args = append (args , "/lib64" , "/usr/lib64" )
147- }
148- }
149136
150137 if err := createLdsoconfdFile (ldsoconfdFilenamePattern , filteredDirectories ... ); err != nil {
151138 return fmt .Errorf ("failed to update ld.so.conf.d: %w" , err )
152139 }
153140
141+ // In most cases, the hook will be executing a host ldconfig that may be configured widely
142+ // differently from what the container image expects. The common case is Debian vs non-Debian.
143+ // But there are also hosts that configure ldconfig to search in a glibc prefix
144+ // (e.g. /usr/lib/glibc). To avoid all these cases, append the container's expected system
145+ // search paths to the top-level ld.so.conf. This will ensure they get scanned but won't
146+ // materially change the scan order.
147+ if err := appendSystemSearchPathsToLdsoconf (topLevelLdsoconfFilePath , l .getSystemSearchPaths ()... ); err != nil {
148+ return fmt .Errorf ("failed to append system search paths to %s: %w" , topLevelLdsoconfFilePath , err )
149+ }
150+
154151 return SafeExec (ldconfigPath , args , nil )
155152}
156153
@@ -177,10 +174,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
177174 return ldconfigPath , nil
178175}
179176
180- func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , map [ string ] struct {}, error ) {
177+ func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , error ) {
181178 ldconfigDirs , err := l .getLdsoconfDirectories (configFilePath )
182179 if err != nil {
183- return nil , nil , err
180+ return nil , err
184181 }
185182
186183 var filtered []string
@@ -191,7 +188,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
191188 filtered = append (filtered , d )
192189 ldconfigDirs [d ] = struct {}{}
193190 }
194- return filtered , ldconfigDirs , nil
191+ return filtered , nil
195192}
196193
197194// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
@@ -235,6 +232,24 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
235232 return nil
236233}
237234
235+ func appendSystemSearchPathsToLdsoconf (configFilePath string , dirs ... string ) error {
236+ if len (dirs ) == 0 {
237+ return nil
238+ }
239+ configFile , err := os .OpenFile (configFilePath , os .O_APPEND | os .O_WRONLY , 0644 )
240+ if err != nil {
241+ return fmt .Errorf ("failed to open config file: %w" , err )
242+ }
243+ defer configFile .Close ()
244+ for _ , dir := range dirs {
245+ _ , err = fmt .Fprintf (configFile , "%s\n " , dir )
246+ if err != nil {
247+ return fmt .Errorf ("failed to update config file: %w" , err )
248+ }
249+ }
250+ return nil
251+ }
252+
238253// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
239254// files that refer to the directory.
240255func (l * Ldconfig ) getLdsoconfDirectories (configFilePath string ) (map [string ]struct {}, error ) {
@@ -318,22 +333,61 @@ func isDebian() bool {
318333 return ! info .IsDir ()
319334}
320335
321- // nonDebianSystemSearchPaths returns the system search paths for non-Debian
322- // systems.
336+ // nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
337+ //
338+ // glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
339+ // aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
340+ // with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
341+ // resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
342+ // x86_64 only).
343+ //
344+ // By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
345+ // "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
346+ // symlinks and skips duplicate directory entries.
347+ //
348+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
349+ // look for "path.system_dirs". For example
350+ // `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
323351//
324- // This list was taken from the output of:
352+ // On most distributions, including Fedora and derivatives, this yields the following
353+ // ldconfig system search paths.
325354//
326- // docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
355+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
356+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
327357func nonDebianSystemSearchPaths () []string {
328- return []string {"/lib64" , "/usr/lib64" }
358+ var paths []string
359+ paths = append (paths , "/lib" , "/usr/lib" )
360+ switch runtime .GOARCH {
361+ case "amd64" :
362+ paths = append (paths ,
363+ "/lib/lib64" ,
364+ "/usr/lib64" ,
365+ "/libx32" ,
366+ "/usr/libx32" ,
367+ )
368+ case "arm64" :
369+ paths = append (paths ,
370+ "/lib/lib64" ,
371+ "/usr/lib64" ,
372+ )
373+ }
374+ return paths
329375}
330376
331- // debianSystemSearchPaths returns the system search paths for Debian-like
332- // systems.
377+ // debianSystemSearchPaths returns the system search paths for Debian-like systems.
378+ //
379+ // Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
380+ // use the same set of system paths as the dynamic linker. These paths are going to include the
381+ // multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
333382//
334- // This list was taken from the output of:
383+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
384+ // look for "path.system_dirs". For example
385+ // `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
335386//
336- // docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
387+ // This yields the following ldconfig system search paths.
388+ //
389+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
390+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
337391func debianSystemSearchPaths () []string {
338392 var paths []string
339393 switch runtime .GOARCH {
@@ -349,6 +403,5 @@ func debianSystemSearchPaths() []string {
349403 )
350404 }
351405 paths = append (paths , "/lib" , "/usr/lib" )
352-
353406 return paths
354407}
0 commit comments