Repository: chainguard-dev/melange
Pull request #1850: pkg/sca/sca.go: Implement `getLdSoConfDLibPaths`
Author: login: sergiodj
Created: 2025-03-10T21:34:26Z | Merged: 2025-03-17T21:56:44Z

This PR implements the machinery necessary for SCA to consider files under `/etc/ld.so.conf.d/`.

The idea is that, if there is a configuration for `ld.so.conf.d` being installed by the package on hand, then the libraries shipped by the package should be listed as `provides` if they're installed inside the paths listed in the `ld.so.conf.d` configuration file.

This commit also implements a simple test to make sure that the new function is properly parsing the configuration file inside `/etc/ld.so.conf.d/`.

Notes:

### SCA Changes

- [X] Examining several representative APKs show no regression / the desired effect (details in notes)

Notes:

As of this writing no packages are actually using `ld.so.conf.d` on Wolfi OS.  I built `/nvidia-cudnn-9` (from extra-packages) and it works as expected:

```
2025/03/10 17:31:12 INFO scanning for ld.so.conf.d files...
2025/03/10 17:31:12 INFO   found ld.so.conf.d file etc/ld.so.conf.d/nvidia-cudnn-9.conf
2025/03/10 17:31:12 INFO     found extra lib path /usr/local/cudnn-9/lib64
```

Relates: https://github.com/chainguard-dev/internal-dev/issues/7609


Diff:
diff --git a/pkg/sca/sca.go b/pkg/sca/sca.go
index c6e722821..1d423c75e 100644
--- a/pkg/sca/sca.go
+++ b/pkg/sca/sca.go
@@ -15,10 +15,12 @@
 package sca
 
 import (
+	"bufio"
 	"bytes"
 	"context"
 	"debug/buildinfo"
 	"debug/elf"
+	"errors"
 	"fmt"
 	"io"
 	"io/fs"
@@ -72,9 +74,10 @@ type SCAHandle interface {
 	BaseDependencies() config.Dependencies
 }
 
-// DependencyGenerator takes an SCAHandle and config.Dependencies pointer and returns
-// findings based on analysis.
-type DependencyGenerator func(context.Context, SCAHandle, *config.Dependencies) error
+// DependencyGenerator takes an SCAHandle, config.Dependencies pointer
+// and a list of paths to be appended to libDirs and returns findings
+// based on analysis.
+type DependencyGenerator func(context.Context, SCAHandle, *config.Dependencies, []string) error
 
 func isInDir(path string, dirs []string) bool {
 	mydir := filepath.Dir(path)
@@ -86,7 +89,83 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
-func generateCmdProviders(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package and all its subpackages, and for each configuration
+// file found under /etc/ld.so.conf.d/ it will parse the file and add
+// its contents to a string vector.  This vector will ultimately
+// contain all extra paths that will be considered by ld when doing
+// symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+	targetPackageNames := hdl.RelativeNames()
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+
+	for _, pkgName := range targetPackageNames {
+		fsys, err := hdl.FilesystemForRelative(pkgName)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := fs.WalkDir(fsys, "etc/ld.so.conf.d", func(path string, d fs.DirEntry, err error) error {
+			if err != nil {
+				if errors.Is(err, fs.ErrNotExist) {
+					return fs.SkipAll
+				}
+				return err
+			}
+
+			// We're only interested in files whose suffix is ".conf"
+			if !strings.HasSuffix(path, ".conf") {
+				return nil
+			}
+
+			fi, err := d.Info()
+			if err != nil {
+				return err
+			}
+
+			if !fi.Mode().IsRegular() {
+				return nil
+			}
+
+			log.Infof("  found ld.so.conf.d file %s", path)
+
+			fd, err := fsys.Open(path)
+			if err != nil {
+				return err
+			}
+			defer fd.Close()
+
+			scanner := bufio.NewScanner(fd)
+			for scanner.Scan() {
+				line := scanner.Text()
+				if line == "" || line[0] != '/' {
+					continue
+				}
+				// Strip the initial slash since
+				// libDirs paths need to be relative.
+				line = line[1:]
+				log.Infof("    found extra lib path %s", line)
+				extraLibPaths = append(extraLibPaths, line)
+			}
+
+			if err := scanner.Err(); err != nil {
+				return err
+			}
+
+			return nil
+		}); err != nil {
+			return nil, err
+		}
+	}
+
+	return extraLibPaths, nil
+}
+
+func generateCmdProviders(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 
 	log.Info("scanning for commands...")
@@ -149,7 +228,7 @@ func findInterpreter(bin *elf.File) (string, error) {
 
 // dereferenceCrossPackageSymlink attempts to dereference a symlink across multiple package
 // directories.
-func dereferenceCrossPackageSymlink(hdl SCAHandle, path string) (string, string, error) {
+func dereferenceCrossPackageSymlink(hdl SCAHandle, path string, extraLibDirs []string) (string, string, error) {
 	targetPackageNames := hdl.RelativeNames()
 
 	pkgFS, err := hdl.Filesystem()
@@ -164,13 +243,15 @@ func dereferenceCrossPackageSymlink(hdl SCAHandle, path string) (string, string,
 
 	realPath = filepath.Base(realPath)
 
+	expandedLibDirs := append(libDirs, extraLibDirs...)
+
 	for _, pkgName := range targetPackageNames {
 		baseFS, err := hdl.FilesystemForRelative(pkgName)
 		if err != nil {
 			return "", "", err
 		}
 
-		for _, libDir := range libDirs {
+		for _, libDir := range expandedLibDirs {
 			testPath := filepath.Join(libDir, realPath)
 
 			if _, err := baseFS.Stat(testPath); err == nil {
@@ -182,13 +263,13 @@ func dereferenceCrossPackageSymlink(hdl SCAHandle, path string) (string, string,
 	return "", "", nil
 }
 
-func processSymlinkSo(ctx context.Context, hdl SCAHandle, path string, generated *config.Dependencies) error {
+func processSymlinkSo(ctx context.Context, hdl SCAHandle, path string, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	if !strings.Contains(path, ".so") {
 		return nil
 	}
 
-	targetPkg, realPath, err := dereferenceCrossPackageSymlink(hdl, path)
+	targetPkg, realPath, err := dereferenceCrossPackageSymlink(hdl, path, extraLibDirs)
 	if err != nil {
 		return nil
 	}
@@ -233,7 +314,7 @@ func processSymlinkSo(ctx context.Context, hdl SCAHandle, path string, generated
 	return nil
 }
 
-func generateSharedObjectNameDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+func generateSharedObjectNameDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	log.Infof("scanning for shared object dependencies...")
 
@@ -242,6 +323,8 @@ func generateSharedObjectNameDeps(ctx context.Context, hdl SCAHandle, generated
 		return err
 	}
 
+	expandedLibDirs := append(libDirs, extraLibDirs...)
+
 	if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
 		if err != nil {
 			return err
@@ -258,7 +341,7 @@ func generateSharedObjectNameDeps(ctx context.Context, hdl SCAHandle, generated
 		isLink := mode.Type()&fs.ModeSymlink == fs.ModeSymlink
 
 		if isLink {
-			if err := processSymlinkSo(ctx, hdl, path, generated); err != nil {
+			if err := processSymlinkSo(ctx, hdl, path, generated, extraLibDirs); err != nil {
 				return err
 			}
 		}
@@ -349,7 +432,7 @@ func generateSharedObjectNameDeps(ctx context.Context, hdl SCAHandle, generated
 			for _, soname := range sonames {
 				libver := sonameLibver(soname)
 
-				if isInDir(path, libDirs) {
+				if isInDir(path, expandedLibDirs) {
 					generated.Provides = append(generated.Provides, fmt.Sprintf("so:%s=%s", soname, libver))
 				} else {
 					generated.Vendored = append(generated.Vendored, fmt.Sprintf("so:%s=%s", soname, libver))
@@ -395,7 +478,7 @@ var generateRuntimePkgConfigDeps = true
 
 // generatePkgConfigDeps generates a list of provided pkg-config package names and versions,
 // as well as dependency relationships.
-func generatePkgConfigDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+func generatePkgConfigDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	log.Infof("scanning for pkg-config data...")
 
@@ -485,7 +568,7 @@ func generatePkgConfigDeps(ctx context.Context, hdl SCAHandle, generated *config
 
 // generatePythonDeps generates a python-3.X-base dependency for packages which ship
 // Python modules.
-func generatePythonDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+func generatePythonDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	log.Infof("scanning for python modules...")
 
@@ -549,7 +632,7 @@ func generatePythonDeps(ctx context.Context, hdl SCAHandle, generated *config.De
 
 // generateRubyDeps generates a ruby-X.Y dependency for packages which ship
 // Ruby gems.
-func generateRubyDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+func generateRubyDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	log.Infof("scanning for ruby gems...")
 
@@ -596,7 +679,7 @@ func generateRubyDeps(ctx context.Context, hdl SCAHandle, generated *config.Depe
 }
 
 // For a documentation package add a dependency on man-db and / or texinfo as appropriate
-func generateDocDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+func generateDocDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	log.Infof("scanning for -doc package...")
 	if !strings.HasSuffix(hdl.PackageName(), "-doc") {
@@ -720,7 +803,7 @@ func getShbang(fp io.Reader) (string, error) {
 	return bin, nil
 }
 
-func generateShbangDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+func generateShbangDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies, extraLibDirs []string) error {
 	log := clog.FromContext(ctx)
 	log.Infof("scanning for shbang deps...")
 
@@ -774,6 +857,11 @@ func generateShbangDeps(ctx context.Context, hdl SCAHandle, generated *config.De
 // Analyze runs the SCA analyzers on a given SCA handle, modifying the generated dependencies
 // set as needed.
 func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+	extraLibDirs, err := getLdSoConfDLibPaths(ctx, hdl)
+	if err != nil {
+		return err
+	}
+
 	generators := []DependencyGenerator{
 		generateSharedObjectNameDeps,
 		generateCmdProviders,
@@ -785,7 +873,7 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
 	}
 
 	for _, gen := range generators {
-		if err := gen(ctx, hdl, generated); err != nil {
+		if err := gen(ctx, hdl, generated, extraLibDirs); err != nil {
 			return err
 		}
 	}
diff --git a/pkg/sca/sca_test.go b/pkg/sca/sca_test.go
index f7793c5d4..c3e8ecf0f 100644
--- a/pkg/sca/sca_test.go
+++ b/pkg/sca/sca_test.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:generate go run ./../../ build --generate-index=false --out-dir=./testdata/generated ./testdata/ld-so-conf-d-test.yaml --arch=x86_64
 //go:generate go run ./../../ build --generate-index=false --out-dir=./testdata/generated ./testdata/shbang-test.yaml --arch=x86_64
 //go:generate go run ./../../ build --generate-index=false --source-dir=./testdata/go-fips-bin/ --out-dir=./testdata/generated ./testdata/go-fips-bin/go-fips-bin.yaml --arch=x86_64
 //go:generate curl -s -o ./testdata/py3-seaborn.yaml https://raw.githubusercontent.com/wolfi-dev/os/7a39ac1d0603a3561790ea2201dd8ad7c2b7e51e/py3-seaborn.yaml
@@ -56,7 +57,7 @@ func (th *testHandle) Version() string {
 
 func (th *testHandle) RelativeNames() []string {
 	// TODO: Support subpackages?
-	return []string{th.pkg.Origin}
+	return []string{th.pkg.Name}
 }
 
 func (th *testHandle) FilesystemForRelative(pkgName string) (SCAFS, error) {
@@ -337,3 +338,20 @@ func TestGetShbang(t *testing.T) {
 		}
 	}
 }
+
+func TestLdSoConfD(t *testing.T) {
+	ctx := slogtest.Context(t)
+	// Generated with `go generate ./...`
+	th := handleFromApk(ctx, t, "generated/x86_64/ld-so-conf-d-test-1-r1.apk", "ld-so-conf-d-test.yaml")
+	defer th.exp.Close()
+
+	if extraLibPaths, err := getLdSoConfDLibPaths(ctx, th); err != nil {
+		t.Fatal(err)
+	} else if extraLibPaths == nil {
+		t.Error("getLdSoConfDLibPaths: expected 'my/lib/test', got nil")
+	} else {
+		if extraLibPaths[0] != "my/lib/test" {
+			t.Errorf("getLdSoConfDLibPaths: expected 'my/lib/test', got '%s'", extraLibPaths[0])
+		}
+	}
+}
diff --git a/pkg/sca/testdata/generated/x86_64/ld-so-conf-d-test-1-r1.apk b/pkg/sca/testdata/generated/x86_64/ld-so-conf-d-test-1-r1.apk
new file mode 100644
index 000000000..3b666c538
Binary files /dev/null and b/pkg/sca/testdata/generated/x86_64/ld-so-conf-d-test-1-r1.apk differ
diff --git a/pkg/sca/testdata/ld-so-conf-d-test.yaml b/pkg/sca/testdata/ld-so-conf-d-test.yaml
new file mode 100644
index 000000000..db9c37fc7
--- /dev/null
+++ b/pkg/sca/testdata/ld-so-conf-d-test.yaml
@@ -0,0 +1,25 @@
+package:
+  name: ld-so-conf-d-test
+  version: 1
+  epoch: 1
+  description: ld.so.conf.d test
+  copyright:
+    - license: MIT
+
+environment:
+  contents:
+    repositories:
+      - https://packages.wolfi.dev/os
+    keyring:
+      - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
+    packages:
+      - build-base
+      - busybox
+
+pipeline:
+  - runs: |
+      mkdir -p "${{targets.destdir}}"/etc/ld.so.conf.d
+      echo "/my/lib/test" > "${{targets.destdir}}"/etc/ld.so.conf.d/ld-so-conf-d-test.conf
+
+update:
+  enabled: false


Reviews:
user: login: dannf
body: very cool! a couple comments inline, but I'm not sure either is a real issue.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T21:58:15Z
user: login: sergiodj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T22:24:17Z
user: login: sergiodj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T22:24:42Z
user: login: dannf
body: Looks like there's some test failures that need addressing, but this LGTM!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:16:55Z
user: login: jonjohnsonjr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T23:45:41Z
user: login: sergiodj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T03:05:20Z
user: login: sergiodj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T03:11:48Z
user: login: jonjohnsonjr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T22:16:09Z
user: login: jonjohnsonjr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T22:16:41Z
user: login: sergiodj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T21:23:09Z
user: login: sergiodj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T21:23:35Z
user: login: jonjohnsonjr
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T21:42:03Z

Review comments:
diff_hunk: @@ -86,6 +87,73 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package, and for each configuration file found under
+// /etc/ld.so.conf.d/ it will parse the file and add its contents to a
+// string vector.  This vector will ultimately contain all extra paths
+// that will be considered by ld when doing symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+	fsys, err := hdl.Filesystem()
+	if err != nil {
+		return nil, err
+	}
+
+	if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+
+		// We're only interested in files inside /etc/ld.so.conf.d/...
+		if !isInDir(path, []string{"etc/ld.so.conf.d"}) {
+			return nil
+		}
+
+		// ... and whose suffix is ".conf"...
+		if !strings.HasSuffix(path, ".conf") {
+			return nil
+		}
+
+		fi, err := d.Info()
+		if err != nil {
+			return err
+		}
+
+		if !fi.Mode().IsRegular() {
path: pkg/sca/sca.go
user: login: dannf
body: should we support symlinks to?
created_at: 2025-03-10T21:51:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -766,6 +834,15 @@ func generateShbangDeps(ctx context.Context, hdl SCAHandle, generated *config.De
 // Analyze runs the SCA analyzers on a given SCA handle, modifying the generated dependencies
 // set as needed.
 func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+	var oldLibDirs []string
+
+	if extraLibPaths, err := getLdSoConfDLibPaths(ctx, hdl); err != nil {
+		return err
+	} else if extraLibPaths != nil {
path: pkg/sca/sca.go
user: login: dannf
body: could this `else if` just be a separate `if`? sorry if that's a dumb question, I don't read `go` that well yet.
created_at: 2025-03-10T21:55:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,6 +87,73 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package, and for each configuration file found under
+// /etc/ld.so.conf.d/ it will parse the file and add its contents to a
+// string vector.  This vector will ultimately contain all extra paths
+// that will be considered by ld when doing symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+	fsys, err := hdl.Filesystem()
+	if err != nil {
+		return nil, err
+	}
+
+	if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+
+		// We're only interested in files inside /etc/ld.so.conf.d/...
+		if !isInDir(path, []string{"etc/ld.so.conf.d"}) {
+			return nil
+		}
+
+		// ... and whose suffix is ".conf"...
+		if !strings.HasSuffix(path, ".conf") {
+			return nil
+		}
+
+		fi, err := d.Info()
+		if err != nil {
+			return err
+		}
+
+		if !fi.Mode().IsRegular() {
path: pkg/sca/sca.go
user: login: sergiodj
body: Hah, it's awesome that you thought about that :-).

I spent some time implementing support for symlinks, but unfortunately I could not get it to work the way I wanted.  The problem is that if you have a symlink inside `/etc/ld.so.conf.d` pointing to a file outside of it, I could not figure out how to obtain the full path of said file using the filesystem functions that are at my disposal.  I'm sure it's possible, but I'm still learning as I go (pun intended)...
created_at: 2025-03-10T22:24:17Z
author_association: MEMBER
diff_hunk: @@ -766,6 +834,15 @@ func generateShbangDeps(ctx context.Context, hdl SCAHandle, generated *config.De
 // Analyze runs the SCA analyzers on a given SCA handle, modifying the generated dependencies
 // set as needed.
 func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
+	var oldLibDirs []string
+
+	if extraLibPaths, err := getLdSoConfDLibPaths(ctx, hdl); err != nil {
+		return err
+	} else if extraLibPaths != nil {
path: pkg/sca/sca.go
user: login: sergiodj
body: Sure thing.  It doesn't matter really, but I understand why you think it's better.
created_at: 2025-03-10T22:24:42Z
author_association: MEMBER
diff_hunk: @@ -86,6 +87,84 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package and all its subpackages, and for each configuration
+// file found under /etc/ld.so.conf.d/ it will parse the file and add
+// its contents to a string vector.  This vector will ultimately
+// contain all extra paths that will be considered by ld when doing
+// symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+	targetPackageNames := hdl.RelativeNames()
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+
+	for _, pkgName := range targetPackageNames {
+		fsys, err := hdl.FilesystemForRelative(pkgName)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+
+			// We're only interested in files inside /etc/ld.so.conf.d/...
+			if !isInDir(path, []string{"etc/ld.so.conf.d"}) {
+				return nil
+			}
+
+			// ... and whose suffix is ".conf"...
+			if !strings.HasSuffix(path, ".conf") {
+				return nil
+			}
+
+			fi, err := d.Info()
+			if err != nil {
+				return err
+			}
+
+			if !fi.Mode().IsRegular() {
+				return nil
+			}
+
+			log.Infof("  found ld.so.conf.d file %s", path)
+
+			fd, err := fsys.Open(path)
+			if err != nil {
+				return err
+			}
+			defer fd.Close()
+
+			scanner := bufio.NewScanner(fd)
+			for scanner.Scan() {
+				line := scanner.Text()
+				if line[0] != '/' {
path: pkg/sca/sca.go
user: login: jonjohnsonjr
body: How does this handle empty lines? I think we might panic here if `line == ""`.
created_at: 2025-03-11T23:42:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -804,5 +894,9 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
 		generated.Provides = nil
 	}
 
+	if oldLibDirs != nil {
+		libDirs = oldLibDirs
path: pkg/sca/sca.go
user: login: jonjohnsonjr
body: This frightens me a bit -- instead of mutating the global here do you mind plumbing this around everywhere? 
created_at: 2025-03-11T23:45:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,6 +87,84 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package and all its subpackages, and for each configuration
+// file found under /etc/ld.so.conf.d/ it will parse the file and add
+// its contents to a string vector.  This vector will ultimately
+// contain all extra paths that will be considered by ld when doing
+// symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+	targetPackageNames := hdl.RelativeNames()
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+
+	for _, pkgName := range targetPackageNames {
+		fsys, err := hdl.FilesystemForRelative(pkgName)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+
+			// We're only interested in files inside /etc/ld.so.conf.d/...
+			if !isInDir(path, []string{"etc/ld.so.conf.d"}) {
+				return nil
+			}
+
+			// ... and whose suffix is ".conf"...
+			if !strings.HasSuffix(path, ".conf") {
+				return nil
+			}
+
+			fi, err := d.Info()
+			if err != nil {
+				return err
+			}
+
+			if !fi.Mode().IsRegular() {
+				return nil
+			}
+
+			log.Infof("  found ld.so.conf.d file %s", path)
+
+			fd, err := fsys.Open(path)
+			if err != nil {
+				return err
+			}
+			defer fd.Close()
+
+			scanner := bufio.NewScanner(fd)
+			for scanner.Scan() {
+				line := scanner.Text()
+				if line[0] != '/' {
path: pkg/sca/sca.go
user: login: sergiodj
body: Good catch.  I've fixed it.
created_at: 2025-03-12T03:05:20Z
author_association: MEMBER
diff_hunk: @@ -804,5 +894,9 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
 		generated.Provides = nil
 	}
 
+	if oldLibDirs != nil {
+		libDirs = oldLibDirs
path: pkg/sca/sca.go
user: login: sergiodj
body: For sure.  But let me take the opportunity and ask a broader question.

I thought about moving `libDirs` inside the `SCABuildInterface`, because this really should be a per-package variable.  However, `SCABuildInterface` only represents one single entity in the package (either the sole package, or one of its subpackages if they exist).  I couldn't find a way to say "hey, this applies to the package *and* its subpackages".

The reason I wanted to do it is because, as you can see now, `getLdSoConfDLibPaths` is being invoked for the package and its subpackages, and it iterates over all files from them, every time.  This is suboptimal.

Something else I thought about is that we could have a global flag to signal whether `getLdSoConfDLibPaths` was already invoked.  But then I have another question: is it guaranteed that we will be dealing with only one package (and its subpackages)?

Sorry for the barrage of questions and thoughts; this code is new to me and I'm trying to make sense of how to best implement what I need here.  Thanks!
created_at: 2025-03-12T03:11:48Z
author_association: MEMBER
diff_hunk: @@ -804,5 +894,9 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
 		generated.Provides = nil
 	}
 
+	if oldLibDirs != nil {
+		libDirs = oldLibDirs
path: pkg/sca/sca.go
user: login: jonjohnsonjr
body: I think this is probably fine for now to ignore it, but either put a big scary TODO on that or plumb it around all the places it needs to go (ew). Ideally we'd have some kind of struct for this.
created_at: 2025-03-13T22:16:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,6 +87,84 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package and all its subpackages, and for each configuration
+// file found under /etc/ld.so.conf.d/ it will parse the file and add
+// its contents to a string vector.  This vector will ultimately
+// contain all extra paths that will be considered by ld when doing
+// symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+	targetPackageNames := hdl.RelativeNames()
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+
+	for _, pkgName := range targetPackageNames {
+		fsys, err := hdl.FilesystemForRelative(pkgName)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
path: pkg/sca/sca.go
user: login: jonjohnsonjr
body: I think we can just `etc/ld.so.conf.d` instead of `.` and skip the isInDir.
created_at: 2025-03-13T22:16:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,6 +87,84 @@ func isInDir(path string, dirs []string) bool {
 	return false
 }
 
+// getLdSoConfDLibPaths will iterate over the files being installed by
+// the package and all its subpackages, and for each configuration
+// file found under /etc/ld.so.conf.d/ it will parse the file and add
+// its contents to a string vector.  This vector will ultimately
+// contain all extra paths that will be considered by ld when doing
+// symbol resolution.
+func getLdSoConfDLibPaths(ctx context.Context, hdl SCAHandle) ([]string, error) {
+	var extraLibPaths []string
+	targetPackageNames := hdl.RelativeNames()
+
+	log := clog.FromContext(ctx)
+
+	log.Info("scanning for ld.so.conf.d files...")
+
+	for _, pkgName := range targetPackageNames {
+		fsys, err := hdl.FilesystemForRelative(pkgName)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
path: pkg/sca/sca.go
user: login: sergiodj
body: Thanks.  This has now been implemented.
created_at: 2025-03-17T21:23:09Z
author_association: MEMBER
diff_hunk: @@ -804,5 +894,9 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
 		generated.Provides = nil
 	}
 
+	if oldLibDirs != nil {
+		libDirs = oldLibDirs
path: pkg/sca/sca.go
user: login: sergiodj
body: ACK.  I decided to plumb the variable around.
created_at: 2025-03-17T21:23:34Z
author_association: MEMBER

PR comments:
user: login: sergiodj
created_at: 2025-03-11T16:38:13Z
body: Hi @jonjohnsonjr!

I'd be delighted if you could offer your review here as well.

Thanks!
author_association: MEMBER
user: login: sergiodj
created_at: 2025-03-11T22:34:06Z
body: > Looks like there's some test failures that need addressing, but this LGTM!

Thanks, @dannf!  I pushed a small change to fix the failure.
author_association: MEMBER
user: login: sergiodj
created_at: 2025-03-17T21:24:34Z
body: OK, this should be ready for re-review.
author_association: MEMBER