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 flatpak #2018

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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 := DetectFlatpakCves(&r, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to detect Flatpak 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 DetectFlatpakCves(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 _, flatpak := range r.Flatpaks {
results, err := client.driver.GetCveIDsByProduct(flatpak.Name)
if err != nil {
return xerrors.Errorf("Failed to detectCveByCpeURI. err: %w", err)
}

vera, err := hver.NewVersion(flatpak.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] = flatpak.Name
}
} else if row.VersionEndIncluding != "" {
verb, err := hver.NewVersion(row.VersionEndIncluding)
if err != nil {
continue
}
if vera.LessThanOrEqual(verb) {
detects[row.CveID] = flatpak.Name
}
}
}
}

for cveId, flatpak := 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{{
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: flatpak})
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
11 changes: 11 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,13 @@ func IsKernelSourcePackage(family, name string) bool {
return false
}
}

type Flatpaks map[string]Flatpak

type Flatpak struct {
Application string `json:"application"`
Name string `json:"name"`
Arch string `json:"arch"`
Version string `json:"version"`
NewVersion string `json:"newVersion"`
}
17 changes: 17 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"`
Flatpaks Flatpaks `json:"flatpaks"`
EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules
WordPressPackages WordPressPackages `json:",omitempty"`
GitHubManifests DependencyGraphManifests `json:"gitHubManifests,omitempty"`
Expand Down Expand Up @@ -229,6 +230,22 @@ func (r ScanResult) FormatUpdatablePkgsSummary() string {
nUpdatable)
}

// FormatUpdatableFlatpakSummary returns a summary of updatable Flatpak packages
func (r ScanResult) FormatUpdatableFlatpakSummary() string {
nUpdatable := 0
for _, f := range r.Flatpaks {
if f.NewVersion == "" {
continue
}
if f.Version != f.NewVersion {
nUpdatable++
}
}
return fmt.Sprintf("Flatpak: %d apps, %d updatable",
len(r.Flatpaks),
nUpdatable)
}

// 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.Flatpaks) {
cols = append(cols, r.FormatUpdatableFlatpakSummary())
}
if 0 < len(r.WordPressPackages) {
cols = append(cols, fmt.Sprintf("%d WordPress pkgs", len(r.WordPressPackages)))
}
Expand Down
87 changes: 87 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

// Flatpaks
Flatpaks models.Flatpaks

// 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,
Flatpaks: l.Flatpaks,
WordPressPackages: l.WordPress,
LibraryScanners: l.LibraryScanners,
WindowsKB: l.windowsKB,
Expand Down Expand Up @@ -1496,3 +1500,86 @@ func (l *base) pkgPs(getOwnerPkgs func([]string) ([]string, error)) error {
}
return nil
}

func (l *base) scanFlatpaks() error {
r := l.exec("flatpak --version", noSudo)

if !r.isSuccess() {
return nil
}

l.log.Info("Scanning Flatpak applications.")

cmd := util.PrependProxyEnv("flatpak list --app --columns=application,arch,version")
r = l.exec(cmd, noSudo)

if !r.isSuccess() {
return xerrors.Errorf("Failed to get the list of flatpak applications.")
}

flatpaks, err := l.parseFlatpakList(r.Stdout)

if err != nil {
return err
}

cmd = util.PrependProxyEnv("flatpak remote-ls --app --updates --columns=application,arch,version")
r = l.exec(cmd, noSudo)

if !r.isSuccess() {
// Only log this error.
err := xerrors.Errorf("Failed to check flatpak updates.")
l.log.Warnf("err: %+v", err)
l.warns = append(l.warns, err)
}

l.Flatpaks = l.fillUpdatableFlatpaks(flatpaks, r.Stdout)

return nil
}

func (l *base) parseFlatpakList(consoleOutput string) (models.Flatpaks, error) {
flatpaks := models.Flatpaks{}
scanner := bufio.NewScanner(strings.NewReader(consoleOutput))

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

if len(ss) != 3 {
continue
}

nameSegments := strings.Split(ss[0], ".")
name := nameSegments[len(nameSegments)-1]
name = strings.ToLower(name)

flatpaks[ss[0]] = models.Flatpak{
Application: ss[0],
Name: name,
Arch: ss[1],
Version: ss[2],
}
}

return flatpaks, nil
}

func (l *base) fillUpdatableFlatpaks(flatpaks models.Flatpaks, consoleOutput string) models.Flatpaks {
scanner := bufio.NewScanner(strings.NewReader(consoleOutput))

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

if len(ss) != 3 {
continue
}

updatableApp := flatpaks[ss[0]]
updatableApp.NewVersion = ss[2]
flatpaks[ss[0]] = updatableApp
}

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

func TestParseFlatpakList(t *testing.T) {

var tests = struct {
in string
expected models.Flatpaks
}{
`Application ID Arch Version
com.github.d4nj1.tlpui x86_64 1.7.1
com.github.finefindus.eyedropper x86_64 2.0.1
com.github.taiko2k.avvie x86_64 2.4`,
models.Flatpaks{
"com.github.d4nj1.tlpui": models.Flatpak{Application: "com.github.d4nj1.tlpui", Name: "tlpui", Arch: "x86_64", Version: "1.7.1"},
"com.github.finefindus.eyedropper": models.Flatpak{Application: "com.github.finefindus.eyedropper", Name: "eyedropper", Arch: "x86_64", Version: "2.0.1"},
"com.github.taiko2k.avvie": models.Flatpak{Application: "com.github.taiko2k.avvie", Name: "avvie", Arch: "x86_64", Version: "2.4"},
},
}

l := base{}
actual, err := l.parseFlatpakList(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.Application]) {
t.Errorf("expected %v, actual %v", e, actual[e.Application])
}
}
}

func TestFillUpdatableFlatpaks(t *testing.T) {

type args struct {
flatpaks models.Flatpaks
consoleOutput string
}

var test = struct {
args args
expected models.Flatpaks
}{
args{
models.Flatpaks{
"org.videolan.VLC": models.Flatpak{Application: "org.videolan.VLC", Name: "vlc", Arch: "x86_64", Version: "3.0.19"},
},
`Application ID Arch Version
org.videolan.VLC x86_64 3.0.21`},
models.Flatpaks{
"org.videolan.VLC": models.Flatpak{Application: "org.videolan.VLC", Name: "vlc", Arch: "x86_64", Version: "3.0.19", NewVersion: "3.0.21"},
},
}

l := base{}
actual := l.fillUpdatableFlatpaks(test.args.flatpaks, test.args.consoleOutput)

for _, e := range test.expected {
if !reflect.DeepEqual(e, actual[e.Application]) {
t.Errorf("expected %v, actual %v", e, actual[e.Application])
}
}
}
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)

scanFlatpaks() 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.scanFlatpaks(); 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 _, flatpak := range currentScanResult.Flatpaks {
if flatpak.Name == affected.Name {
line := fmt.Sprintf("[flatpak] %s-%s", flatpak.Name, flatpak.Version)
lines = append(lines, line)
break
}
}
}
sort.Strings(vinfo.CpeURIs)
for _, uri := range vinfo.CpeURIs {
Expand Down