github.com/google/osv-scalibr@v0.4.1/enricher/govulncheck/source/govulncheck_test.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 // Copyright 2025 Google LLC 16 // 17 // Licensed under the Apache License, Version 2.0 (the "License"); 18 // you may not use this file except in compliance with the License. 19 // You may obtain a copy of the License at 20 // 21 // http://www.apache.org/licenses/LICENSE-2.0 22 // 23 // Unless required by applicable law or agreed to in writing, software 24 // distributed under the License is distributed on an "AS IS" BASIS, 25 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 // See the License for the specific language governing permissions and 27 // limitations under the License. 28 package source_test 29 30 import ( 31 "os" 32 "path/filepath" 33 "testing" 34 35 "github.com/google/go-cmp/cmp" 36 37 cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" 38 govcsource "github.com/google/osv-scalibr/enricher/govulncheck/source" 39 vulnpb "github.com/ossf/osv-schema/bindings/go/osvschema" 40 "google.golang.org/protobuf/encoding/protojson" 41 42 "github.com/google/osv-scalibr/enricher" 43 "github.com/google/osv-scalibr/extractor" 44 "github.com/google/osv-scalibr/extractor/filesystem/language/golang/gomod" 45 scalibrfs "github.com/google/osv-scalibr/fs" 46 "github.com/google/osv-scalibr/inventory" 47 "github.com/google/osv-scalibr/inventory/vex" 48 "github.com/google/osv-scalibr/purl" 49 ) 50 51 const testProjPath = "./testdata/goproj" 52 const vulndbPath = "./testdata/vulndb" 53 const reachableVulnID = "GO-2023-1558" 54 const unreachableVulnID1 = "GO-2021-0053" 55 const unreachableVulnID2 = "GO-2024-2937" 56 57 func TestEnricher(t *testing.T) { 58 testCases := []struct { 59 name string 60 vulnID string 61 expectedSignals []*vex.FindingExploitabilitySignal 62 }{ 63 { 64 name: "reachable vuln", 65 vulnID: reachableVulnID, 66 expectedSignals: nil, 67 }, 68 { 69 name: "unreachable vuln 1 (package imported, vulnerable function not called)", 70 vulnID: unreachableVulnID1, 71 expectedSignals: []*vex.FindingExploitabilitySignal{{ 72 Plugin: govcsource.Name, 73 Justification: vex.VulnerableCodeNotInExecutePath, 74 }}, 75 }, 76 { 77 name: "unreachable vuln 2 (package not imported at all, just present in go.mod)", 78 vulnID: unreachableVulnID2, 79 expectedSignals: []*vex.FindingExploitabilitySignal{{ 80 Plugin: govcsource.Name, 81 Justification: vex.VulnerableCodeNotInExecutePath, 82 }}, 83 }, 84 } 85 86 pkgs := setupPackages() 87 vulns := setupPackageVulns(t, pkgs) 88 input := enricher.ScanInput{ 89 ScanRoot: &scalibrfs.ScanRoot{ 90 Path: testProjPath, 91 FS: scalibrfs.DirFS("."), 92 }, 93 } 94 95 inv := inventory.Inventory{ 96 Packages: pkgs, 97 PackageVulns: vulns, 98 } 99 100 gvcClient, err := newMockGovulncheckClient("testdata/govulncheckinput.json", nil, true) 101 if err != nil { 102 t.Fatalf("failed to create mock govulncheck client: %v", err) 103 } 104 enr := govcsource.NewWithClient(&cpb.PluginConfig{}, gvcClient) 105 106 err = enr.Enrich(t.Context(), &input, &inv) 107 108 if err != nil { 109 t.Fatalf("govulncheck enrich failed: %s", err) 110 } 111 112 vulnsByID := make(map[string]*inventory.PackageVuln) 113 for _, v := range inv.PackageVulns { 114 vulnsByID[v.Vulnerability.Id] = v 115 } 116 117 for _, tc := range testCases { 118 t.Run(tc.name, func(t *testing.T) { 119 vuln, ok := vulnsByID[tc.vulnID] 120 if !ok { 121 t.Fatalf("vulnerability %s not found in inventory", tc.vulnID) 122 } 123 124 if diff := cmp.Diff(tc.expectedSignals, vuln.ExploitabilitySignals); diff != "" { 125 t.Errorf("ExploitabilitySignals mismatch (-want +got):\n%s", diff) 126 } 127 }) 128 } 129 } 130 131 func setupPackages() []*extractor.Package { 132 pkgs := []*extractor.Package{ 133 { 134 Name: "stdlib", 135 Version: "1.19", 136 PURLType: purl.TypeGolang, 137 Locations: []string{filepath.Join(".", "go.mod")}, 138 Plugins: []string{gomod.Name}, 139 }, 140 // Affected by GO-2021-0053, but we don't actually call the vulnerable func 141 { 142 Name: "github.com/gogo/protobuf", 143 Version: "1.3.1", 144 PURLType: purl.TypeGolang, 145 Locations: []string{filepath.Join(".", "go.mod")}, 146 Plugins: []string{gomod.Name}, 147 }, 148 // Affected by GO-2023-1558, and we do call the vulnerable func 149 { 150 Name: "github.com/ipfs/go-bitfield", 151 Version: "1.0.0", 152 PURLType: purl.TypeGolang, 153 Locations: []string{filepath.Join(".", "go.mod")}, 154 Plugins: []string{gomod.Name}, 155 }, 156 // Affected by GO-2024-2937, but only present in the go.mod file, nor present in the code 157 { 158 Name: "golang.org/x/image", 159 Version: "0.4.0", 160 PURLType: purl.TypeGolang, 161 Locations: []string{filepath.Join(".", "go.mod")}, 162 Plugins: []string{gomod.Name}, 163 }, 164 } 165 166 return pkgs 167 } 168 169 func setupPackageVulns(t *testing.T, pkgs []*extractor.Package) []*inventory.PackageVuln { 170 t.Helper() 171 pkgVulns := []*inventory.PackageVuln{ 172 { 173 Vulnerability: loadVuln(t, reachableVulnID), 174 Package: getRefToPackage(pkgs, "github.com/ipfs/go-bitfield"), 175 }, 176 { 177 Vulnerability: loadVuln(t, unreachableVulnID1), 178 Package: getRefToPackage(pkgs, "github.com/gogo/protobuf"), 179 }, 180 { 181 Vulnerability: loadVuln(t, unreachableVulnID2), 182 Package: getRefToPackage(pkgs, "golang.org/x/image"), 183 }, 184 } 185 186 return pkgVulns 187 } 188 189 func loadVuln(t *testing.T, vulnID string) *vulnpb.Vulnerability { 190 t.Helper() 191 path := filepath.Join(vulndbPath, vulnID+".json") 192 content, err := os.ReadFile(path) 193 if err != nil { 194 t.Fatalf("failed to read vuln file %s: %v", path, err) 195 } 196 197 vuln := &vulnpb.Vulnerability{} 198 if err := protojson.Unmarshal(content, vuln); err != nil { 199 t.Fatalf("failed to unmarshal vuln from %s: %v", path, err) 200 } 201 202 return vuln 203 } 204 205 func getRefToPackage(pkgs []*extractor.Package, name string) *extractor.Package { 206 for _, pkg := range pkgs { 207 if pkg.Name == name { 208 return pkg 209 } 210 } 211 return nil 212 }