diff --git a/integration/testdata/debian-buster-ignore-unfixed.json.golden b/integration/testdata/debian-buster-ignore-unfixed.json.golden index cb5a606e3a94..014dce8e1261 100644 --- a/integration/testdata/debian-buster-ignore-unfixed.json.golden +++ b/integration/testdata/debian-buster-ignore-unfixed.json.golden @@ -62,7 +62,7 @@ "PkgName": "libidn2-0", "PkgIdentifier": { "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1", - "UID": "473f5eb9e3d4a2f2" + "UID": "90ca98dd0e7ef4c9" }, "InstalledVersion": "2.0.5-1", "FixedVersion": "2.0.5-1+deb10u1", diff --git a/integration/testdata/debian-buster.json.golden b/integration/testdata/debian-buster.json.golden index 4c9d64fc8347..c8e6e5ec6eb4 100644 --- a/integration/testdata/debian-buster.json.golden +++ b/integration/testdata/debian-buster.json.golden @@ -59,7 +59,7 @@ "PkgName": "bash", "PkgIdentifier": { "PURL": "pkg:deb/debian/bash@5.0-4?arch=amd64\u0026distro=debian-10.1", - "UID": "d45ab8ae65ffe67" + "UID": "8d7a8682e8e570f6" }, "InstalledVersion": "5.0-4", "Status": "affected", @@ -126,7 +126,7 @@ "PkgName": "libidn2-0", "PkgIdentifier": { "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1", - "UID": "473f5eb9e3d4a2f2" + "UID": "90ca98dd0e7ef4c9" }, "InstalledVersion": "2.0.5-1", "FixedVersion": "2.0.5-1+deb10u1", diff --git a/integration/testdata/debian-stretch.json.golden b/integration/testdata/debian-stretch.json.golden index e4be6f91f1f7..204f6e188871 100644 --- a/integration/testdata/debian-stretch.json.golden +++ b/integration/testdata/debian-stretch.json.golden @@ -59,7 +59,7 @@ "PkgName": "bash", "PkgIdentifier": { "PURL": "pkg:deb/debian/bash@4.4-5?arch=amd64\u0026distro=debian-9.9", - "UID": "6100d09336f565a0" + "UID": "169a2eb9316cbc25" }, "InstalledVersion": "4.4-5", "Status": "end_of_life", @@ -126,7 +126,7 @@ "PkgName": "e2fslibs", "PkgIdentifier": { "PURL": "pkg:deb/debian/e2fslibs@1.43.4-2?arch=amd64\u0026distro=debian-9.9", - "UID": "656652ce5818f7b6" + "UID": "8e9a3beaf044163" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", @@ -200,7 +200,7 @@ "PkgName": "e2fsprogs", "PkgIdentifier": { "PURL": "pkg:deb/debian/e2fsprogs@1.43.4-2?arch=amd64\u0026distro=debian-9.9", - "UID": "3d19fd957338dc06" + "UID": "8e1a49ed8eab9cdd" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", @@ -274,7 +274,7 @@ "PkgName": "libcomerr2", "PkgIdentifier": { "PURL": "pkg:deb/debian/libcomerr2@1.43.4-2?arch=amd64\u0026distro=debian-9.9", - "UID": "6ba1fac685a0c068" + "UID": "8defa5f1bc216456" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", @@ -348,7 +348,7 @@ "PkgName": "libss2", "PkgIdentifier": { "PURL": "pkg:deb/debian/libss2@1.43.4-2?arch=amd64\u0026distro=debian-9.9", - "UID": "e507c185f61cd2e8" + "UID": "acde693812d0c389" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", diff --git a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden index 5ec55a3fc397..062609e5e606 100644 --- a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden +++ b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden @@ -77,7 +77,7 @@ "PkgName": "e2fsprogs", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "ae80ec86b8816b6c" + "UID": "b72413ab1b4bb266" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -148,7 +148,7 @@ "PkgName": "libcom-err2", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "3c28244e063693a2" + "UID": "7bb2b92159d48f66" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -219,7 +219,7 @@ "PkgName": "libext2fs2", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "937ce6e3021ed568" + "UID": "bbf41e10db8e7ccc" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -290,7 +290,7 @@ "PkgName": "libss2", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "7a50c6bc4279c93b" + "UID": "c85c62b3de87638" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", diff --git a/integration/testdata/ubuntu-1804.json.golden b/integration/testdata/ubuntu-1804.json.golden index 7ae275b7e5a6..3b33f1b215c3 100644 --- a/integration/testdata/ubuntu-1804.json.golden +++ b/integration/testdata/ubuntu-1804.json.golden @@ -77,7 +77,7 @@ "PkgName": "bash", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/bash@4.4.18-2ubuntu1.2?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "da318bd19a304cc0" + "UID": "bbe28cf85cdf4616" }, "InstalledVersion": "4.4.18-2ubuntu1.2", "Status": "affected", @@ -140,7 +140,7 @@ "PkgName": "e2fsprogs", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "ae80ec86b8816b6c" + "UID": "b72413ab1b4bb266" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -211,7 +211,7 @@ "PkgName": "libcom-err2", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "3c28244e063693a2" + "UID": "7bb2b92159d48f66" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -282,7 +282,7 @@ "PkgName": "libext2fs2", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "937ce6e3021ed568" + "UID": "bbf41e10db8e7ccc" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -353,7 +353,7 @@ "PkgName": "libss2", "PkgIdentifier": { "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", - "UID": "7a50c6bc4279c93b" + "UID": "c85c62b3de87638" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index ef20482a782c..bcce8387c9c1 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -27,11 +27,12 @@ const ( TypeUbuntuESM Type = "ubuntu-esm" // OS Package - TypeApk Type = "apk" - TypeDpkg Type = "dpkg" - TypeDpkgLicense Type = "dpkg-license" // For analyzing licenses - TypeRpm Type = "rpm" - TypeRpmqa Type = "rpmqa" + TypeApk Type = "apk" + TypeDpkg Type = "dpkg" + TypeDpkgLicense Type = "dpkg-license" // For analyzing licenses + TypeDpkgSystemFiles Type = "dpkg-system-files" // For analyzing system files + TypeRpm Type = "rpm" + TypeRpmqa Type = "rpmqa" // OS Package Repository TypeApkRepo Type = "apk-repo" @@ -156,6 +157,7 @@ var ( TypeApk, TypeDpkg, TypeDpkgLicense, + TypeDpkgSystemFiles, TypeRpm, TypeRpmqa, TypeApkRepo, diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go index a83592e82523..e0a3f90d5143 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -1,7 +1,6 @@ package dpkg import ( - "bufio" "context" "errors" "fmt" @@ -45,7 +44,6 @@ const ( statusFile = "var/lib/dpkg/status" statusDir = "var/lib/dpkg/status.d/" - infoDir = "var/lib/dpkg/info/" availableFile = "var/lib/dpkg/available" ) @@ -55,7 +53,6 @@ var ( ) func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { - var systemInstalledFiles []string var packageInfos []types.PackageInfo // parse `available` file to get digest for packages @@ -68,21 +65,8 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis return path != availableFile } - packageFiles := make(map[string][]string) - // parse other files err = fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { - // parse list files - if a.isListFile(filepath.Split(path)) { - scanner := bufio.NewScanner(r) - systemFiles, err := a.parseDpkgInfoList(scanner) - if err != nil { - return err - } - packageFiles[strings.TrimSuffix(filepath.Base(path), ".list")] = systemFiles - systemInstalledFiles = append(systemInstalledFiles, systemFiles...) - return nil - } // parse status files infos, err := a.parseDpkgStatus(path, r, digests) if err != nil { @@ -95,67 +79,12 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis return nil, xerrors.Errorf("dpkg walk error: %w", err) } - // map the packages to their respective files - for i, pkgInfo := range packageInfos { - for j, pkg := range pkgInfo.Packages { - installedFiles, found := packageFiles[pkg.Name] - if !found { - installedFiles = packageFiles[pkg.Name+":"+pkg.Arch] - } - packageInfos[i].Packages[j].InstalledFiles = installedFiles - } - } - return &analyzer.AnalysisResult{ - PackageInfos: packageInfos, - SystemInstalledFiles: systemInstalledFiles, + PackageInfos: packageInfos, }, nil } -// parseDpkgInfoList parses /var/lib/dpkg/info/*.list -func (a dpkgAnalyzer) parseDpkgInfoList(scanner *bufio.Scanner) ([]string, error) { - var ( - allLines []string - installedFiles []string - previous string - ) - - for scanner.Scan() { - current := scanner.Text() - if current == "/." { - continue - } - allLines = append(allLines, current) - } - - if err := scanner.Err(); err != nil { - return nil, xerrors.Errorf("scan error: %w", err) - } - - // Add the file if it is not directory. - // e.g. - // /usr/sbin - // /usr/sbin/tarcat - // - // In the above case, we should take only /usr/sbin/tarcat since /usr/sbin is a directory - // sort first,see here:https://github.com/aquasecurity/trivy/discussions/6543 - sort.Strings(allLines) - for _, current := range allLines { - if !strings.HasPrefix(current, previous+"/") { - installedFiles = append(installedFiles, previous) - } - previous = current - } - - // // Add the last file - if previous != "" && !strings.HasSuffix(previous, "/") { - installedFiles = append(installedFiles, previous) - } - - return installedFiles, nil -} - // parseDpkgAvailable parses /var/lib/dpkg/available func (a dpkgAnalyzer) parseDpkgAvailable(fsys fs.FS) (map[string]digest.Digest, error) { f, err := fsys.Open(availableFile) @@ -290,7 +219,7 @@ func (a dpkgAnalyzer) parseDpkgPkg(header textproto.MIMEHeader) *types.Package { func (a dpkgAnalyzer) Required(filePath string, _ os.FileInfo) bool { dir, fileName := filepath.Split(filePath) - if a.isListFile(dir, fileName) || filePath == statusFile || filePath == availableFile { + if filePath == statusFile || filePath == availableFile { return true } @@ -357,14 +286,6 @@ func (a dpkgAnalyzer) consolidateDependencies(pkgs map[string]*types.Package, pk } } -func (a dpkgAnalyzer) isListFile(dir, fileName string) bool { - if dir != infoDir { - return false - } - - return strings.HasSuffix(fileName, ".list") -} - func (a dpkgAnalyzer) Type() analyzer.Type { return analyzer.TypeDpkg } diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go index c131b900d899..5037c6260acf 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go @@ -1417,28 +1417,6 @@ func Test_dpkgAnalyzer_Analyze(t *testing.T) { }, }, }, - { - name: "info list", - testFiles: map[string]string{"./testdata/tar.list": "var/lib/dpkg/info/tar.list"}, - want: &analyzer.AnalysisResult{ - SystemInstalledFiles: []string{ - "/bin/tar", - "/etc/rmt", - "/usr/lib/mime/packages/tar", - "/usr/sbin/rmt-tar", - "/usr/sbin/tarcat", - "/usr/share/doc/tar/AUTHORS", - "/usr/share/doc/tar/NEWS.gz", - "/usr/share/doc/tar/README.Debian", - "/usr/share/doc/tar/THANKS.gz", - "/usr/share/doc/tar/changelog.Debian.gz", - "/usr/share/doc/tar/copyright", - "/usr/share/man/man1/tar.1.gz", - "/usr/share/man/man1/tarcat.1.gz", - "/usr/share/man/man8/rmt-tar.8.gz", - }, - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1490,21 +1468,11 @@ func Test_dpkgAnalyzer_Required(t *testing.T) { filePath: "var/lib/dpkg/status.d/base-files.md5sums", want: false, }, - { - name: "list file", - filePath: "var/lib/dpkg/info/bash.list", - want: true, - }, { name: "available file", filePath: "var/lib/dpkg/available", want: true, }, - { - name: "sad path", - filePath: "var/lib/dpkg/status/bash.list", - want: false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/fanal/analyzer/pkg/dpkg/list.go b/pkg/fanal/analyzer/pkg/dpkg/list.go new file mode 100644 index 000000000000..790ba23f4dcc --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/list.go @@ -0,0 +1,117 @@ +package dpkg + +import ( + "bufio" + "context" + "os" + "path/filepath" + "sort" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +func init() { + analyzer.RegisterAnalyzer(newDpkgListAnalyzer()) +} + +const ( + infoDir = "var/lib/dpkg/info/" + listAnalyzerVersion = 1 +) + +type dpkgListAnalyzer struct { + logger *log.Logger +} + +func newDpkgListAnalyzer() *dpkgListAnalyzer { + return &dpkgListAnalyzer{ + logger: log.WithPrefix("dpkg-list"), + } +} + +func (a dpkgListAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + scanner := bufio.NewScanner(input.Content) + systemFiles, err := a.parseDpkgInfoList(scanner) + if err != nil { + return nil, xerrors.Errorf("failed to parse dpkg info: %w", err) + } + + return &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: input.FilePath, + Packages: []types.Package{ + { + Name: strings.TrimSuffix(filepath.Base(input.FilePath), ".list"), + InstalledFiles: systemFiles, + }, + }, + }, + }, + SystemInstalledFiles: systemFiles, + }, nil +} + +// parseDpkgInfoList parses /var/lib/dpkg/info/*.list +func (a dpkgListAnalyzer) parseDpkgInfoList(scanner *bufio.Scanner) ([]string, error) { + var ( + allLines []string + installedFiles []string + previous string + ) + + for scanner.Scan() { + current := scanner.Text() + if current == "/." { + continue + } + allLines = append(allLines, current) + } + + if err := scanner.Err(); err != nil { + return nil, xerrors.Errorf("scan error: %w", err) + } + + // Add the file if it is not directory. + // e.g. + // /usr/sbin + // /usr/sbin/tarcat + // + // In the above case, we should take only /usr/sbin/tarcat since /usr/sbin is a directory + // sort first,see here:https://github.com/aquasecurity/trivy/discussions/6543 + sort.Strings(allLines) + for _, current := range allLines { + if !strings.HasPrefix(current, previous+"/") { + installedFiles = append(installedFiles, previous) + } + previous = current + } + + // Add the last file + if previous != "" && !strings.HasSuffix(previous, "/") { + installedFiles = append(installedFiles, previous) + } + + return installedFiles, nil +} + +func (a dpkgListAnalyzer) Required(filePath string, _ os.FileInfo) bool { + dir, fileName := filepath.Split(filePath) + if dir != infoDir { + return false + } + return filepath.Ext(fileName) == ".list" +} + +func (a dpkgListAnalyzer) Type() analyzer.Type { + return analyzer.TypeDpkgSystemFiles +} + +func (a dpkgListAnalyzer) Version() int { + return listAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/list_test.go b/pkg/fanal/analyzer/pkg/dpkg/list_test.go new file mode 100644 index 000000000000..da0dca437365 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/dpkg/list_test.go @@ -0,0 +1,124 @@ +package dpkg + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_dpkgListAnalyzer_Analyze(t *testing.T) { + systemFiles := []string{ + "/bin/tar", + "/etc/rmt", + "/usr/lib/mime/packages/tar", + "/usr/sbin/rmt-tar", + "/usr/sbin/tarcat", + "/usr/share/doc/tar/AUTHORS", + "/usr/share/doc/tar/NEWS.gz", + "/usr/share/doc/tar/README.Debian", + "/usr/share/doc/tar/THANKS.gz", + "/usr/share/doc/tar/changelog.Debian.gz", + "/usr/share/doc/tar/copyright", + "/usr/share/man/man1/tar.1.gz", + "/usr/share/man/man1/tarcat.1.gz", + "/usr/share/man/man8/rmt-tar.8.gz", + } + tests := []struct { + name string + filePath string + testFile string + want *analyzer.AnalysisResult + wantErr bool + }{ + { + name: "info list", + filePath: "var/lib/dpkg/info/tar.list", + testFile: "./testdata/tar.list", + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/info/tar.list", + Packages: []types.Package{ + { + Name: "tar", + InstalledFiles: systemFiles, + }, + }, + }, + }, + SystemInstalledFiles: systemFiles, + }, + }, + { + name: "info list with arch", + filePath: "var/lib/dpkg/info/tar:amd64.list", + testFile: "./testdata/tar.list", + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/info/tar:amd64.list", + Packages: []types.Package{ + { + Name: "tar:amd64", + InstalledFiles: systemFiles, + }, + }, + }, + }, + SystemInstalledFiles: systemFiles, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.testFile) + require.NoError(t, err) + + input := analyzer.AnalysisInput{ + Content: f, + FilePath: tt.filePath, + } + a := newDpkgListAnalyzer() + + got, err := a.Analyze(context.Background(), input) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func Test_dpkgListAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "var/lib/dpkg/info/bash.list", + want: true, + }, + { + name: "happy path with arch", + filePath: "var/lib/dpkg/info/zlib1g:amd64.list", + want: true, + }, + { + name: "sad path", + filePath: "var/lib/dpkg/status/bash.list", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := newDpkgListAnalyzer() + got := a.Required(tt.filePath, nil) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index 41487819b4ae..21d8eb9a72f4 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -2,6 +2,7 @@ package applier import ( "fmt" + "path/filepath" "strings" "time" @@ -94,6 +95,7 @@ func lookupOriginLayerForLib(filePath string, lib ftypes.Package, layers []ftype func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { sep := "/" nestedMap := nested.Nested{} + dpkSystemFilesMap := make(map[string][]string) secretsMap := make(map[string]ftypes.Secret) var mergedLayer ftypes.ArtifactDetail @@ -114,8 +116,16 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { // Apply OS packages for _, pkgInfo := range layer.PackageInfos { - key := fmt.Sprintf("%s/type:ospkg", pkgInfo.FilePath) - nestedMap.SetByString(key, sep, pkgInfo) + // Save system files for debian packages. + // We will fill them in later. + if filepath.Ext(pkgInfo.FilePath) == ".list" { + for _, pkg := range pkgInfo.Packages { + dpkSystemFilesMap[pkg.Name] = pkg.InstalledFiles + } + } else { + key := fmt.Sprintf("%s/type:ospkg", pkgInfo.FilePath) + nestedMap.SetByString(key, sep, pkgInfo) + } } // Apply language-specific packages @@ -218,15 +228,22 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { mergedLayer.Packages[i].BuildInfo = buildInfo } - if mergedLayer.OS.Family != "" { - mergedLayer.Packages[i].Identifier.PURL = newPURL(mergedLayer.OS.Family, types.Metadata{OS: &mergedLayer.OS}, pkg) - } - mergedLayer.Packages[i].Identifier.UID = calcPkgUID("", pkg) - // Only debian packages if licenses, ok := dpkgLicenses[pkg.Name]; ok { mergedLayer.Packages[i].Licenses = licenses } + + // Fill system installed files for debian packages + if installedFiles, found := dpkSystemFilesMap[pkg.Name]; found { + mergedLayer.Packages[i].InstalledFiles = installedFiles + } else if installedFiles, found = dpkSystemFilesMap[pkg.Name+":"+pkg.Arch]; found { + mergedLayer.Packages[i].InstalledFiles = installedFiles + } + + if mergedLayer.OS.Family != "" { + mergedLayer.Packages[i].Identifier.PURL = newPURL(mergedLayer.OS.Family, types.Metadata{OS: &mergedLayer.OS}, pkg) + } + mergedLayer.Packages[i].Identifier.UID = calcPkgUID("", pkg) } for _, app := range mergedLayer.Applications { diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index f5bccfa86bf6..29fff6f2d3d3 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -843,6 +843,185 @@ func TestApplyLayers(t *testing.T) { }, }, }, + { + name: "happy path with filling system files for debian packages", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 2, + DiffID: "sha256:03fdf04efd9e8117f277abeef88a9d51d45c154bfa042591f85656e8ffccc141", + OS: types.OS{ + Family: "ubuntu", + Name: "24.04", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/info/zlib1g:arm64.list", + Packages: types.Packages{ + { + Name: "zlib1g:arm64", + InstalledFiles: []string{ + "/usr/lib/aarch64-linux-gnu/libz.so.1", + "/usr/lib/aarch64-linux-gnu/libz.so.1.3", + "/usr/share/doc/zlib1g/changelog.Debian.gz", + "/usr/share/doc/zlib1g/copyright", + }, + }, + }, + }, + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "zlib1g@1:1.3.dfsg-3.1ubuntu2", + Name: "zlib1g", + Version: "1.3.dfsg", + Release: "3.1ubuntu2", + Epoch: 1, + Arch: "arm64", + SrcName: "zlib1g", + SrcVersion: "1.3.dfsg", + SrcRelease: "3.1ubuntu2", + SrcEpoch: 1, + }, + }, + }, + }, + }, + // Install `curl` + { + SchemaVersion: 2, + DiffID: "sha256:3836402300043690208bff1a68978994817546557b9116c6db540935766c652f", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/info/curl.list", + Packages: types.Packages{ + { + Name: "curl", + InstalledFiles: []string{ + "/usr/bin/curl", + "/usr/share/doc/curl/README.Debian", + "/usr/share/doc/curl/changelog.Debian.gz", + "/usr/share/doc/curl/copyright", + "/usr/share/man/man1/curl.1.gz", + "/usr/share/zsh/vendor-completions/_curl", + }, + }, + }, + }, + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "curl@8.5.0-2ubuntu10.1", + Name: "curl", + Version: "8.5.0", + Release: "2ubuntu10.1", + Arch: "arm64", + SrcName: "curl", + SrcVersion: "8.5.0", + SrcRelease: "2ubuntu10.1", + }, + { + ID: "zlib1g@1:1.3.dfsg-3.1ubuntu2", + Name: "zlib1g", + Version: "1.3.dfsg", + Release: "3.1ubuntu2", + Epoch: 1, + Arch: "arm64", + SrcName: "zlib1g", + SrcVersion: "1.3.dfsg", + SrcRelease: "3.1ubuntu2", + SrcEpoch: 1, + }, + }, + }, + }, + }, + // Remove curl + { + SchemaVersion: 2, + DiffID: "sha256:c50cbe1d0dee626cc85de5c53c45a105d8c5cc56e6b675264aa8ba2e03c6bfe7", + WhiteoutFiles: []string{ + "usr/bin/curl", + "usr/share/doc/curl", + "usr/share/zsh", + "var/lib/dpkg/info/curl.list", + "var/lib/dpkg/info/curl.md5sums", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "zlib1g@1:1.3.dfsg-3.1ubuntu2", + Name: "zlib1g", + Version: "1.3.dfsg", + Release: "3.1ubuntu2", + Epoch: 1, + Arch: "arm64", + SrcName: "zlib1g", + SrcVersion: "1.3.dfsg", + SrcRelease: "3.1ubuntu2", + SrcEpoch: 1, + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "ubuntu", + Name: "24.04", + }, + Packages: types.Packages{ + { + ID: "zlib1g@1:1.3.dfsg-3.1ubuntu2", + Name: "zlib1g", + Version: "1.3.dfsg", + Release: "3.1ubuntu2", + Epoch: 1, + Arch: "arm64", + SrcName: "zlib1g", + SrcVersion: "1.3.dfsg", + SrcRelease: "3.1ubuntu2", + SrcEpoch: 1, + Identifier: types.PkgIdentifier{ + UID: "8ad895abf3283710", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "ubuntu", + Name: "zlib1g", + Version: "1.3.dfsg-3.1ubuntu2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "arm64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "distro", + Value: "ubuntu-24.04", + }, + }, + }, + }, + Layer: types.Layer{ + DiffID: "sha256:03fdf04efd9e8117f277abeef88a9d51d45c154bfa042591f85656e8ffccc141", + }, + InstalledFiles: []string{ + "/usr/lib/aarch64-linux-gnu/libz.so.1", + "/usr/lib/aarch64-linux-gnu/libz.so.1.3", + "/usr/share/doc/zlib1g/changelog.Debian.gz", + "/usr/share/doc/zlib1g/copyright", + }, + }, + }, + }, + }, { name: "happy path, opaque dirs with the trailing slash", inputLayers: []types.BlobInfo{ diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index 05fc6b229105..d2dbc96d0aaf 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -352,17 +352,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + MissingBlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobID: "sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -429,7 +429,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "../../test/testdata/alpine-311.tar.gz", Type: artifact.TypeContainerImage, ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ @@ -488,25 +488,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", + "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", + "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", + "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", + "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", + "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", + "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + BlobID: "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -594,7 +594,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + BlobID: "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -690,7 +690,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + BlobID: "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -898,7 +898,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + BlobID: "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1761,10 +1761,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", + "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", + "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", + "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1848,6 +1848,7 @@ func TestArtifact_Inspect(t *testing.T) { analyzer.TypeDebian, analyzer.TypeDpkg, analyzer.TypeDpkgLicense, + analyzer.TypeDpkgSystemFiles, analyzer.TypeComposer, analyzer.TypeBundler, analyzer.TypeLicenseFile, @@ -2012,7 +2013,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ Err: xerrors.New("MissingBlobs failed"), @@ -2026,16 +2027,16 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ - MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + MissingBlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobID: "sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -2095,18 +2096,18 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", + "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", + "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", + "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", + "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", + "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", + "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", }, }, }, @@ -2114,7 +2115,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + BlobID: "sha256:53f13904cd95a8be98f2b176f937e5259f54a2fc55cf707c7b7bfc1abd797af6", BlobInfoAnything: true, }, @@ -2125,7 +2126,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + BlobID: "sha256:5c4ef5a20dc2904670cf3313a9c19b45d4ab2eeb57fd70436adff680c352dbc9", BlobInfoAnything: true, }, @@ -2136,7 +2137,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + BlobID: "sha256:5f4cda156ab01361bde987b12854918e4609e288f18b995279b2fa003942e887", BlobInfoAnything: true, }, @@ -2147,7 +2148,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + BlobID: "sha256:fec820dae7a39acb3c70b8992c82487ae81d0a307f5f3a902613874e2b6cb414", BlobInfoAnything: true, }, @@ -2164,17 +2165,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + MissingBlobIDs: []string{"sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobID: "sha256:19d98f2aba362091fd3f6f8dc0928abe22486e85c16842a1f9a58542bc1c2a81", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "",