nosudo on CentOS and Fetch Changelogs on Amazon, RHEL (#448)

* Use repoquery for no sudo and avoid unintended line feed of yum or rpm. #444

* Change data type of enablerepo in config.toml. string to array

* Fetch yum changelogs at once then grep CVE-IDs

* Fix changelog parse logic and Update Gopkg
This commit is contained in:
Kota Kanbe
2017-07-18 15:54:25 +09:00
committed by kota kanbe
parent 738e9fb119
commit a9ebac3818
16 changed files with 944 additions and 916 deletions

View File

@@ -1,6 +1,5 @@
language: go
go:
- 1.7
- 1.8

61
Gopkg.lock generated
View File

@@ -2,10 +2,10 @@
[[projects]]
branch = "master"
name = "github.com/Azure/azure-storage-go"
packages = ["."]
revision = "32cfbe17a139c17f84be16bdf8f9c45c840a046b"
name = "github.com/Azure/azure-sdk-for-go"
packages = ["storage"]
revision = "59c277f1b488b81b1a5f944212f25b69bea8ece3"
version = "v10.1.0-beta"
[[projects]]
name = "github.com/Azure/go-autorest"
@@ -14,10 +14,10 @@
version = "v8.1.0"
[[projects]]
branch = "master"
name = "github.com/BurntSushi/toml"
packages = ["."]
revision = "a368813c5e648fee92e5f6c30e3944ff9d5e8895"
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
[[projects]]
name = "github.com/asaskevich/govalidator"
@@ -27,14 +27,15 @@
[[projects]]
name = "github.com/aws/aws-sdk-go"
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/request","aws/session","aws/signer/v4","private/endpoints","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","private/waiter","service/s3","service/sts"]
revision = "5b341290c488aa6bd76b335d819b4a68516ec3ab"
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/s3","service/sts"]
revision = "6d7fc1a00fcae6bbb53550f4a0b98324fd7aa250"
version = "v1.10.12"
[[projects]]
name = "github.com/boltdb/bolt"
packages = ["."]
revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9"
version = "v1.3.0"
revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8"
version = "v1.3.1"
[[projects]]
name = "github.com/cenkalti/backoff"
@@ -63,8 +64,8 @@
[[projects]]
name = "github.com/go-redis/redis"
packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"]
revision = "e14976b254c5bc5f399dd0ae9314b1d02a176897"
version = "v6.5.0"
revision = "da63fe7def48e378caf9539abf64b9b1e37bc01e"
version = "v6.5.3"
[[projects]]
name = "github.com/go-sql-driver/mysql"
@@ -109,10 +110,10 @@
version = "0.2.2"
[[projects]]
branch = "master"
name = "github.com/jroimartin/gocui"
packages = ["."]
revision = "612b0b2987ec1a6af46d7008cef1efd4b3898346"
revision = "4e9ce9a8e26f2ef33dfe297dbdfca148733b6b9b"
version = "v0.3.0"
[[projects]]
branch = "master"
@@ -128,9 +129,15 @@
[[projects]]
branch = "master"
name = "github.com/knqyf263/go-rpm-version"
packages = ["."]
revision = "74609b86c936dff800c69ec89fcf4bc52d5f13a4"
[[projects]]
name = "github.com/kotakanbe/go-cve-dictionary"
packages = ["config","db","jvn","log","models","nvd","util"]
revision = "89e381b4e7e5a31097bbd5779cbb555f5bd3fe87"
version = "v0.1.1"
[[projects]]
name = "github.com/kotakanbe/go-pingscanner"
@@ -153,14 +160,14 @@
[[projects]]
name = "github.com/labstack/gommon"
packages = ["color","log"]
revision = "1121fd3e243c202482226a7afe4dcd07ffc4139a"
version = "v0.2.1"
revision = "779b8a8b9850a97acba6a3fe20feb628c39e17c1"
version = "0.2.2"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [".","hstore","oid"]
revision = "8837942c3e09574accbc5f150e2c5e057189cace"
revision = "dd1fe2071026ce53f36a39112e645b4d4f5793a4"
[[projects]]
name = "github.com/mattn/go-colorable"
@@ -202,7 +209,7 @@
branch = "master"
name = "github.com/nsf/termbox-go"
packages = ["."]
revision = "72800b73ab9a3c78df350738298b0361354772ff"
revision = "4ed959e0540971545eddb8c75514973d670cf739"
[[projects]]
name = "github.com/parnurzeal/gorequest"
@@ -232,7 +239,7 @@
branch = "master"
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "3d4380f53a34dcdc95f0c1db702615992b38d9a4"
revision = "5ff5dd844dfeb4e23e27528f79f1f845bc8bb78f"
[[projects]]
branch = "master"
@@ -252,39 +259,33 @@
packages = ["oval"]
revision = "003ac9af5fffac6c97ab1def025d2cb73e88469a"
[[projects]]
branch = "master"
name = "go4.org"
packages = ["syncutil"]
revision = "034d17a462f7b2dcd1a4a73553ec5357ff6e6c6e"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"]
revision = "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d"
revision = "7f7c0c2d75ebb4e32a21396ce36e87b6dadc91c9"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","idna","publicsuffix"]
revision = "455220fa52c866a8aa14ff5e8cc68cde16b8395e"
revision = "b3756b4b77d7b13260a0a2ec658753cf48922eac"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "90796e5a05ce440b41c768bd9af257005e470461"
revision = "4cd6d1a821c7175768725b55ca82f14683a29ea4"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = ["internal/gen","internal/triegen","internal/ucd","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "6353ef0f924300eea566d3438817aa4d3374817e"
revision = "836efe42bb4aa16aaa17b9c155d8813d336ed720"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "a6b387c74e75e1f971ee643c8904f6fd4e3dfdb7fa36119ab7bc28d9cfd66427"
inputs-digest = "269ff02f8e4540ba049a340068dac0ff4f0495df9f8eeb21d4a545ea5dedf2dd"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,23 +1,57 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/Azure/azure-storage-go"
[[constraint]]
branch = "master"
name = "github.com/BurntSushi/toml"
version = "0.3.0"
[[constraint]]
name = "github.com/asaskevich/govalidator"
version = "6.0.0"
[[constraint]]
name = "github.com/boltdb/bolt"
version = "1.3.1"
[[constraint]]
name = "github.com/cenkalti/backoff"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/sirupsen/logrus"
[[constraint]]
name = "github.com/aws/aws-sdk-go"
revision = "5b341290c488aa6bd76b335d819b4a68516ec3ab"
name = "github.com/google/subcommands"
[[constraint]]
branch = "master"
name = "github.com/gosuri/uitable"
[[constraint]]
branch = "master"
name = "github.com/howeyc/gopass"
[[constraint]]
name = "github.com/jroimartin/gocui"
version = "0.3.0"
[[constraint]]
branch = "master"
@@ -25,12 +59,28 @@
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/go-cve-dictionary"
name = "github.com/knqyf263/go-deb-version"
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/goval-dictionary"
name = "github.com/knqyf263/go-rpm-version"
[[constraint]]
name = "github.com/kotakanbe/go-pingscanner"
version = "0.1.0"
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/logrus-prefixed-formatter"
[[constraint]]
name = "github.com/parnurzeal/gorequest"
version = "0.2.15"
[[constraint]]
name = "github.com/rifflock/lfshook"
version = "1.7.0"
[[constraint]]
branch = "master"
name = "github.com/sirupsen/logrus"

View File

@@ -803,12 +803,12 @@ In order to scan, the following dependencies are required, so you need to instal
|:-------------|-------------------:|:-------------|
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8| aptitude |
| CentOS | 6, 7| yum-plugin-changelog |
| Amazon | All | - |
| RHEL | 5 | yum-security |
| RHEL | 6, 7 | - |
| Oracle Linux | 5 | yum-security |
| Oracle Linux | 6, 7 | - |
| CentOS | 6, 7| yum-plugin-changelog, yum-utils |
| Amazon | All | - | TODO yum-utils?, yum-plugin-changelog
| RHEL | 5 | yum-security | TODO yum-utils?
| RHEL | 6, 7 | - | TODO yum-utils?
| Oracle Linux | 5 | yum-security | TODO yum-utils?
| Oracle Linux | 6, 7 | - |TODO yum-utils?
| FreeBSD | 10 | - |
| Raspbian | Wheezy, Jessie | - |

View File

@@ -67,6 +67,7 @@ func (*ScanCmd) Usage() string {
[-cachedb-path=/path/to/cache.db]
[-ssh-native-insecure]
[-containers-only]
[-package-list-only]
[-skip-broken]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]

View File

@@ -418,7 +418,7 @@ type ServerInfo struct {
Optional [][]interface{}
// For CentOS, RHEL, Amazon
Enablerepo string
Enablerepo []string
// used internal
LogMsgAnsiColor string // DebugLog Color

View File

@@ -20,7 +20,6 @@ package config
import (
"fmt"
"os"
"strings"
"github.com/BurntSushi/toml"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
@@ -164,7 +163,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
s.Enablerepo = d.Enablerepo
}
if len(s.Enablerepo) != 0 {
for _, repo := range strings.Split(s.Enablerepo, ",") {
for _, repo := range s.Enablerepo {
switch repo {
case "base", "updates":
// nop

View File

@@ -18,4 +18,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package models
// JSONVersion is JSON Version
const JSONVersion = "0.3.0"
const JSONVersion = 2

View File

@@ -42,6 +42,7 @@ func (ps Packages) MergeNewVersion(as Packages) {
if pack, ok := ps[a.Name]; ok {
pack.NewVersion = a.NewVersion
pack.NewRelease = a.NewRelease
pack.Repository = a.Repository
ps[a.Name] = pack
}
}
@@ -79,6 +80,16 @@ func (ps Packages) FormatUpdatablePacksSummary() string {
return fmt.Sprintf("%d updatable packages", nUpdatable)
}
// FindOne search a element by name-newver-newrel-arch
func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
for key, p := range ps {
if f(p) {
return key, p, true
}
}
return "", Package{}, false
}
// Package has installed packages.
type Package struct {
Name string
@@ -86,6 +97,7 @@ type Package struct {
Release string
NewVersion string
NewRelease string
Arch string
Repository string
Changelog Changelog
NotFixedYet bool // Ubuntu OVAL Only
@@ -145,8 +157,8 @@ func (p Package) FormatChangelog() string {
}
// Changelog has contents of changelog and how to get it.
// Method: modesl.detectionMethodStr
// Method: models.detectionMethodStr
type Changelog struct {
Contents string
Method string
Method DetectionMethod
}

View File

@@ -32,7 +32,7 @@ type ScanResults []ScanResult
// ScanResult has the result of scanned CVE information.
type ScanResult struct {
ScannedAt time.Time
JSONVersion string
JSONVersion int
Lang string
ServerName string // TOML Section key
Family string

View File

@@ -26,7 +26,8 @@ import (
"github.com/future-architect/vuls/config"
)
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
// VulnInfos has a map of VulnInfo
// Key: CveID
type VulnInfos map[string]VulnInfo
// Find elements that matches the function passed in argument
@@ -198,13 +199,18 @@ type DistroAdvisory struct {
// Score: 0 - 100
type Confidence struct {
Score int
DetectionMethod string
DetectionMethod DetectionMethod
}
func (c Confidence) String() string {
return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
}
// DetectionMethod indicates
// - How to detect the CveID
// - How to get the changelog difference between installed and candidate version
type DetectionMethod string
const (
// CpeNameMatchStr is a String representation of CpeNameMatch
CpeNameMatchStr = "CpeNameMatch"

View File

@@ -24,7 +24,7 @@ import (
"fmt"
"time"
"github.com/Azure/azure-storage-go"
storage "github.com/Azure/azure-sdk-for-go/storage"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"

View File

@@ -45,14 +45,12 @@ var currentDetailLimitY int
func RunTui(results models.ScanResults) subcommands.ExitStatus {
scanResults = results
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Errorf("%s", err)
return subcommands.ExitFailure
}
// g, err := gocui.NewGui(gocui.OutputNormal)
g := gocui.NewGui()
defer g.Close()
g.SetManagerFunc(layout)
g.SetLayout(layout)
// g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Errorf("%s", err)
return subcommands.ExitFailure
@@ -177,19 +175,19 @@ func nextView(g *gocui.Gui, v *gocui.View) error {
var err error
if v == nil {
_, err = g.SetCurrentView("side")
err = g.SetCurrentView("side")
}
switch v.Name() {
case "side":
_, err = g.SetCurrentView("summary")
err = g.SetCurrentView("summary")
case "summary":
_, err = g.SetCurrentView("detail")
err = g.SetCurrentView("detail")
case "detail":
_, err = g.SetCurrentView("changelog")
err = g.SetCurrentView("changelog")
case "changelog":
_, err = g.SetCurrentView("side")
err = g.SetCurrentView("side")
default:
_, err = g.SetCurrentView("summary")
err = g.SetCurrentView("summary")
}
return err
}
@@ -198,19 +196,19 @@ func previousView(g *gocui.Gui, v *gocui.View) error {
var err error
if v == nil {
_, err = g.SetCurrentView("side")
err = g.SetCurrentView("side")
}
switch v.Name() {
case "side":
_, err = g.SetCurrentView("side")
err = g.SetCurrentView("side")
case "summary":
_, err = g.SetCurrentView("side")
err = g.SetCurrentView("side")
case "detail":
_, err = g.SetCurrentView("summary")
err = g.SetCurrentView("summary")
case "changelog":
_, err = g.SetCurrentView("detail")
err = g.SetCurrentView("detail")
default:
_, err = g.SetCurrentView("side")
err = g.SetCurrentView("side")
}
return err
}
@@ -393,7 +391,7 @@ func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
func previousSummary(g *gocui.Gui, v *gocui.View) error {
if v != nil {
// cursor to summary
if _, err := g.SetCurrentView("summary"); err != nil {
if err := g.SetCurrentView("summary"); err != nil {
return err
}
// move next line
@@ -401,7 +399,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
return err
}
// cursor to detail
if _, err := g.SetCurrentView("detail"); err != nil {
if err := g.SetCurrentView("detail"); err != nil {
return err
}
}
@@ -411,7 +409,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
func nextSummary(g *gocui.Gui, v *gocui.View) error {
if v != nil {
// cursor to summary
if _, err := g.SetCurrentView("summary"); err != nil {
if err := g.SetCurrentView("summary"); err != nil {
return err
}
// move next line
@@ -419,7 +417,7 @@ func nextSummary(g *gocui.Gui, v *gocui.View) error {
return err
}
// cursor to detail
if _, err := g.SetCurrentView("detail"); err != nil {
if err := g.SetCurrentView("detail"); err != nil {
return err
}
}
@@ -502,7 +500,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
return err
}
fmt.Fprintln(v, l)
if _, err := g.SetCurrentView("msg"); err != nil {
if err := g.SetCurrentView("msg"); err != nil {
return err
}
}
@@ -525,7 +523,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
return err
}
fmt.Fprintln(v, l)
if _, err := g.SetCurrentView("msg"); err != nil {
if err := g.SetCurrentView("msg"); err != nil {
return err
}
}
@@ -536,7 +534,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
if err := g.DeleteView("msg"); err != nil {
return err
}
if _, err := g.SetCurrentView("summary"); err != nil {
if err := g.SetCurrentView("summary"); err != nil {
return err
}
return nil
@@ -592,7 +590,7 @@ func setSideLayout(g *gocui.Gui) error {
}
currentScanResult = scanResults[0]
vinfos = scanResults[0].ScannedCves.ToSortedSlice()
if _, err := g.SetCurrentView("side"); err != nil {
if err := g.SetCurrentView("side"); err != nil {
return err
}
}

View File

@@ -167,7 +167,7 @@ func (o *debian) checkDependencies() error {
}
func (o *debian) scanPackages() error {
installed, upgradable, err := o.scanInstalledPackages()
installed, updatable, err := o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages")
return err
@@ -178,7 +178,7 @@ func (o *debian) scanPackages() error {
return nil
}
unsecure, err := o.scanUnsecurePackages(upgradable)
unsecure, err := o.scanUnsecurePackages(updatable)
if err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
@@ -189,7 +189,7 @@ func (o *debian) scanPackages() error {
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, error) {
installed := models.Packages{}
upgradable := models.Packages{}
updatable := models.Packages{}
r := o.exec("dpkg-query -W", noSudo)
if !r.isSuccess() {
@@ -214,27 +214,27 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, erro
}
}
upgradableNames, err := o.GetUpgradablePackNames()
updatableNames, err := o.getUpdatablePackNames()
if err != nil {
return nil, nil, err
}
for _, name := range upgradableNames {
for _, name := range updatableNames {
for _, pack := range installed {
if pack.Name == name {
upgradable[name] = pack
updatable[name] = pack
break
}
}
}
// Fill the candidate versions of upgradable packages
err = o.fillCandidateVersion(upgradable)
err = o.fillCandidateVersion(updatable)
if err != nil {
return nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
}
installed.MergeNewVersion(upgradable)
installed.MergeNewVersion(updatable)
return installed, upgradable, nil
return installed, updatable, nil
}
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`)
@@ -263,14 +263,14 @@ func (o *debian) aptGetUpdate() error {
return nil
}
func (o *debian) scanUnsecurePackages(upgradable models.Packages) (models.VulnInfos, error) {
func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
o.aptGetUpdate()
// Setup changelog cache
current := cache.Meta{
Name: o.getServerInfo().GetServerName(),
Distro: o.getServerInfo().Distro,
Packs: upgradable,
Packs: updatable,
}
o.log.Debugf("Ensure changelog cache: %s", current.Name)
@@ -280,7 +280,7 @@ func (o *debian) scanUnsecurePackages(upgradable models.Packages) (models.VulnIn
}
// Collect CVE information of upgradable packages
vulnInfos, err := o.scanVulnInfos(upgradable, meta)
vulnInfos, err := o.scanVulnInfos(updatable, meta)
if err != nil {
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
}
@@ -349,7 +349,7 @@ func (o *debian) fillCandidateVersion(packages models.Packages) (err error) {
return
}
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
func (o *debian) getUpdatablePackNames() (packNames []string, err error) {
cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get upgrade --dry-run")
r := o.exec(cmd, noSudo)
if r.isSuccess(0, 1) {
@@ -360,7 +360,7 @@ func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
func (o *debian) parseAptGetUpgrade(stdout string) (updatableNames []string, err error) {
startRe := regexp.MustCompile(`The following packages will be upgraded:`)
stopRe := regexp.MustCompile(`^(\d+) upgraded.*`)
startLineFound, stopLineFound := false, false
@@ -375,21 +375,21 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
}
result := stopRe.FindStringSubmatch(line)
if len(result) == 2 {
numUpgradablePacks, err := strconv.Atoi(result[1])
nUpdatable, err := strconv.Atoi(result[1])
if err != nil {
return nil, fmt.Errorf(
"Failed to scan upgradable packages number. line: %s", line)
}
if numUpgradablePacks != len(upgradableNames) {
if nUpdatable != len(updatableNames) {
return nil, fmt.Errorf(
"Failed to scan upgradable packages, expected: %s, detected: %d",
result[1], len(upgradableNames))
result[1], len(updatableNames))
}
stopLineFound = true
o.log.Debugf("Found the stop line. line: %s", line)
break
}
upgradableNames = append(upgradableNames, strings.Fields(line)...)
updatableNames = append(updatableNames, strings.Fields(line)...)
}
if !startLineFound {
// no upgrades
@@ -410,20 +410,20 @@ type DetectedCveID struct {
Confidence models.Confidence
}
func (o *debian) scanVulnInfos(upgradablePacks models.Packages, meta *cache.Meta) (models.VulnInfos, error) {
func (o *debian) scanVulnInfos(updatablePacks models.Packages, meta *cache.Meta) (models.VulnInfos, error) {
type response struct {
pack *models.Package
DetectedCveIDs []DetectedCveID
}
resChan := make(chan response, len(upgradablePacks))
errChan := make(chan error, len(upgradablePacks))
reqChan := make(chan models.Package, len(upgradablePacks))
resChan := make(chan response, len(updatablePacks))
errChan := make(chan error, len(updatablePacks))
reqChan := make(chan models.Package, len(updatablePacks))
defer close(resChan)
defer close(errChan)
defer close(reqChan)
go func() {
for _, pack := range upgradablePacks {
for _, pack := range updatablePacks {
reqChan <- pack
}
}()
@@ -431,7 +431,7 @@ func (o *debian) scanVulnInfos(upgradablePacks models.Packages, meta *cache.Meta
timeout := time.After(30 * 60 * time.Second)
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range upgradablePacks {
for range updatablePacks {
tasks <- func() {
select {
case pack := <-reqChan:
@@ -459,7 +459,7 @@ func (o *debian) scanVulnInfos(upgradablePacks models.Packages, meta *cache.Meta
// { DetectedCveID{} : [package] }
cvePackages := make(map[DetectedCveID][]string)
errs := []error{}
for i := 0; i < len(upgradablePacks); i++ {
for i := 0; i < len(updatablePacks); i++ {
select {
case response := <-resChan:
o.Packages[response.pack.Name] = *response.pack
@@ -474,7 +474,7 @@ func (o *debian) scanVulnInfos(upgradablePacks models.Packages, meta *cache.Meta
cvePackages[cve] = packNames
}
o.log.Infof("(%d/%d) Scanned %s: %s",
i+1, len(upgradablePacks), response.pack.Name, cves)
i+1, len(updatablePacks), response.pack.Name, cves)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
@@ -500,7 +500,7 @@ func (o *debian) scanVulnInfos(upgradablePacks models.Packages, meta *cache.Meta
}
// Update meta package information of changelog cache to the latest one.
meta.Packs = upgradablePacks
meta.Packs = updatablePacks
if err := cache.DB.RefreshMeta(*meta); err != nil {
return nil, err
}
@@ -664,7 +664,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C
clog := models.Changelog{
Contents: strings.Join(buf, "\n"),
Method: string(confidence.DetectionMethod),
Method: confidence.DetectionMethod,
}
pack := o.Packages[name]
pack.Changelog = clog

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package scan
import (
"bufio"
"fmt"
"regexp"
"strings"
@@ -27,7 +28,7 @@ import (
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/k0kubun/pp"
ver "github.com/knqyf263/go-rpm-version"
)
// inherit OsTypeInterface
@@ -147,14 +148,14 @@ func (o *redhat) checkIfSudoNoPasswd() error {
if majorVersion < 6 {
cmds = []cmd{
{"yum --color=never repolist", zero},
{"yum --color=never check-update", []int{0, 100}},
// {"yum --color=never check-update", []int{0, 100}},
{"yum --color=never list-security --security", zero},
{"yum --color=never info-security", zero},
}
} else {
cmds = []cmd{
{"yum --color=never repolist", zero},
{"yum --color=never check-update", []int{0, 100}},
// {"yum --color=never check-update", []int{0, 100}},
{"yum --color=never --security updateinfo list updates", zero},
{"yum --color=never --security updateinfo updates", zero},
}
@@ -174,12 +175,11 @@ func (o *redhat) checkIfSudoNoPasswd() error {
return nil
}
// CentOS 6, 7 ... yum-plugin-changelog
// CentOS 6, 7 ... yum-plugin-changelog, yum-utils
// RHEL 5 ... yum-security
// RHEL 6, 7 ... -
// Amazon ... -
func (o *redhat) checkDependencies() error {
var packName string
if o.Distro.Family == config.Amazon {
return nil
}
@@ -207,12 +207,14 @@ func (o *redhat) checkDependencies() error {
}
}
//TODO Check if yum-plugin-changelog is installed when scan with --changelog option on Amazon,RHEL, Oracle
var packNames []string
switch o.Distro.Family {
case config.CentOS:
packName = "yum-plugin-changelog"
packNames = []string{"yum-plugin-changelog", "yum-utils"}
case config.RedHat, config.Oracle:
if majorVersion < 6 {
packName = "yum-security"
packNames = []string{"yum-security"}
} else {
// yum-plugin-security is installed by default on RHEL6, 7
return nil
@@ -221,27 +223,45 @@ func (o *redhat) checkDependencies() error {
return fmt.Errorf("Not implemented yet: %s", o.Distro)
}
cmd := "rpm -q " + packName
if r := o.exec(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("%s is not installed", packName)
o.log.Errorf(msg)
return fmt.Errorf(msg)
for _, name := range packNames {
cmd := "rpm -q " + name
if r := o.exec(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("%s is not installed", name)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
}
o.log.Infof("Dependencies... Pass")
o.log.Infof("Dependencies ... Pass")
return nil
}
func (o *redhat) scanPackages() error {
var err error
var packs []models.Package
if packs, err = o.scanInstalledPackages(); err != nil {
installed, err := o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages")
return err
}
o.setPackages(models.NewPackages(packs...))
updatable, err := o.scanUpdatablePackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages")
return err
}
installed.MergeNewVersion(updatable)
o.setPackages(installed)
if config.Conf.PackageListOnly {
return nil
}
//TODO Cache changelogs to bolt
//TODO --with-changelog
if err := o.fillChangelogs(updatable); err != nil {
return nil
}
var vinfos models.VulnInfos
if vinfos, err = o.scanVulnInfos(); err != nil {
if vinfos, err = o.scanUnsecurePackages(updatable); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
@@ -249,406 +269,360 @@ func (o *redhat) scanPackages() error {
return nil
}
func (o *redhat) scanInstalledPackages() (installed []models.Package, err error) {
cmd := "rpm -qa --queryformat '%{NAME}\t%{EPOCHNUM}\t%{VERSION}\t%{RELEASE}\n'"
func (o *redhat) scanInstalledPackages() (models.Packages, error) {
installed := models.Packages{}
cmd := "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'"
r := o.exec(cmd, noSudo)
if r.isSuccess() {
// e.g.
// openssl 1.0.1e 30.el6.11
// openssl 0 1.0.1e 30.el6.11 x86_64
lines := strings.Split(r.Stdout, "\n")
for _, line := range lines {
if trimed := strings.TrimSpace(line); len(trimed) != 0 {
var pack models.Package
if pack, err = o.parseScannedPackagesLine(line); err != nil {
return
pack, err := o.parseInstalledPackagesLine(line)
if err != nil {
return nil, err
}
installed = append(installed, pack)
installed[pack.Name] = pack
}
}
return
return installed, nil
}
return nil, fmt.Errorf(
"Scan packages failed. status: %d, stdout: %s, stderr: %s",
return nil, fmt.Errorf("Scan packages failed. status: %d, stdout: %s, stderr: %s",
r.ExitStatus, r.Stdout, r.Stderr)
}
func (o *redhat) parseScannedPackagesLine(line string) (models.Package, error) {
func (o *redhat) parseInstalledPackagesLine(line string) (models.Package, error) {
fields := strings.Fields(line)
if len(fields) != 4 {
if len(fields) != 5 {
return models.Package{},
fmt.Errorf("Failed to parse package line: %s", line)
}
ver := ""
if fields[1] == "0" {
epoch := fields[1]
if epoch == "0" {
ver = fields[2]
} else {
ver = fmt.Sprintf("%s:%s", fields[1], fields[2])
ver = fmt.Sprintf("%s:%s", epoch, fields[2])
}
return models.Package{
Name: fields[0],
Version: ver,
Release: fields[3],
Arch: fields[4],
}, nil
}
func (o *redhat) scanVulnInfos() (models.VulnInfos, error) {
if o.Distro.Family != config.CentOS {
// Amazon, RHEL, Oracle Linux has yum updateinfo as default
// yum updateinfo can collenct vendor advisory information.
return o.scanUnsecurePackagesUsingYumPluginSecurity()
}
// CentOS does not have security channel...
// So, yum check-update then parse chnagelog.
return o.scanUnsecurePackagesUsingYumCheckUpdate()
}
// For CentOS
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, error) {
cmd := "LANGUAGE=en_US.UTF-8 yum --color=never %s check-update"
if o.getServerInfo().Enablerepo != "" {
cmd = fmt.Sprintf(cmd, "--enablerepo="+o.getServerInfo().Enablerepo)
} else {
cmd = fmt.Sprintf(cmd, "")
func (o *redhat) scanUpdatablePackages() (models.Packages, error) {
cmd := "repoquery --all --pkgnarrow=updates --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPO}'"
for _, repo := range o.getServerInfo().Enablerepo {
cmd += " --enablerepo=" + repo
}
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
// get Updateble package name, installed, candidate version.
packages, err := o.parseYumCheckUpdateLines(r.Stdout)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
}
o.log.Debugf("%s", pp.Sprintf("%v", packages))
// set candidate version info
o.Packages.MergeNewVersion(packages)
// Collect CVE-IDs in changelog
type PackageCveIDs struct {
Package models.Package
CveIDs []string
}
allChangelog, err := o.getAllChangelog(packages)
if err != nil {
o.log.Errorf("Failed to getAllchangelog. err: %s", err)
return nil, err
}
// { packageName: changelog-lines }
var rpm2changelog map[string]*string
rpm2changelog, err = o.divideChangelogByPackage(allChangelog)
if err != nil {
return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
}
for name, clog := range rpm2changelog {
for _, p := range o.Packages {
n := fmt.Sprintf("%s-%s-%s", p.Name, p.NewVersion, p.NewRelease)
if name == n {
p.Changelog = models.Changelog{
Contents: *clog,
Method: models.ChangelogExactMatchStr,
}
o.Packages[p.Name] = p
break
}
}
}
var results []PackageCveIDs
i := 0
for name := range packages {
changelog := o.getChangelogCVELines(rpm2changelog, packages[name])
// Collect unique set of CVE-ID in each changelog
uniqueCveIDMap := make(map[string]bool)
lines := strings.Split(changelog, "\n")
for _, line := range lines {
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
for _, c := range cveIDs {
uniqueCveIDMap[c] = true
}
}
// keys
var cveIDs []string
for k := range uniqueCveIDMap {
cveIDs = append(cveIDs, k)
}
p := PackageCveIDs{
Package: packages[name],
CveIDs: cveIDs,
}
results = append(results, p)
o.log.Infof("(%d/%d) Scanned %s-%s-%s -> %s-%s : %s",
i+1,
len(packages),
p.Package.Name,
p.Package.Version,
p.Package.Release,
p.Package.NewVersion,
p.Package.NewRelease,
p.CveIDs)
i++
}
// transform datastructure
// - From
// [
// {
// Pack: models.Packages,
// CveIDs: []string,
// },
// ]
// - To
// map {
// CveID: models.Packages{}
// }
cveIDPackages := make(map[string]models.Packages)
for _, res := range results {
for _, cveID := range res.CveIDs {
if packages, ok := cveIDPackages[cveID]; ok {
packages[res.Package.Name] = res.Package
cveIDPackages[cveID] = packages
} else {
cveIDPackages[cveID] = models.NewPackages(res.Package)
}
}
}
vinfos := models.VulnInfos{}
for cveID, packs := range cveIDPackages {
names := []string{}
for name := range packs {
names = append(names, name)
}
// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
vinfos[cveID] = models.VulnInfo{
CveID: cveID,
PackageNames: names,
Confidence: models.ChangelogExactMatch,
}
}
return vinfos, nil
// Collect Updateble packages, installed, candidate version and repository.
return o.parseUpdatablePacksLines(r.Stdout)
}
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
func (o *redhat) parseYumCheckUpdateLines(stdout string) (models.Packages, error) {
results := models.Packages{}
needToParse := false
// parseUpdatablePacksLines parse the stdout of repoquery to get package name, candidate version
func (o *redhat) parseUpdatablePacksLines(stdout string) (models.Packages, error) {
updatable := models.Packages{}
lines := strings.Split(stdout, "\n")
for _, line := range lines {
// update information of packages begin after blank line.
if trimed := strings.TrimSpace(line); len(trimed) == 0 {
needToParse = true
// TODO remove
// if strings.HasPrefix(line, "Obsoleting") ||
// strings.HasPrefix(line, "Security:") {
// // see https://github.com/future-architect/vuls/issues/165
// continue
// }
if len(strings.TrimSpace(line)) == 0 {
continue
}
if needToParse {
if strings.HasPrefix(line, "Obsoleting") ||
strings.HasPrefix(line, "Security:") {
// see https://github.com/future-architect/vuls/issues/165
continue
}
candidate, err := o.parseYumCheckUpdateLine(line)
if err != nil {
return results, err
}
installed, found := o.Packages[candidate.Name]
if !found {
o.log.Warnf("Not found the package in rpm -qa. candidate: %s-%s-%s",
candidate.Name, candidate.Version, candidate.Release)
results[candidate.Name] = candidate
continue
}
installed.NewVersion = candidate.NewVersion
installed.NewRelease = candidate.NewRelease
installed.Repository = candidate.Repository
results[installed.Name] = installed
pack, err := o.parseUpdatablePacksLine(line)
if err != nil {
return updatable, err
}
updatable[pack.Name] = pack
}
return results, nil
return updatable, nil
}
func (o *redhat) parseYumCheckUpdateLine(line string) (models.Package, error) {
func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
fields := strings.Fields(line)
if len(fields) < 3 {
return models.Package{}, fmt.Errorf("Unknown format: %s", line)
if len(fields) < 5 {
return models.Package{}, fmt.Errorf("Unknown format: %s, fields: %s", line, fields)
}
splitted := strings.Split(fields[0], ".")
packName := ""
if len(splitted) == 1 {
packName = fields[0]
ver := ""
epoch := fields[1]
if epoch == "0" {
ver = fields[2]
} else {
packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
ver = fmt.Sprintf("%s:%s", epoch, fields[2])
}
verfields := strings.Split(fields[1], "-")
if len(verfields) != 2 {
return models.Package{}, fmt.Errorf("Unknown format: %s", line)
}
release := verfields[1]
repos := strings.Join(fields[2:len(fields)], " ")
repos := strings.Join(fields[4:len(fields)], " ")
return models.Package{
Name: packName,
NewVersion: verfields[0],
NewRelease: release,
p := models.Package{
Name: fields[0],
NewVersion: ver,
NewRelease: fields[3],
Repository: repos,
}, nil
}
func (o *redhat) mkPstring() *string {
str := ""
return &str
}
func (o *redhat) regexpReplace(src string, pat string, rep string) string {
re := regexp.MustCompile(pat)
return re.ReplaceAllString(src, rep)
}
var changeLogCVEPattern = regexp.MustCompile(`CVE-[0-9]+-[0-9]+`)
func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, pack models.Package) string {
rpm := fmt.Sprintf("%s-%s-%s", pack.Name, pack.NewVersion, pack.NewRelease)
retLine := ""
if rpm2changelog[rpm] != nil {
lines := strings.Split(*rpm2changelog[rpm], "\n")
for _, line := range lines {
if changeLogCVEPattern.MatchString(line) {
retLine += fmt.Sprintf("%s\n", line)
}
}
}
return retLine
return p, nil
}
func (o *redhat) divideChangelogByPackage(allChangelog string) (map[string]*string, error) {
var majorVersion int
var err error
if o.Distro.Family == config.CentOS {
majorVersion, err = o.Distro.MajorVersion()
if err != nil {
return nil, fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
func (o *redhat) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
if o.Distro.Family != config.CentOS {
// Amazon, RHEL, Oracle Linux has yum updateinfo as default
// yum updateinfo can collenct vendor advisory information.
return o.scanCveIDsByCommands(updatable)
}
// Parse chnagelog because CentOS does not have security channel...
return o.scanCveIDsInChangelog(updatable)
}
func (o *redhat) fillChangelogs(updatables models.Packages) error {
names := []string{}
for name := range updatables {
names = append(names, name)
}
if err := o.fillDiffChangelogs(names); err != nil {
return err
}
emptyChangelogPackNames := []string{}
for _, pack := range o.Packages {
if pack.NewVersion != "" && pack.Changelog.Contents == "" {
emptyChangelogPackNames = append(emptyChangelogPackNames, pack.Name)
}
}
orglines := strings.Split(allChangelog, "\n")
tmpline := ""
var lines []string
var prev, now bool
for i := range orglines {
if majorVersion == 5 {
/* for CentOS5 (yum-util < 1.1.20) */
prev = false
now = false
if 0 < i {
prev, err = o.isRpmPackageNameLine(orglines[i-1])
if err != nil {
return nil, err
}
}
now, err = o.isRpmPackageNameLine(orglines[i])
if err != nil {
return nil, err
}
if prev && now {
tmpline = fmt.Sprintf("%s, %s", tmpline, orglines[i])
continue
}
if !prev && now {
tmpline = fmt.Sprintf("%s%s", tmpline, orglines[i])
continue
}
if tmpline != "" {
lines = append(lines, fmt.Sprintf("%s", tmpline))
tmpline = ""
}
lines = append(lines, fmt.Sprintf("%s", orglines[i]))
} else {
/* for CentOS6,7 (yum-util >= 1.1.20) */
line := orglines[i]
line = o.regexpReplace(line, `^ChangeLog for: `, "")
line = o.regexpReplace(line, `^\*\*\sNo\sChangeLog\sfor:.*`, "")
lines = append(lines, line)
i := 0
for _, name := range emptyChangelogPackNames {
i++
o.log.Infof("(%d/%d) Fetched Changelogs %s", i, len(emptyChangelogPackNames), name)
if err := o.fillDiffChangelogs([]string{name}); err != nil {
return err
}
}
rpm2changelog := make(map[string]*string)
writePointer := o.mkPstring()
for _, line := range lines {
match, err := o.isRpmPackageNameLine(line)
if err != nil {
return nil, err
}
if match {
rpms := strings.Split(line, ",")
pNewString := o.mkPstring()
writePointer = pNewString
for _, rpm := range rpms {
rpm = strings.TrimSpace(rpm)
rpm = o.regexpReplace(rpm, `\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "")
if ss := strings.Split(rpm, ":"); 1 < len(ss) {
epoch := ss[0]
packVersion := strings.Join(ss[1:len(ss)], ":")
if sss := strings.Split(packVersion, "-"); 2 < len(sss) {
version := strings.Join(sss[len(sss)-2:len(sss)], "-")
name := strings.Join(sss[0:len(sss)-2], "-")
rpm = fmt.Sprintf("%s-%s:%s", name, epoch, version)
}
}
rpm2changelog[rpm] = pNewString
}
} else {
if strings.HasPrefix(line, "Dependencies Resolved") {
return rpm2changelog, nil
}
*writePointer += fmt.Sprintf("%s\n", line)
}
}
return rpm2changelog, nil
return nil
}
// CentOS
func (o *redhat) getAllChangelog(packages models.Packages) (stdout string, err error) {
packageNames := ""
for _, pack := range packages {
packageNames += fmt.Sprintf("%s ", pack.Name)
}
command := ""
if 0 < len(config.Conf.HTTPProxy) {
command += util.ProxyEnv()
}
func (o *redhat) getAvailableChangelogs(packNames []string) (map[string]string, error) {
yumopts := ""
if o.getServerInfo().Enablerepo != "" {
yumopts = " --enablerepo=" + o.getServerInfo().Enablerepo
if 0 < len(o.getServerInfo().Enablerepo) {
yumopts = " --enablerepo=" + strings.Join(o.getServerInfo().Enablerepo, ",")
}
if config.Conf.SkipBroken {
yumopts += " --skip-broken"
}
cmd := `yum --color=never %s changelog all %s | grep -A 10000 '==================== Available Packages ===================='`
cmd = fmt.Sprintf(cmd, yumopts, strings.Join(packNames, " "))
// yum update --changelog doesn't have --color option.
command += fmt.Sprintf(" LANGUAGE=en_US.UTF-8 yum --changelog --assumeno update %s ", yumopts) + packageNames
r := o.exec(command, sudo)
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
if !r.isSuccess(0, 1) {
return "", fmt.Errorf(
"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
r.ExitStatus, r.Stdout, r.Stderr)
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
return strings.Replace(r.Stdout, "\r", "", -1), nil
return o.divideChangelogsIntoEachPackages(r.Stdout), nil
}
// Divide available change logs of all updatable packages into each package's changelog
func (o *redhat) divideChangelogsIntoEachPackages(stdout string) map[string]string {
changelogs := make(map[string]string)
scanner := bufio.NewScanner(strings.NewReader(stdout))
crlf, newBlock := false, true
packNameVer, contents := "", []string{}
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "==================== Available Packages ====================") {
continue
}
if newBlock {
left := strings.Fields(line)[0]
// ss := strings.Split(left, ".")
// packNameVer = strings.Join(ss[0:len(ss)-1], ".")
packNameVer = left
newBlock = false
continue
}
if len(strings.TrimSpace(line)) == 0 {
if crlf {
changelogs[packNameVer] = strings.Join(contents, "\n")
packNameVer = ""
contents = []string{}
newBlock = true
crlf = false
} else {
contents = append(contents, line)
crlf = true
}
} else {
contents = append(contents, line)
crlf = false
}
}
if 0 < len(contents) {
changelogs[packNameVer] = strings.Join(contents, "\n")
}
return changelogs
}
func (o *redhat) fillDiffChangelogs(packNames []string) error {
changelogs, err := o.getAvailableChangelogs(packNames)
if err != nil {
return err
}
for s := range changelogs {
// name, pack, found := o.Packages.FindOne(func(p models.Package) bool {
name, pack, found := o.Packages.FindOne(func(p models.Package) bool {
var epochNameVerRel string
if index := strings.Index(p.NewVersion, ":"); 0 < index {
epoch := p.NewVersion[0:index]
ver := p.NewVersion[index+1 : len(p.NewVersion)]
epochNameVerRel = fmt.Sprintf("%s:%s-%s",
epoch, p.Name, ver)
} else {
epochNameVerRel = fmt.Sprintf("%s-%s",
p.Name, p.NewVersion)
}
return strings.HasPrefix(s, epochNameVerRel)
})
if found {
diff, err := o.getDiffChangelog(pack, changelogs[s])
detectionMethod := models.ChangelogExactMatchStr
if err != nil {
o.log.Debug(err)
// Try without epoch
if index := strings.Index(pack.Version, ":"); 0 < index {
pack.Version = pack.Version[index+1 : len(pack.Version)]
o.log.Debug("Try without epoch", pack)
diff, err = o.getDiffChangelog(pack, changelogs[s])
if err != nil {
o.log.Debugf("Failed to find the version in changelog: %s-%s-%s",
pack.Name, pack.Version, pack.Release)
detectionMethod = models.FailedToFindVersionInChangelog
} else {
o.log.Debugf("Found the version in changelog without epoch: %s-%s-%s",
pack.Name, pack.Version, pack.Release)
detectionMethod = models.ChangelogLenientMatchStr
}
}
}
pack = o.Packages[name]
pack.Changelog = models.Changelog{
Contents: diff,
Method: models.DetectionMethod(detectionMethod),
}
o.Packages[name] = pack
}
}
return nil
}
func (o *redhat) getDiffChangelog(pack models.Package, availableChangelog string) (string, error) {
installedVer := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
scanner := bufio.NewScanner(strings.NewReader(availableChangelog))
diff := []string{}
found := false
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "* ") {
diff = append(diff, line)
continue
}
// openssh on RHEL
// openssh-server-6.6.1p1-35.el7_3.x86_64 rhui-rhel-7-server-rhui-rpms
// Wed Mar 1 21:00:00 2017 Jakub Jelen <jjelen@redhat.com> - 6.6.1p1-35 + 0.9.3-9
ss := strings.Split(line, " + ")
if 1 < len(ss) {
line = ss[0]
}
ss = strings.Split(line, " ")
if len(ss) < 2 {
diff = append(diff, line)
continue
}
v := ss[len(ss)-1]
v = strings.TrimPrefix(v, "-")
v = strings.TrimPrefix(v, "[")
v = strings.TrimSuffix(v, "]")
version := ver.NewVersion(v)
if installedVer.Equal(version) || installedVer.GreaterThan(version) {
found = true
break
}
diff = append(diff, line)
}
if len(diff) == 0 || !found {
return availableChangelog,
fmt.Errorf("Failed to find the version in changelog: %s-%s-%s",
pack.Name, pack.Version, pack.Release)
}
return strings.TrimSpace(strings.Join(diff, "\n")), nil
}
func (o *redhat) scanCveIDsInChangelog(updatable models.Packages) (models.VulnInfos, error) {
packCveIDs := make(map[string][]string)
for name := range updatable {
cveIDs := []string{}
pack := o.Packages[name]
if pack.Changelog.Method == models.FailedToFindVersionInChangelog {
continue
}
scanner := bufio.NewScanner(strings.NewReader(pack.Changelog.Contents))
for scanner.Scan() {
if matches := cveRe.FindAllString(scanner.Text(), -1); 0 < len(matches) {
for _, m := range matches {
cveIDs = util.AppendIfMissing(cveIDs, m)
}
}
}
packCveIDs[name] = cveIDs
}
// transform datastructure
// - From
// "packname": []{"CVE-2017-1111", ".../
//
// - To
// map {
// "CVE-2017-1111": "packname",
// }
vinfos := models.VulnInfos{}
for name, cveIDs := range packCveIDs {
for _, cid := range cveIDs {
if v, ok := vinfos[cid]; ok {
v.PackageNames = append(v.PackageNames, name)
vinfos[cid] = v
} else {
vinfos[cid] = models.VulnInfo{
CveID: cid,
PackageNames: []string{name},
Confidence: models.ChangelogExactMatch,
}
}
}
}
return vinfos, nil
}
type distroAdvisoryCveIDs struct {
@@ -658,7 +632,7 @@ type distroAdvisoryCveIDs struct {
// Scaning unsecure packages using yum-plugin-security.
// Amazon, RHEL, Oracle Linux
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, error) {
func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInfos, error) {
if o.Distro.Family == config.CentOS {
// CentOS has no security channel.
// So use yum check-update && parse changelog
@@ -689,22 +663,6 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos,
}
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
// get package name, version, rel to be upgrade.
cmd = "LANGUAGE=en_US.UTF-8 yum --color=never check-update"
r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
updatable, err := o.parseYumCheckUpdateLines(r.Stdout)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
}
o.log.Debugf("%s", pp.Sprintf("%v", updatable))
// set candidate version info
o.Packages.MergeNewVersion(updatable)
dict := make(map[string]models.Packages)
for _, advIDPackNames := range advIDPackNamesList {
packages := models.Packages{}
@@ -796,8 +754,6 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
for cveID := range cveIDsSetInThisSection {
foundCveIDs = append(foundCveIDs, cveID)
}
//TODO remove
// sort.Strings(foundCveIDs)
result = append(result, distroAdvisoryCveIDs{
DistroAdvisory: advisory,
@@ -883,24 +839,6 @@ func (o *redhat) changeSectionState(state int) (newState int) {
return newState
}
var rpmPackageArchPattern = regexp.MustCompile(
`^[^ ]+\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`)
func (o *redhat) isRpmPackageNameLine(line string) (bool, error) {
s := strings.TrimPrefix(line, "ChangeLog for: ")
ss := strings.Split(s, ", ")
if len(ss) == 0 {
return false, nil
}
for _, s := range ss {
s = strings.TrimRight(s, " \r\n")
if !rpmPackageArchPattern.MatchString(s) {
return false, nil
}
}
return true, nil
}
var yumCveIDPattern = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
@@ -1032,9 +970,10 @@ func (o *redhat) clone() osTypeInterface {
func (o *redhat) sudo() bool {
switch o.Distro.Family {
case config.Amazon:
case config.Amazon, config.CentOS:
return false
default:
// RHEL
return true
}
}

File diff suppressed because it is too large Load Diff