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  }