From ff45b9ea6391b4df47f97ba4fac62e85ddd8fad0 Mon Sep 17 00:00:00 2001 From: Sverrir Sigurdsson <43657200+sverrirsig@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:10:55 +0000 Subject: [PATCH] fix: release package collection lock in Ubuntu ESM detection (#5336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sverrir Sigurðsson --- internal/os/feature_detection.go | 10 ++++------ internal/os/feature_detection_test.go | 28 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/internal/os/feature_detection.go b/internal/os/feature_detection.go index 1aaf3f23c..a836a2191 100644 --- a/internal/os/feature_detection.go +++ b/internal/os/feature_detection.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "regexp" + "slices" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" @@ -191,12 +192,9 @@ func hasInstalledESMPackage(packages *pkg.Collection) bool { if packages == nil { return false } - for p := range packages.Enumerate(pkg.DebPkg) { - if esmVersionPattern.MatchString(p.Version) { - return true - } - } - return false + return slices.ContainsFunc(packages.Sorted(pkg.DebPkg), func(p pkg.Package) bool { + return esmVersionPattern.MatchString(p.Version) + }) } func fileMatchesAny(resolver file.Resolver, location file.Location, patterns ...*regexp.Regexp) (bool, error) { diff --git a/internal/os/feature_detection_test.go b/internal/os/feature_detection_test.go index bb8a30de1..b4f2ed38d 100644 --- a/internal/os/feature_detection_test.go +++ b/internal/os/feature_detection_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "slices" "testing" + "time" "github.com/stretchr/testify/require" @@ -232,6 +233,33 @@ func Test_findUbuntuFeatures(t *testing.T) { } } +// returning early from Collection.Enumerate leaks its goroutine and read lock, so any later write blocks forever. +func Test_findUbuntuFeatures_releasesPackageCollectionLock(t *testing.T) { + resolver := fixtureResolverForDir(t, "testdata/ubuntu_plain") + + s := sbom.SBOM{} + s.Artifacts.LinuxDistribution = &linux.Release{ID: "ubuntu"} + s.Artifacts.Packages = pkg.NewCollection( + pkg.Package{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg}, + pkg.Package{Name: "libcap2", Version: "1:2.32-1ubuntu0.1~esm1", Type: pkg.DebPkg}, + ) + + require.NoError(t, os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s))) + require.True(t, s.Artifacts.LinuxDistribution.ExtendedSupport) + + added := make(chan struct{}) + go func() { + s.Artifacts.Packages.Add(pkg.Package{Name: "zlib1g", Version: "1:1.2.11.dfsg-2ubuntu1", Type: pkg.DebPkg}) + close(added) + }() + + select { + case <-added: + case <-time.After(5 * time.Second): + t.Fatal("package collection is still read-locked after feature detection") + } +} + // a read error in the apt-source signal must not suppress the ubuntu-advantage status signal; the three // signals are meant to be independent fallbacks. func Test_findUbuntuFeatures_signalErrorDoesNotSuppressOthers(t *testing.T) {