Skip to content

Commit 7f73f39

Browse files
jfroyelezar
authored andcommitted
Write system paths to lexicographically last ld.so.conf.d drop-in
In most cases, the hook will be executing a host ldconfig that may be configured widely differently from what the container image expects. The common case is Debian vs non-Debian. But there are also hosts that configure ldconfig to search in a glibc prefix (e.g. /usr/lib/glibc). To avoid all these cases, write the container's expected system search paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify the top-level file if it exists. Signed-off-by: Jean-Francois Roy <[email protected]>
1 parent 4e4381f commit 7f73f39

File tree

2 files changed

+73
-29
lines changed

2 files changed

+73
-29
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ const (
3737
// higher precedence than other libraries on the system, but lower than
3838
// the 00-cuda-compat that is included in some containers.
3939
ldsoconfdFilenamePattern = "00-nvcr-*.conf"
40+
// ldsoconfdSystemDirsFilenamePattern specifies the filename pattern for the drop-in conf file
41+
// that includes the expected system directories for the container.
42+
// This is chosen to have a high likelihood of being lexographically last in
43+
// in the list of config files, since system search paths should be
44+
// considered last.
45+
ldsoconfdSystemDirsFilenamePattern = "zz-nvcr-*.conf"
4046
)
4147

4248
type Ldconfig struct {
@@ -123,7 +129,7 @@ func (l *Ldconfig) UpdateLDCache() error {
123129
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
124130
// be configured to use a different config file by default.
125131
const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
126-
filteredDirectories, ldconfigDirs, err := l.filterDirectories(topLevelLdsoconfFilePath, l.directories...)
132+
filteredDirectories, err := l.filterDirectories(topLevelLdsoconfFilePath, l.directories...)
127133
if err != nil {
128134
return err
129135
}
@@ -133,22 +139,22 @@ func (l *Ldconfig) UpdateLDCache() error {
133139
"-f", topLevelLdsoconfFilePath,
134140
"-C", "/etc/ld.so.cache",
135141
}
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-
}
149142

150143
if err := createLdsoconfdFile(ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
151-
return fmt.Errorf("failed to update ld.so.conf.d: %w", err)
144+
return fmt.Errorf("failed to write %s drop-in: %w", ldsoconfdFilenamePattern, err)
145+
}
146+
147+
if l.isDebianLikeHost != l.isDebianLikeContainer {
148+
// In most cases, the hook will be executing a host ldconfig that may be configured widely
149+
// differently from what the container image expects. The common case is Debian vs non-Debian.
150+
// But there are also hosts that configure ldconfig to search in a glibc prefix
151+
// (e.g. /usr/lib/glibc). To avoid all these cases, write the container's expected system search
152+
// paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the
153+
// top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify
154+
// the top-level file if it exists.
155+
if err := createLdsoconfdFile(ldsoconfdSystemDirsFilenamePattern, l.getSystemSearchPaths()...); err != nil {
156+
return fmt.Errorf("failed to write %s drop-in: %w", ldsoconfdSystemDirsFilenamePattern, err)
157+
}
152158
}
153159

154160
return SafeExec(ldconfigPath, args, nil)
@@ -183,10 +189,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
183189
return ldconfigPath, nil
184190
}
185191

186-
func (l *Ldconfig) filterDirectories(configFilePath string, directories ...string) ([]string, map[string]struct{}, error) {
192+
func (l *Ldconfig) filterDirectories(configFilePath string, directories ...string) ([]string, error) {
187193
ldconfigDirs, err := l.getLdsoconfDirectories(configFilePath)
188194
if err != nil {
189-
return nil, nil, err
195+
return nil, err
190196
}
191197

192198
var filtered []string
@@ -197,7 +203,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
197203
filtered = append(filtered, d)
198204
ldconfigDirs[d] = struct{}{}
199205
}
200-
return filtered, ldconfigDirs, nil
206+
return filtered, nil
201207
}
202208

203209
// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
@@ -324,22 +330,61 @@ func isDebian() bool {
324330
return !info.IsDir()
325331
}
326332

327-
// nonDebianSystemSearchPaths returns the system search paths for non-Debian
328-
// systems.
333+
// nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
334+
//
335+
// glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
336+
// aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
337+
// with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
338+
// resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
339+
// x86_64 only).
329340
//
330-
// This list was taken from the output of:
341+
// By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
342+
// "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
343+
// symlinks and skips duplicate directory entries.
331344
//
332-
// docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
345+
// To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
346+
// look for "path.system_dirs". For example
347+
// `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
348+
//
349+
// On most distributions, including Fedora and derivatives, this yields the following
350+
// ldconfig system search paths.
351+
//
352+
// TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
353+
// TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
333354
func nonDebianSystemSearchPaths() []string {
334-
return []string{"/lib64", "/usr/lib64"}
355+
var paths []string
356+
paths = append(paths, "/lib", "/usr/lib")
357+
switch runtime.GOARCH {
358+
case "amd64":
359+
paths = append(paths,
360+
"/lib/lib64",
361+
"/usr/lib64",
362+
"/libx32",
363+
"/usr/libx32",
364+
)
365+
case "arm64":
366+
paths = append(paths,
367+
"/lib/lib64",
368+
"/usr/lib64",
369+
)
370+
}
371+
return paths
335372
}
336373

337-
// debianSystemSearchPaths returns the system search paths for Debian-like
338-
// systems.
374+
// debianSystemSearchPaths returns the system search paths for Debian-like systems.
339375
//
340-
// This list was taken from the output of:
376+
// Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
377+
// use the same set of system paths as the dynamic linker. These paths are going to include the
378+
// multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
341379
//
342-
// docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
380+
// To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
381+
// look for "path.system_dirs". For example
382+
// `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
383+
//
384+
// This yields the following ldconfig system search paths.
385+
//
386+
// TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
387+
// TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
343388
func debianSystemSearchPaths() []string {
344389
var paths []string
345390
switch runtime.GOARCH {
@@ -355,6 +400,5 @@ func debianSystemSearchPaths() []string {
355400
)
356401
}
357402
paths = append(paths, "/lib", "/usr/lib")
358-
359403
return paths
360404
}

internal/ldconfig/ldconfig_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ include INCLUDED_PATTERN*
117117
l := &Ldconfig{
118118
isDebianLikeContainer: true,
119119
}
120-
filtered, _, err := l.filterDirectories(topLevelConfPath, tc.input...)
120+
filtered, err := l.filterDirectories(topLevelConfPath, tc.input...)
121121

122122
require.NoError(t, err)
123123
require.Equal(t, tc.expected, filtered)

0 commit comments

Comments
 (0)