Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Support for snap package manager #2017

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/future-architect/vuls/oval"
"github.com/future-architect/vuls/reporter"
"github.com/future-architect/vuls/util"
hver "github.com/hashicorp/go-version"
cvemodels "github.com/vulsio/go-cve-dictionary/models"
)

Expand Down Expand Up @@ -54,6 +55,10 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}

if err := DetectSnapCves(&r, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to detect Snap CVE: %w", err)
}

cpeURIs, owaspDCXMLPath := []string{}, ""
cpes := []Cpe{}
if len(r.Container.ContainerID) == 0 {
Expand Down Expand Up @@ -367,6 +372,83 @@ func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf c
return nil
}

func DetectSnapCves(r *models.ScanResult, cnf config.GoCveDictConf, logOpts logging.LogOpts) error {
detects := make(map[string]string)
client, err := newGoCveDictClient(&cnf, logOpts)
if err != nil {
return xerrors.Errorf("Failed to newGoCveDictClient. err: %w", err)
}
defer func() {
if err := client.closeDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()

for _, snap := range r.Snaps {
results, err := client.driver.GetCveIDsByProduct(snap.Name)
if err != nil {
return xerrors.Errorf("Failed to detectCveByCpeURI. err: %w", err)
}

vera, err := hver.NewVersion(snap.Version)
if err != nil {
continue
}

for _, row := range results {
if row.VersionEndExcluding != "" {
verb, err := hver.NewVersion(row.VersionEndExcluding)
if err != nil {
continue
}
if vera.LessThan(verb) {
detects[row.CveID] = snap.Name
}
} else if row.VersionEndIncluding != "" {
verb, err := hver.NewVersion(row.VersionEndIncluding)
if err != nil {
continue
}
if vera.LessThanOrEqual(verb) {
detects[row.CveID] = snap.Name
}
}
}
}

for cveId, snap := range detects {
v, ok := r.ScannedCves[cveId]
if ok {
if v.CveContents == nil {
v.CveContents = models.NewCveContents(models.CveContent{
Type: models.Nvd,
CveID: cveId,
})
} else {
v.CveContents[models.Nvd] = []models.CveContent{models.CveContent{
Type: models.Nvd,
CveID: cveId,
}}
}
v.Confidences.AppendIfMissing(models.NvdVendorProductMatch)
} else {
v = models.VulnInfo{
CveID: cveId,
CveContents: models.NewCveContents(models.CveContent{
Type: models.Nvd,
CveID: cveId,
}),
Confidences: models.Confidences{models.NvdVendorProductMatch},
}
}

v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{Name: snap})
r.ScannedCves[cveId] = v
}

return nil
}

// isPkgCvesDetactable checks whether CVEs is detactable with gost and oval from the result
func isPkgCvesDetactable(r *models.ScanResult) bool {
switch r.Family {
Expand Down
9 changes: 9 additions & 0 deletions models/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type Package struct {
NewRelease string `json:"newRelease"`
Arch string `json:"arch"`
Repository string `json:"repository"`
Type string `json:"type,omitempty"`
ModularityLabel string `json:"modularitylabel"`
Changelog *Changelog `json:"changelog,omitempty"`
AffectedProcs []AffectedProcess `json:",omitempty"`
Expand Down Expand Up @@ -456,3 +457,11 @@ func IsKernelSourcePackage(family, name string) bool {
return false
}
}

type Snaps map[string]Snap

type Snap struct {
Name string `json:"name"`
Version string `json:"version"`
Revision string `json:"revision"`
}
6 changes: 6 additions & 0 deletions models/scanresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type ScanResult struct {
RunningKernel Kernel `json:"runningKernel"`
Packages Packages `json:"packages"`
SrcPackages SrcPackages `json:",omitempty"`
Snaps Snaps `json:"snaps"`
EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules
WordPressPackages WordPressPackages `json:",omitempty"`
GitHubManifests DependencyGraphManifests `json:"gitHubManifests,omitempty"`
Expand Down Expand Up @@ -229,6 +230,11 @@ func (r ScanResult) FormatUpdatablePkgsSummary() string {
nUpdatable)
}

// FormatUpdatableSnapSummary returns a summary of updatable Snap packages
func (r ScanResult) FormatSnapSummary() string {
return fmt.Sprintf("Snap: %d packages", len(r.Snaps))
}

// FormatExploitCveSummary returns a summary of exploit cve
func (r ScanResult) FormatExploitCveSummary() string {
nExploitCve := 0
Expand Down
3 changes: 3 additions & 0 deletions reporter/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ func formatScanSummary(rs ...models.ScanResult) string {
fmt.Sprintf("%s%s", r.Family, r.Release),
r.FormatUpdatablePkgsSummary(),
}
if 0 < len(r.Snaps) {
cols = append(cols, r.FormatSnapSummary())
}
if 0 < len(r.WordPressPackages) {
cols = append(cols, fmt.Sprintf("%d WordPress pkgs", len(r.WordPressPackages)))
}
Expand Down
51 changes: 51 additions & 0 deletions scanner/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ type osPackages struct {
// installed source packages (Debian based only)
SrcPackages models.SrcPackages

// Snaps
Snaps models.Snaps

// Detected Vulnerabilities Key: CVE-ID
VulnInfos models.VulnInfos

Expand Down Expand Up @@ -546,6 +549,7 @@ func (l *base) convertToModel() models.ScanResult {
RunningKernel: l.Kernel,
Packages: l.Packages,
SrcPackages: l.SrcPackages,
Snaps: l.Snaps,
WordPressPackages: l.WordPress,
LibraryScanners: l.LibraryScanners,
WindowsKB: l.windowsKB,
Expand Down Expand Up @@ -1496,3 +1500,50 @@ func (l *base) pkgPs(getOwnerPkgs func([]string) ([]string, error)) error {
}
return nil
}

func (l *base) scanSnaps() error {
r := l.exec("snap version", noSudo)
if !r.isSuccess() {
return nil
}

l.log.Info("Scanning Snap packages.")

cmd := util.PrependProxyEnv("snap list")
r = l.exec(cmd, noSudo)
if !r.isSuccess() {
return xerrors.Errorf("Scanning snaps failed.")
}

snaps, err := l.parseSnapList(r.Stdout)
if err != nil {
return err
}

l.Snaps = snaps

// Snaps update automatically, therefore we do not check for updates.

return nil
}

func (l *base) parseSnapList(commandOutput string) (models.Snaps, error) {
snaps := models.Snaps{}
scanner := bufio.NewScanner(strings.NewReader(commandOutput))

// Skip header line
scanner.Scan()

for scanner.Scan() {
line := scanner.Text()
ss := strings.Fields(line)

snaps[ss[0]] = models.Snap{
Name: ss[0],
Version: ss[1],
Revision: ss[2],
}
}

return snaps, nil
}
40 changes: 40 additions & 0 deletions scanner/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,43 @@ func Test_findPortScanSuccessOn(t *testing.T) {
})
}
}

func TestParseSnapList(t *testing.T) {
var tests = struct {
in string
expected models.Snaps
}{
`Name Version Rev Tracking Publisher Notes
bare 1.0 5 latest/stable canonical** base
core22 20240731 1564 latest/stable canonical** base
firefox 129.0.2-1 4793 latest/stable/… mozilla** -
firmware-updater 0+git.5007558 127 1/stable/… canonical** -
gnome-42-2204 0+git.510a601 176 latest/stable/… canonical** -
gtk-common-themes 0.1-81-g442e511 1535 latest/stable/… canonical** -
snap-store 0+git.e3dd562 1173 2/stable/… canonical** -
snapd 2.63 21759 latest/stable canonical** snapd
snapd-desktop-integration 0.9 178 latest/stable/… canonical** -`,
models.Snaps{
"bare": models.Snap{Name: "bare", Version: "1.0", Revision: "5"},
"core22": models.Snap{Name: "core22", Version: "20240731", Revision: "1564"},
"firefox": models.Snap{Name: "firefox", Version: "129.0.2-1", Revision: "4793"},
"firmware-updater": models.Snap{Name: "firmware-updater", Version: "0+git.5007558", Revision: "127"},
"gnome-42-2204": models.Snap{Name: "gnome-42-2204", Version: "0+git.510a601", Revision: "176"},
"gtk-common-themes": models.Snap{Name: "gtk-common-themes", Version: "0.1-81-g442e511", Revision: "1535"},
"snap-store": models.Snap{Name: "snap-store", Version: "0+git.e3dd562", Revision: "1173"},
"snapd": models.Snap{Name: "snapd", Version: "2.63", Revision: "21759"},
"snapd-desktop-integration": models.Snap{Name: "snapd-desktop-integration", Version: "0.9", Revision: "178"},
},
}
l := base{}
actual, err := l.parseSnapList(tests.in)
if err != nil {
t.Errorf("Error occurred. in: %s, err: %+v", tests.in, err)
return
}
for _, e := range tests.expected {
if !reflect.DeepEqual(e, actual[e.Name]) {
t.Errorf("expected %v, actual %v", e, actual[e.Name])
}
}
}
5 changes: 5 additions & 0 deletions scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type osTypeInterface interface {

parseInstalledPackages(string) (models.Packages, models.SrcPackages, error)

scanSnaps() error

runningContainers() ([]config.Container, error)
exitedContainers() ([]config.Container, error)
allContainers() ([]config.Container, error)
Expand Down Expand Up @@ -965,6 +967,9 @@ func (s Scanner) getScanResults(scannedAt time.Time) (results models.ScanResults
if err = o.postScan(); err != nil {
return err
}
if err = o.scanSnaps(); err != nil {
return err
}
}
if o.getServerInfo().Module.IsScanPort() {
if err = o.scanPorts(); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,14 @@ func setChangelogLayout(g *gocui.Gui) error {
lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s", p.PID, p.Name, ports))
}
}

for _, snap := range currentScanResult.Snaps {
if snap.Name == affected.Name {
line := fmt.Sprintf("[snap] %s-%s", snap.Name, snap.Version)
lines = append(lines, line)
break
}
}
}
sort.Strings(vinfo.CpeURIs)
for _, uri := range vinfo.CpeURIs {
Expand Down