Skip to content

Commit

Permalink
feat(vuln): handle scanning conan v2.x lockfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
dus7eh committed Mar 20, 2024
1 parent 3177924 commit 5cb6032
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 25 deletions.
91 changes: 70 additions & 21 deletions pkg/dependency/parser/c/conan/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
xio "github.com/aquasecurity/trivy/pkg/x/io"
)

type LockFile struct {
type LockFileV1 struct {
GraphLock GraphLock `json:"graph_lock"`
}

Expand All @@ -30,23 +30,19 @@ type Node struct {
EndLine int
}

type LockFileV2 struct {
Requires []string `json:"requires"`
}

type Parser struct{}

func NewParser() types.Parser {
return &Parser{}
}

func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
var lock LockFile
input, err := io.ReadAll(r)
if err != nil {
return nil, nil, xerrors.Errorf("failed to read canon lock file: %w", err)
}
if err := jfather.Unmarshal(input, &lock); err != nil {
return nil, nil, xerrors.Errorf("failed to decode canon lock file: %w", err)
}

// Get a list of direct dependencies
func (p *Parser) parseRequirementsV1(lock LockFileV1) ([]types.Library, []types.Dependency, error) {
var libs []types.Library
var deps []types.Dependency
var directDeps []string
if root, ok := lock.GraphLock.Nodes["0"]; ok {
directDeps = root.Requires
Expand All @@ -58,7 +54,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
if node.Ref == "" {
continue
}
lib, err := parseRef(node)
lib, err := parseRefV1(node)
if err != nil {
log.Logger.Debug(err)
continue
Expand All @@ -72,8 +68,6 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
}

// Parse dependency graph
var libs []types.Library
var deps []types.Dependency
for i, node := range lock.GraphLock.Nodes {
lib, ok := parsed[i]
if !ok {
Expand All @@ -98,21 +92,64 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
return libs, deps, nil
}

func parseRef(node Node) (types.Library, error) {
func (p *Parser) parseRequirementsV2(lock LockFileV2) ([]types.Library, []types.Dependency, error) {
var libs []types.Library

for _, req := range lock.Requires {
lib, _ := parseRefV2(req)
libs = append(libs, lib)
}
return libs, []types.Dependency{}, nil
}

func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
var lockV1 LockFileV1
var lockV2 LockFileV2
input, err := io.ReadAll(r)
if err != nil {
return nil, nil, xerrors.Errorf("failed to read conan lock file: %w", err)
}

// try to parse requirements as conan v1.x
if err := jfather.Unmarshal(input, &lockV1); err != nil {
return nil, nil, xerrors.Errorf("failed to decode conan lock file: %w", err)
}
if lockV1.GraphLock.Nodes != nil {
log.Logger.Debug("Handling conan lockfile as v1.x")
return p.parseRequirementsV1(lockV1)
} else {
// try to parse requirements as conan v2.x
log.Logger.Debug("Handling conan lockfile as v2.x")
if err := jfather.Unmarshal(input, &lockV2); err != nil {
return nil, nil, xerrors.Errorf("failed to decode conan lock file: %w", err)
}
return p.parseRequirementsV2(lockV2)
}
}

func parsePackage(text string) (string, string, error) {
// full ref format: package/version@user/channel#rrev:package_id#prev
// various examples:
// 'pkga/0.1@user/testing'
// 'pkgb/0.1.0'
// 'pkgc/system'
// 'pkgd/0.1.0#7dcb50c43a5a50d984c2e8fa5898bf18'
ss := strings.Split(strings.Split(strings.Split(node.Ref, "@")[0], "#")[0], "/")
ss := strings.Split(strings.Split(strings.Split(text, "@")[0], "#")[0], "/")
if len(ss) != 2 {
return types.Library{}, xerrors.Errorf("Unable to determine conan dependency: %q", node.Ref)
return "", "", xerrors.Errorf("Unable to determine conan dependency: %q", text)
}
return ss[0], ss[1], nil
}

func parseRefV1(node Node) (types.Library, error) {
name, version, err := parsePackage(node.Ref)
if err != nil {
return types.Library{}, err
}
return types.Library{
ID: dependency.ID(ftypes.Conan, ss[0], ss[1]),
Name: ss[0],
Version: ss[1],
ID: dependency.ID(ftypes.Conan, name, version),
Name: name,
Version: version,
Locations: []types.Location{
{
StartLine: node.StartLine,
Expand All @@ -122,6 +159,18 @@ func parseRef(node Node) (types.Library, error) {
}, nil
}

func parseRefV2(req string) (types.Library, error) {
name, version, err := parsePackage(req)
if err != nil {
return types.Library{}, err
}
return types.Library{
ID: dependency.ID(ftypes.Conan, name, version),
Name: name,
Version: version,
}, nil
}

// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
func (n *Node) UnmarshalJSONWithMetadata(node jfather.Node) error {
if err := node.Decode(&n); err != nil {
Expand Down
25 changes: 21 additions & 4 deletions pkg/dependency/parser/c/conan/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestParse(t *testing.T) {
}{
{
name: "happy path",
inputFile: "testdata/happy.lock",
inputFile: "testdata/happy_v1_case1.lock",
wantLibs: []types.Library{
{
ID: "pkga/0.0.1",
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestParse(t *testing.T) {
},
{
name: "happy path. lock file with revisions support",
inputFile: "testdata/happy2.lock",
inputFile: "testdata/happy_v1_case2.lock",
wantLibs: []types.Library{
{
ID: "openssl/3.0.3",
Expand Down Expand Up @@ -105,13 +105,30 @@ func TestParse(t *testing.T) {
},
},
},
{
name: "happy path conan v2",
inputFile: "testdata/happy_v2.lock",
wantLibs: []types.Library{
{
ID: "matrix/1.3",
Name: "matrix",
Version: "1.3",
},
{
ID: "sound32/1.0",
Name: "sound32",
Version: "1.0",
},
},
wantDeps: []types.Dependency{},
},
{
name: "happy path. lock file without dependencies",
inputFile: "testdata/empty.lock",
inputFile: "testdata/empty_v1.lock",
},
{
name: "sad path. wrong ref format",
inputFile: "testdata/sad.lock",
inputFile: "testdata/sad_v1.lock",
},
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/dependency/parser/c/conan/testdata/happy_v2.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.5",
"requires": [
"sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
"matrix/1.3#905c3f0babc520684c84127378fefdd0%1675278900.0103245"
],
"build_requires": [
"automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50%1701120593.68",
"autoconf/2.71#00a1e46d8ba5baaf7f10d64c1a6a0342%1709043523.063"
],
"python_requires": []
}

0 comments on commit 5cb6032

Please sign in to comment.