github.com/google/osv-scalibr@v0.4.1/annotator/osduplicate/rpm/rpm_linux.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build linux 16 17 package rpm 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io/fs" 24 "os" 25 "path" 26 "path/filepath" 27 "slices" 28 "strings" 29 30 rpmdb "github.com/erikvarga/go-rpmdb/pkg" 31 "github.com/google/osv-scalibr/annotator" 32 "github.com/google/osv-scalibr/annotator/osduplicate" 33 "github.com/google/osv-scalibr/extractor" 34 scalibrfs "github.com/google/osv-scalibr/fs" 35 "github.com/google/osv-scalibr/inventory" 36 "github.com/google/osv-scalibr/inventory/vex" 37 "github.com/google/osv-scalibr/log" 38 39 // SQLite driver needed for parsing rpmdb.sqlite files. 40 _ "modernc.org/sqlite" 41 ) 42 43 var ( 44 // Directories and files where RPM descriptor packages can be found. 45 rpmDirectories = []string{ 46 "usr/lib/sysimage/rpm/", 47 "var/lib/rpm/", 48 "usr/share/rpm/", 49 } 50 rpmFilenames = []string{ 51 // Berkley DB (old format) 52 "Packages", 53 // NDB (very rare alternative to sqlite) 54 "Packages.db", 55 // SQLite3 (new format) 56 "rpmdb.sqlite", 57 } 58 ) 59 60 // Annotate adds annotations to language packages that have already been found in RPM OS packages. 61 func (a *Annotator) Annotate(ctx context.Context, input *annotator.ScanInput, results *inventory.Inventory) error { 62 locationToPKGs := osduplicate.BuildLocationToPKGsMap(results, input.ScanRoot) 63 64 errs := []error{} 65 for _, dir := range rpmDirectories { 66 for _, file := range rpmFilenames { 67 // Return if canceled or exceeding deadline. 68 if err := ctx.Err(); err != nil { 69 errs = append(errs, fmt.Errorf("%s halted at %q because of context error: %w", a.Name(), input.ScanRoot.Path, err)) 70 break 71 } 72 73 dbPath := path.Join(dir, file) 74 // Skip files that don't exist. 75 if _, err := fs.Stat(input.ScanRoot.FS, dbPath); err != nil { 76 continue 77 } 78 79 if err := a.annotatePackagesInRPMDB(ctx, input.ScanRoot, dbPath, locationToPKGs); err != nil { 80 return err 81 } 82 } 83 } 84 85 return errors.Join(errs...) 86 } 87 88 func (a *Annotator) annotatePackagesInRPMDB(ctx context.Context, root *scalibrfs.ScanRoot, dbPath string, locationToPKGs map[string][]*extractor.Package) error { 89 realDBPath, err := scalibrfs.GetRealPath(root, dbPath, nil) 90 if err != nil { 91 return fmt.Errorf("GetRealPath(%v, %v): %w", root, dbPath, err) 92 } 93 if root.IsVirtual() { 94 // The file got copied to a temporary dir, remove it at the end. 95 defer func() { 96 dir := filepath.Dir(realDBPath) 97 if err := os.RemoveAll(dir); err != nil { 98 log.Errorf("os.RemoveAll(%q): %v", dir, err) 99 } 100 }() 101 } 102 103 db, err := rpmdb.Open(realDBPath) 104 if err != nil { 105 return err 106 } 107 defer db.Close() 108 109 var pkgs []*rpmdb.PackageInfo 110 if a.Timeout == 0 { 111 pkgs, err = db.ListPackages() 112 if err != nil { 113 return err 114 } 115 } else { 116 ctx, cancelFunc := context.WithTimeout(ctx, a.Timeout) 117 defer cancelFunc() 118 119 pkgs, err = db.ListPackagesWithContext(ctx) 120 if err != nil { 121 return err 122 } 123 } 124 125 for _, pkg := range pkgs { 126 for i, base := range pkg.BaseNames { 127 if len(pkg.DirIndexes) <= i { 128 return fmt.Errorf("malformed RPM directory index: want %d entries, got %d", i+1, len(pkg.DirIndexes)) 129 } 130 dir := pkg.DirNames[pkg.DirIndexes[i]] 131 // Remove leading '/' since SCALIBR fs paths don't include that. 132 path := strings.TrimPrefix(dir+base, "/") 133 134 if pkgs, ok := locationToPKGs[path]; ok { 135 for _, pkg := range pkgs { 136 if !slices.ContainsFunc(pkg.ExploitabilitySignals, func(s *vex.PackageExploitabilitySignal) bool { 137 return s.Plugin == Name 138 }) { 139 pkg.ExploitabilitySignals = append(pkg.ExploitabilitySignals, &vex.PackageExploitabilitySignal{ 140 Plugin: Name, 141 Justification: vex.ComponentNotPresent, 142 MatchesAllVulns: true, 143 }) 144 } 145 } 146 } 147 } 148 } 149 150 return nil 151 }