Skip to content

Commit 33ce626

Browse files
committed
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 5790adc commit 33ce626

File tree

2 files changed

+68
-29
lines changed

2 files changed

+68
-29
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const (
4646
// ld.so.conf file, but some may not. And some container images may not have a top-level
4747
// ld.so.conf file at all.
4848
defaultLdsoconfdDir = "/etc/ld.so.conf.d"
49+
// ldsoconfdSystemDirsFilenamePattern specifies the filename pattern for the drop-in conf file
50+
// that includes the expected system directories for the container.
51+
ldsoconfdSystemDirsFilenamePattern = "zz-nvcr-*.conf"
4952
)
5053

5154
type Ldconfig struct {
@@ -131,7 +134,7 @@ func (l *Ldconfig) UpdateLDCache() error {
131134

132135
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
133136
// be configured to use a different config file by default.
134-
filteredDirectories, ldconfigDirs, err := l.filterDirectories(defaultTopLevelLdsoconfFilePath, l.directories...)
137+
filteredDirectories, err := l.filterDirectories(defaultTopLevelLdsoconfFilePath, l.directories...)
135138
if err != nil {
136139
return err
137140
}
@@ -141,25 +144,23 @@ func (l *Ldconfig) UpdateLDCache() error {
141144
"-f", defaultTopLevelLdsoconfFilePath,
142145
"-C", "/etc/ld.so.cache",
143146
}
144-
// If we are running in a non-debian container on a debian host we also
145-
// need to add the system directories for non-debian hosts to the list of
146-
// folders processed by ldconfig.
147-
// We only do this if they are not already tracked, since the folders on
148-
// on the command line have a higher priority than folders in ld.so.conf.
149-
if l.isDebianLikeHost && !l.isDebianLikeContainer {
150-
for _, systemSearchPath := range l.getSystemSearchPaths() {
151-
if _, ok := ldconfigDirs[systemSearchPath]; ok {
152-
continue
153-
}
154-
args = append(args, "/lib64", "/usr/lib64")
155-
}
156-
}
157147

158148
if err := ensureLdsoconfFile(defaultTopLevelLdsoconfFilePath, defaultLdsoconfdDir); err != nil {
159149
return fmt.Errorf("failed to ensure ld.so.conf file: %w", err)
160150
}
161151
if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
162-
return fmt.Errorf("failed to create ld.so.conf.d drop-in file: %w", err)
152+
return fmt.Errorf("failed to write %s drop-in: %w", ldsoconfdFilenamePattern, err)
153+
}
154+
155+
// In most cases, the hook will be executing a host ldconfig that may be configured widely
156+
// differently from what the container image expects. The common case is Debian vs non-Debian.
157+
// But there are also hosts that configure ldconfig to search in a glibc prefix
158+
// (e.g. /usr/lib/glibc). To avoid all these cases, write the container's expected system search
159+
// paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the
160+
// top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify
161+
// the top-level file if it exists.
162+
if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdSystemDirsFilenamePattern, l.getSystemSearchPaths()...); err != nil {
163+
return fmt.Errorf("failed to write %s drop-in: %w", ldsoconfdSystemDirsFilenamePattern, err)
163164
}
164165

165166
return SafeExec(ldconfigPath, args, nil)
@@ -194,10 +195,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
194195
return ldconfigPath, nil
195196
}
196197

197-
func (l *Ldconfig) filterDirectories(configFilePath string, directories ...string) ([]string, map[string]struct{}, error) {
198+
func (l *Ldconfig) filterDirectories(configFilePath string, directories ...string) ([]string, error) {
198199
ldconfigDirs, err := l.getLdsoconfDirectories(configFilePath)
199200
if err != nil {
200-
return nil, nil, err
201+
return nil, err
201202
}
202203

203204
var filtered []string
@@ -208,7 +209,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
208209
filtered = append(filtered, d)
209210
ldconfigDirs[d] = struct{}{}
210211
}
211-
return filtered, ldconfigDirs, nil
212+
return filtered, nil
212213
}
213214

214215
// createLdsoconfdFile creates a ld.so.conf.d drop-in file with the specified directories on each
@@ -350,22 +351,61 @@ func isDebian() bool {
350351
return !info.IsDir()
351352
}
352353

353-
// nonDebianSystemSearchPaths returns the system search paths for non-Debian
354-
// systems.
354+
// nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
355+
//
356+
// glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
357+
// aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
358+
// with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
359+
// resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
360+
// x86_64 only).
361+
//
362+
// By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
363+
// "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
364+
// symlinks and skips duplicate directory entries.
355365
//
356-
// This list was taken from the output of:
366+
// To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
367+
// look for "path.system_dirs". For example
368+
// `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
357369
//
358-
// docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
370+
// On most distributions, including Fedora and derivatives, this yields the following
371+
// ldconfig system search paths.
372+
//
373+
// TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
374+
// TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
359375
func nonDebianSystemSearchPaths() []string {
360-
return []string{"/lib64", "/usr/lib64"}
376+
var paths []string
377+
paths = append(paths, "/lib", "/usr/lib")
378+
switch runtime.GOARCH {
379+
case "amd64":
380+
paths = append(paths,
381+
"/lib/lib64",
382+
"/usr/lib64",
383+
"/libx32",
384+
"/usr/libx32",
385+
)
386+
case "arm64":
387+
paths = append(paths,
388+
"/lib/lib64",
389+
"/usr/lib64",
390+
)
391+
}
392+
return paths
361393
}
362394

363-
// debianSystemSearchPaths returns the system search paths for Debian-like
364-
// systems.
395+
// debianSystemSearchPaths returns the system search paths for Debian-like systems.
365396
//
366-
// This list was taken from the output of:
397+
// Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
398+
// use the same set of system paths as the dynamic linker. These paths are going to include the
399+
// multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
367400
//
368-
// docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
401+
// To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
402+
// look for "path.system_dirs". For example
403+
// `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
404+
//
405+
// This yields the following ldconfig system search paths.
406+
//
407+
// TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
408+
// TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
369409
func debianSystemSearchPaths() []string {
370410
var paths []string
371411
switch runtime.GOARCH {
@@ -381,6 +421,5 @@ func debianSystemSearchPaths() []string {
381421
)
382422
}
383423
paths = append(paths, "/lib", "/usr/lib")
384-
385424
return paths
386425
}

internal/ldconfig/ldconfig_test.go

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

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

0 commit comments

Comments
 (0)