blob: 91cb83a8c17334a6d714dfcb8935fe82734dcdd0 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cvelist is used to fetch and parse information from
// https://github.com/CVEProject/cvelist
package cvelist
import (
"encoding/json"
"fmt"
"io"
"path"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"golang.org/x/vulndb/internal/cveschema"
)
// Run clones the CVEProject/cvelist repository and compares the files to the
// existing triaged-cve-list.
func Run(triaged map[string]bool) error {
// 1. Clone the repo.
repo, root, err := cloneRepo(cvelistRepoURL)
if err != nil {
return err
}
return walkRepo(repo, root, "", triaged)
}
const cvelistRepoURL = "https://github.com/CVEProject/cvelist"
// cloneRepo returns a repo and tree object for the repo at HEAD by
// cloning the repo at repoURL.
func cloneRepo(repoURL string) (repo *git.Repository, root *object.Tree, err error) {
repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
URL: repoURL,
ReferenceName: plumbing.HEAD,
SingleBranch: true,
Depth: 1,
Tags: git.NoTags,
})
if err != nil {
return nil, nil, err
}
refName := plumbing.HEAD
ref, err := repo.Reference(refName, true)
if err != nil {
return nil, nil, err
}
commit, err := repo.CommitObject(ref.Hash())
if err != nil {
return nil, nil, err
}
root, err = repo.TreeObject(commit.TreeHash)
if err != nil {
return nil, nil, err
}
return repo, root, nil
}
// walkRepo looks at the files in t, recursively, and check if it is a CVE that
// needs to be manually triaged.
func walkRepo(r *git.Repository, t *object.Tree, dirpath string, triaged map[string]bool) (err error) {
var recent []object.TreeEntry
for _, e := range t.Entries {
if e.Mode == filemode.Dir && strings.HasPrefix(e.Name, "202") {
recent = append(recent, e)
}
}
for _, e := range recent {
switch e.Mode {
case filemode.Dir:
dp := path.Join(dirpath, e.Name)
t2, err := r.TreeObject(e.Hash)
if err != nil {
return err
}
if err := walkRepo(r, t2, dp, triaged); err != nil {
return err
}
default:
if !strings.HasPrefix(e.Name, "CVE-") {
continue
}
cveID := strings.TrimSuffix(e.Name, ".json")
if triaged[cveID] {
continue
}
blob, err := r.BlobObject(e.Hash)
if err != nil {
return fmt.Errorf("r.BlobObject: %v", err)
}
src, err := blob.Reader()
if err != nil {
_ = src.Close()
return fmt.Errorf("blob.Reader: %v", err)
}
_, err = parseCVE(src)
if err != nil {
_ = src.Close()
filename := path.Join(dirpath, e.Name)
return fmt.Errorf("parseCVE(%q, src): %v", filename, err)
}
// TODO: implement triage CVE logic
if err := src.Close(); err != nil {
return fmt.Errorf("src.Close: %v", err)
}
}
}
return nil
}
// parseCVEJSON parses a CVE file following the CVE JSON format:
// https://github.com/CVEProject/automation-working-group/blob/master/cve_json_schema/DRAFT-JSON-file-format-v4.md
func parseCVE(src io.Reader) (_ *cveschema.CVE, err error) {
var c cveschema.CVE
d := json.NewDecoder(src)
if err := d.Decode(&c); err != nil {
return nil, fmt.Errorf("d.Decode: %v", err)
}
return &c, nil
}