@@ -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 , systemSearchPath )
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
@@ -183,10 +180,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
183180 return ldconfigPath , nil
184181}
185182
186- func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , map [ string ] struct {}, error ) {
183+ func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , error ) {
187184 ldconfigDirs , err := l .getLdsoconfDirectories (configFilePath )
188185 if err != nil {
189- return nil , nil , err
186+ return nil , err
190187 }
191188
192189 var filtered []string
@@ -197,7 +194,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
197194 filtered = append (filtered , d )
198195 ldconfigDirs [d ] = struct {}{}
199196 }
200- return filtered , ldconfigDirs , nil
197+ return filtered , nil
201198}
202199
203200// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
@@ -241,6 +238,24 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
241238 return nil
242239}
243240
241+ func appendSystemSearchPathsToLdsoconf (configFilePath string , dirs ... string ) error {
242+ if len (dirs ) == 0 {
243+ return nil
244+ }
245+ configFile , err := os .OpenFile (configFilePath , os .O_APPEND | os .O_WRONLY , 0644 )
246+ if err != nil {
247+ return fmt .Errorf ("failed to open config file: %w" , err )
248+ }
249+ defer configFile .Close ()
250+ for _ , dir := range dirs {
251+ _ , err = fmt .Fprintf (configFile , "%s\n " , dir )
252+ if err != nil {
253+ return fmt .Errorf ("failed to update config file: %w" , err )
254+ }
255+ }
256+ return nil
257+ }
258+
244259// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
245260// files that refer to the directory.
246261func (l * Ldconfig ) getLdsoconfDirectories (configFilePath string ) (map [string ]struct {}, error ) {
@@ -324,22 +339,61 @@ func isDebian() bool {
324339 return ! info .IsDir ()
325340}
326341
327- // nonDebianSystemSearchPaths returns the system search paths for non-Debian
328- // systems.
342+ // nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
343+ //
344+ // glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
345+ // aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
346+ // with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
347+ // resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
348+ // x86_64 only).
349+ //
350+ // By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
351+ // "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
352+ // symlinks and skips duplicate directory entries.
353+ //
354+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
355+ // look for "path.system_dirs". For example
356+ // `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
329357//
330- // This list was taken from the output of:
358+ // On most distributions, including Fedora and derivatives, this yields the following
359+ // ldconfig system search paths.
331360//
332- // docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
361+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
362+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
333363func nonDebianSystemSearchPaths () []string {
334- return []string {"/lib64" , "/usr/lib64" }
364+ var paths []string
365+ paths = append (paths , "/lib" , "/usr/lib" )
366+ switch runtime .GOARCH {
367+ case "amd64" :
368+ paths = append (paths ,
369+ "/lib/lib64" ,
370+ "/usr/lib64" ,
371+ "/libx32" ,
372+ "/usr/libx32" ,
373+ )
374+ case "arm64" :
375+ paths = append (paths ,
376+ "/lib/lib64" ,
377+ "/usr/lib64" ,
378+ )
379+ }
380+ return paths
335381}
336382
337- // debianSystemSearchPaths returns the system search paths for Debian-like
338- // systems.
383+ // debianSystemSearchPaths returns the system search paths for Debian-like systems.
384+ //
385+ // Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
386+ // use the same set of system paths as the dynamic linker. These paths are going to include the
387+ // multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
339388//
340- // This list was taken from the output of:
389+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
390+ // look for "path.system_dirs". For example
391+ // `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
341392//
342- // docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
393+ // This yields the following ldconfig system search paths.
394+ //
395+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
396+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
343397func debianSystemSearchPaths () []string {
344398 var paths []string
345399 switch runtime .GOARCH {
@@ -355,6 +409,5 @@ func debianSystemSearchPaths() []string {
355409 )
356410 }
357411 paths = append (paths , "/lib" , "/usr/lib" )
358-
359412 return paths
360413}
0 commit comments