From 787604de6a85555e2c9c14dcd51d4db67c7c4469 Mon Sep 17 00:00:00 2001 From: MaineK00n Date: Tue, 15 Feb 2022 17:11:54 +0900 Subject: [PATCH] fix(suse): fix openSUSE, openSUSE Leap, SLES, SLED scan (#1384) * fix(suse): fix openSUSE, openSUSE Leap scan * docs: update README * fix: unknown CveContent.Type * fix: tui reporting * fix: listening port was duplicated in format-full-text * fix .gitignore * fix: add EOL data for SLES12.5 Co-authored-by: Kota Kanbe --- .gitignore | 2 + README.md | 2 +- config/config.go | 7 ++ config/os.go | 68 +++++++++++++- constant/constant.go | 8 +- go.mod | 8 +- go.sum | 14 ++- models/cvecontents.go | 4 + models/vulninfos.go | 4 +- oval/redhat.go | 38 +------- oval/redhat_test.go | 73 --------------- oval/suse.go | 26 ++++-- oval/util.go | 56 +++++++++++- oval/util_test.go | 73 +++++++++++++++ reporter/util.go | 7 +- scanner/redhatbase.go | 41 +++++---- scanner/suse.go | 201 +++++++++++++++++++++++++++++++++++++----- scanner/suse_test.go | 35 +++++--- scanner/utils.go | 2 +- tui/tui.go | 6 +- 20 files changed, 471 insertions(+), 204 deletions(-) diff --git a/.gitignore b/.gitignore index cb3f68d3..38684da8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ dist/ vuls.* vuls !cmd/vuls +future-vuls +trivy-to-vuls diff --git a/README.md b/README.md index f5d234dd..1f74f8b6 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Vuls is a tool created to solve the problems listed above. It has the following [Supports major Linux/FreeBSD](https://vuls.io/docs/en/supported-os.html) -- Alpine, Amazon Linux, CentOS, Alma Linux, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, Fedora, and Ubuntu +- Alpine, Amazon Linux, CentOS, AlmaLinux, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, openSUSE, openSUSE Leap, SUSE Enterprise Linux, Fedora, and Ubuntu - FreeBSD - Cloud, on-premise, Running Docker Container diff --git a/config/config.go b/config/config.go index 8ddf1248..197e42ce 100644 --- a/config/config.go +++ b/config/config.go @@ -307,6 +307,13 @@ func (l Distro) MajorVersion() (int, error) { if 0 < len(l.Release) { return strconv.Atoi(strings.Split(strings.TrimPrefix(l.Release, "stream"), ".")[0]) } + case constant.OpenSUSE: + if l.Release != "" { + if l.Release == "tumbleweed" { + return 0, nil + } + return strconv.Atoi(strings.Split(l.Release, ".")[0]) + } default: if 0 < len(l.Release) { return strconv.Atoi(strings.Split(l.Release, ".")[0]) diff --git a/config/os.go b/config/os.go index 25a7c96c..75fa1af9 100644 --- a/config/os.go +++ b/config/os.go @@ -147,8 +147,74 @@ func GetEOL(family, release string) (eol EOL, found bool) { StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC), }, }[release] + case constant.OpenSUSE: + // https://en.opensuse.org/Lifetime + eol, found = map[string]EOL{ + "10.2": {Ended: true}, + "10.3": {Ended: true}, + "11.0": {Ended: true}, + "11.1": {Ended: true}, + "11.2": {Ended: true}, + "11.3": {Ended: true}, + "11.4": {Ended: true}, + "12.1": {Ended: true}, + "12.2": {Ended: true}, + "12.3": {Ended: true}, + "13.1": {Ended: true}, + "13.2": {Ended: true}, + "tumbleweed": {}, + }[release] + case constant.OpenSUSELeap: + // https://en.opensuse.org/Lifetime + eol, found = map[string]EOL{ + "42.1": {Ended: true}, + "42.2": {Ended: true}, + "42.3": {Ended: true}, + "15.0": {Ended: true}, + "15.1": {Ended: true}, + "15.2": {Ended: true}, + "15.3": {StandardSupportUntil: time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC)}, + "15.4": {StandardSupportUntil: time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC)}, + }[release] case constant.SUSEEnterpriseServer: - //TODO + // https://www.suse.com/lifecycle + eol, found = map[string]EOL{ + "11": {Ended: true}, + "11.1": {Ended: true}, + "11.2": {Ended: true}, + "11.3": {Ended: true}, + "11.4": {Ended: true}, + "12": {Ended: true}, + "12.1": {Ended: true}, + "12.2": {Ended: true}, + "12.3": {Ended: true}, + "12.4": {Ended: true}, + "12.5": {StandardSupportUntil: time.Date(2024, 10, 31, 23, 59, 59, 0, time.UTC)}, + "15": {Ended: true}, + "15.1": {Ended: true}, + "15.2": {Ended: true}, + "15.3": {StandardSupportUntil: time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC)}, + "15.4": {StandardSupportUntil: time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC)}, + }[release] + case constant.SUSEEnterpriseDesktop: + // https://www.suse.com/lifecycle + eol, found = map[string]EOL{ + "11": {Ended: true}, + "11.1": {Ended: true}, + "11.2": {Ended: true}, + "11.3": {Ended: true}, + "11.4": {Ended: true}, + "12": {Ended: true}, + "12.1": {Ended: true}, + "12.2": {Ended: true}, + "12.3": {Ended: true}, + "12.4": {Ended: true}, + "15": {Ended: true}, + "15.1": {Ended: true}, + "15.2": {Ended: true}, + "15.3": {StandardSupportUntil: time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC)}, + "15.4": {StandardSupportUntil: time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC)}, + }[release] case constant.Alpine: // https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/alpine/alpine.go#L19 // https://alpinelinux.org/releases/ diff --git a/constant/constant.go b/constant/constant.go index 84020db1..53d7a72d 100644 --- a/constant/constant.go +++ b/constant/constant.go @@ -24,7 +24,7 @@ const ( Rocky = "rocky" // Fedora is - // Fedora = "fedora" + Fedora = "fedora" // Amazon is Amazon = "amazon" @@ -53,9 +53,6 @@ const ( // SUSEEnterpriseDesktop is SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop" - // SUSEOpenstackCloud is - SUSEOpenstackCloud = "suse.openstack.cloud" - // Alpine is Alpine = "alpine" @@ -64,7 +61,4 @@ const ( // DeepSecurity is DeepSecurity = "deepsecurity" - - //Fedora is - Fedora = "fedora" ) diff --git a/go.mod b/go.mod index 42e82e0e..d33cd956 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/google/subcommands v1.2.0 github.com/gosuri/uitable v0.0.4 github.com/hashicorp/go-uuid v1.0.2 - github.com/hashicorp/go-version v1.3.0 + github.com/hashicorp/go-version v1.4.0 github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c github.com/jesseduffield/gocui v0.3.0 github.com/k0kubun/pp v3.0.1+incompatible @@ -56,14 +56,14 @@ require ( github.com/vulsio/go-kev v0.1.0 github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14 github.com/vulsio/gost v0.4.1-0.20211028071837-7ad032a6ffa8 - github.com/vulsio/goval-dictionary v0.7.0 + github.com/vulsio/goval-dictionary v0.7.1-0.20220212015000-031fc960b77c golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 - gopkg.in/ini.v1 v1.66.3 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect gorm.io/driver/mysql v1.2.3 // indirect gorm.io/driver/postgres v1.2.3 // indirect gorm.io/driver/sqlite v1.2.6 // indirect @@ -112,7 +112,6 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/htcat/htcat v1.0.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect @@ -151,7 +150,6 @@ require ( github.com/stretchr/testify v1.7.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect - github.com/ymomoi/goval-parser v0.0.0-20170813122243-0a0be1dd9d08 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.7.0 // indirect diff --git a/go.sum b/go.sum index 39065575..994031dd 100644 --- a/go.sum +++ b/go.sum @@ -975,8 +975,9 @@ github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= +github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -1001,7 +1002,6 @@ github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09Uny github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk= github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/htcat/htcat v1.0.2 h1:zro95dGwkKDeZOgq9ei+9szd5qurGxBGfHY8hRehA7k= github.com/htcat/htcat v1.0.2/go.mod h1:i8ViQbjSi2+lJzM6Lx20FIxHENCz6mzJglK3HH06W3s= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -1763,8 +1763,8 @@ github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14 h1:2uYZw2gQ0kymw github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14/go.mod h1:NGdcwWxCK/ES8vZ/crzREqI69S5gH1MivCpSp1pa2Rc= github.com/vulsio/gost v0.4.1-0.20211028071837-7ad032a6ffa8 h1:jqsECpLRp1EAXGOdhPxHzqYjWP5l980GjJ8s/AUYH/4= github.com/vulsio/gost v0.4.1-0.20211028071837-7ad032a6ffa8/go.mod h1:DaWLus8dJ4DdhVsBe5TAEEZ3IdoTMIb/z2StR4Bhb7Q= -github.com/vulsio/goval-dictionary v0.7.0 h1:pnzY1l1KztwlE9FNEUxUpfg08YvMdNt4AL4ohxTVyAY= -github.com/vulsio/goval-dictionary v0.7.0/go.mod h1:aSJK5KAr0o+A0ccgdtQHvaMiAjXEv813QWcEtLr+mvo= +github.com/vulsio/goval-dictionary v0.7.1-0.20220212015000-031fc960b77c h1:LV/HjQRJGhJiKq6huf6ywF09OW+btoo0Y96y01vVp7o= +github.com/vulsio/goval-dictionary v0.7.1-0.20220212015000-031fc960b77c/go.mod h1:BEvFNaiPCKwYWtITjn+hGJQT9N8WfigSd7NXHNnbxkI= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= @@ -1779,8 +1779,6 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= -github.com/ymomoi/goval-parser v0.0.0-20170813122243-0a0be1dd9d08 h1:OsHsjWw5m3P0r+RJITvigJu9dn6L8812S54x42jxeII= -github.com/ymomoi/goval-parser v0.0.0-20170813122243-0a0be1dd9d08/go.mod h1:ox1Nt/rGgWuhVrNg+jKYonAs4BiQG1tRJwj4ue91iy4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -2582,8 +2580,8 @@ gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w= -gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= diff --git a/models/cvecontents.go b/models/cvecontents.go index db06879f..a23249b7 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -4,6 +4,8 @@ import ( "sort" "strings" "time" + + "github.com/future-architect/vuls/constant" ) // CveContents has CveContent @@ -333,6 +335,8 @@ func NewCveContentType(name string) CveContentType { return DebianSecurityTracker case "ubuntu_api": return UbuntuAPI + case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop: + return SUSE case "microsoft": return Microsoft case "wordpress": diff --git a/models/vulninfos.go b/models/vulninfos.go index 34839b88..b8aa7bd9 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -510,7 +510,7 @@ func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) { // Cvss3Scores returns CVSS V3 Score func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) { - order := []CveContentType{RedHatAPI, RedHat, Nvd, Jvn} + order := []CveContentType{RedHatAPI, RedHat, SUSE, Nvd, Jvn} for _, ctype := range order { if conts, found := v.CveContents[ctype]; found { for _, cont := range conts { @@ -549,7 +549,7 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) { } } - // Memo: Only RedHat, Oracle and Amazon has severity data in advisory. + // Memo: Only RedHat, SUSE, Oracle and Amazon has severity data in advisory. for _, adv := range v.DistroAdvisories { if adv.Severity != "" { score := severityToCvssScoreRoughly(adv.Severity) diff --git a/oval/redhat.go b/oval/redhat.go index 9ecd71c4..647006b1 100644 --- a/oval/redhat.go +++ b/oval/redhat.go @@ -5,7 +5,6 @@ package oval import ( "fmt" - "strconv" "strings" "github.com/future-architect/vuls/config" @@ -225,8 +224,8 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo continue } - score2, vec2 := o.parseCvss2(cve.Cvss2) - score3, vec3 := o.parseCvss3(cve.Cvss3) + score2, vec2 := parseCvss2(cve.Cvss2) + score3, vec3 := parseCvss3(cve.Cvss3) sev2, sev3, severity := "", "", def.Advisory.Severity if cve.Impact != "" { @@ -262,39 +261,6 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo return nil } -// ParseCvss2 divide CVSSv2 string into score and vector -// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P -func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string) { - var err error - ss := strings.Split(scoreVector, "/") - if 1 < len(ss) { - if score, err = strconv.ParseFloat(ss[0], 64); err != nil { - return 0, "" - } - return score, strings.Join(ss[1:], "/") - } - return 0, "" -} - -// ParseCvss3 divide CVSSv3 string into score and vector -// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L -func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string) { - var err error - for _, s := range []string{ - "/CVSS:3.0/", - "/CVSS:3.1/", - } { - ss := strings.Split(scoreVector, s) - if 1 < len(ss) { - if score, err = strconv.ParseFloat(ss[0], 64); err != nil { - return 0, "" - } - return score, strings.TrimPrefix(s, "/") + ss[1] - } - } - return 0, "" -} - // RedHat is the interface for RedhatBase OVAL type RedHat struct { RedHatBase diff --git a/oval/redhat_test.go b/oval/redhat_test.go index 2f7e3359..796f3b97 100644 --- a/oval/redhat_test.go +++ b/oval/redhat_test.go @@ -11,79 +11,6 @@ import ( ovalmodels "github.com/vulsio/goval-dictionary/models" ) -func TestParseCvss2(t *testing.T) { - type out struct { - score float64 - vector string - } - var tests = []struct { - in string - out out - }{ - { - in: "5/AV:N/AC:L/Au:N/C:N/I:N/A:P", - out: out{ - score: 5.0, - vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - }, - }, - { - in: "", - out: out{ - score: 0, - vector: "", - }, - }, - } - for _, tt := range tests { - s, v := RedHatBase{}.parseCvss2(tt.in) - if s != tt.out.score || v != tt.out.vector { - t.Errorf("\nexpected: %f, %s\n actual: %f, %s", - tt.out.score, tt.out.vector, s, v) - } - } -} - -func TestParseCvss3(t *testing.T) { - type out struct { - score float64 - vector string - } - var tests = []struct { - in string - out out - }{ - { - in: "5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - out: out{ - score: 5.6, - vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - }, - }, - { - in: "6.1/CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - out: out{ - score: 6.1, - vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - }, - }, - { - in: "", - out: out{ - score: 0, - vector: "", - }, - }, - } - for _, tt := range tests { - s, v := RedHatBase{}.parseCvss3(tt.in) - if s != tt.out.score || v != tt.out.vector { - t.Errorf("\nexpected: %f, %s\n actual: %f, %s", - tt.out.score, tt.out.vector, s, v) - } - } -} - func TestPackNamesOfUpdate(t *testing.T) { var tests = []struct { in models.ScanResult diff --git a/oval/suse.go b/oval/suse.go index c7013c4d..2361264d 100644 --- a/oval/suse.go +++ b/oval/suse.go @@ -4,8 +4,9 @@ package oval import ( + "fmt" + "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/constant" "github.com/future-architect/vuls/logging" "github.com/future-architect/vuls/models" ovalmodels "github.com/vulsio/goval-dictionary/models" @@ -17,11 +18,10 @@ type SUSE struct { } // NewSUSE creates OVAL client for SUSE -func NewSUSE(cnf config.VulnDictInterface) SUSE { - // TODO implement other family +func NewSUSE(cnf config.VulnDictInterface, family string) SUSE { return SUSE{ Base{ - family: constant.SUSEEnterpriseServer, + family: family, Cnf: cnf, }, } @@ -56,7 +56,7 @@ func (o SUSE) FillWithOval(r *models.ScanResult) (nCVEs int, err error) { for _, vuln := range r.ScannedCves { if conts, ok := vuln.CveContents[models.SUSE]; ok { for i, cont := range conts { - cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID + cont.SourceLink = fmt.Sprintf("https://www.suse.com/security/cve/%s.html", cont.CveID) vuln.CveContents[models.SUSE][i] = cont } } @@ -117,11 +117,23 @@ func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent { RefID: r.RefID, }) } - - return &models.CveContent{ + cveCont := models.CveContent{ CveID: def.Title, Title: def.Title, Summary: def.Description, References: refs, } + + if 0 < len(def.Advisory.Cves) { + if len(def.Advisory.Cves) == 1 { + cve := def.Advisory.Cves[0] + score3, vec3 := parseCvss3(cve.Cvss3) + cveCont.Cvss3Score = score3 + cveCont.Cvss3Vector = vec3 + cveCont.Cvss3Severity = cve.Impact + } else { + logging.Log.Warnf("Unknown Oval format. Please register the issue as it needs to be investigated. https://github.com/future-architect/vuls/issues family: %s, defID: %s", o.family, def.DefinitionID) + } + } + return &cveCont } diff --git a/oval/util.go b/oval/util.go index 04038c68..116c06cd 100644 --- a/oval/util.go +++ b/oval/util.go @@ -9,6 +9,7 @@ import ( "net/http" "regexp" "sort" + "strconv" "strings" "time" @@ -398,7 +399,10 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru constant.Fedora, constant.Amazon, constant.Oracle, + constant.OpenSUSE, + constant.OpenSUSELeap, constant.SUSEEnterpriseServer, + constant.SUSEEnterpriseDesktop, constant.Debian, constant.Raspbian, constant.Ubuntu: @@ -457,7 +461,10 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error return vera.LessThan(verb), nil case constant.Oracle, + constant.OpenSUSE, + constant.OpenSUSELeap, constant.SUSEEnterpriseServer, + constant.SUSEEnterpriseDesktop, constant.Amazon, constant.Fedora: vera := rpmver.NewVersion(newVer) @@ -500,9 +507,14 @@ func NewOVALClient(family string, cnf config.GovalDictConf) (Client, error) { return NewRocky(&cnf), nil case constant.Oracle: return NewOracle(&cnf), nil + case constant.OpenSUSE: + return NewSUSE(&cnf, constant.OpenSUSE), nil + case constant.OpenSUSELeap: + return NewSUSE(&cnf, constant.OpenSUSELeap), nil case constant.SUSEEnterpriseServer: - // TODO other suse family - return NewSUSE(&cnf), nil + return NewSUSE(&cnf, constant.SUSEEnterpriseServer), nil + case constant.SUSEEnterpriseDesktop: + return NewSUSE(&cnf, constant.SUSEEnterpriseDesktop), nil case constant.Alpine: return NewAlpine(&cnf), nil case constant.Amazon: @@ -535,9 +547,14 @@ func GetFamilyInOval(familyInScanResult string) (string, error) { return constant.Fedora, nil case constant.Oracle: return constant.Oracle, nil + case constant.OpenSUSE: + return constant.OpenSUSE, nil + case constant.OpenSUSELeap: + return constant.OpenSUSELeap, nil case constant.SUSEEnterpriseServer: - // TODO other suse family return constant.SUSEEnterpriseServer, nil + case constant.SUSEEnterpriseDesktop: + return constant.SUSEEnterpriseDesktop, nil case constant.Alpine: return constant.Alpine, nil case constant.Amazon: @@ -554,3 +571,36 @@ func GetFamilyInOval(familyInScanResult string) (string, error) { } } + +// ParseCvss2 divide CVSSv2 string into score and vector +// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P +func parseCvss2(scoreVector string) (score float64, vector string) { + var err error + ss := strings.Split(scoreVector, "/") + if 1 < len(ss) { + if score, err = strconv.ParseFloat(ss[0], 64); err != nil { + return 0, "" + } + return score, strings.Join(ss[1:], "/") + } + return 0, "" +} + +// ParseCvss3 divide CVSSv3 string into score and vector +// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L +func parseCvss3(scoreVector string) (score float64, vector string) { + var err error + for _, s := range []string{ + "/CVSS:3.0/", + "/CVSS:3.1/", + } { + ss := strings.Split(scoreVector, s) + if 1 < len(ss) { + if score, err = strconv.ParseFloat(ss[0], 64); err != nil { + return 0, "" + } + return score, strings.TrimPrefix(s, "/") + ss[1] + } + } + return 0, "" +} diff --git a/oval/util_test.go b/oval/util_test.go index 6b02a82e..21823674 100644 --- a/oval/util_test.go +++ b/oval/util_test.go @@ -2049,3 +2049,76 @@ func Test_ovalResult_Sort(t *testing.T) { }) } } + +func TestParseCvss2(t *testing.T) { + type out struct { + score float64 + vector string + } + var tests = []struct { + in string + out out + }{ + { + in: "5/AV:N/AC:L/Au:N/C:N/I:N/A:P", + out: out{ + score: 5.0, + vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + }, + }, + { + in: "", + out: out{ + score: 0, + vector: "", + }, + }, + } + for _, tt := range tests { + s, v := parseCvss2(tt.in) + if s != tt.out.score || v != tt.out.vector { + t.Errorf("\nexpected: %f, %s\n actual: %f, %s", + tt.out.score, tt.out.vector, s, v) + } + } +} + +func TestParseCvss3(t *testing.T) { + type out struct { + score float64 + vector string + } + var tests = []struct { + in string + out out + }{ + { + in: "5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + out: out{ + score: 5.6, + vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + }, + }, + { + in: "6.1/CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + out: out{ + score: 6.1, + vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + }, + }, + { + in: "", + out: out{ + score: 0, + vector: "", + }, + }, + } + for _, tt := range tests { + s, v := parseCvss3(tt.in) + if s != tt.out.score || v != tt.out.vector { + t.Errorf("\nexpected: %f, %s\n actual: %f, %s", + tt.out.score, tt.out.vector, s, v) + } + } +} diff --git a/reporter/util.go b/reporter/util.go index bb50c950..8671e22f 100644 --- a/reporter/util.go +++ b/reporter/util.go @@ -373,8 +373,8 @@ No CVE-IDs are found in updatable packages. if len(pack.AffectedProcs) != 0 { for _, p := range pack.AffectedProcs { if len(p.ListenPortStats) == 0 { - data = append(data, []string{"", - fmt.Sprintf(" - PID: %s %s, Port: []", p.PID, p.Name)}) + data = append(data, []string{"", fmt.Sprintf(" - PID: %s %s", p.PID, p.Name)}) + continue } var ports []string @@ -412,8 +412,7 @@ No CVE-IDs are found in updatable packages. wp.Name, p.Version, p.Update, wp.FixedIn, p.Status)}) } } else { - data = append(data, []string{"WordPress", - fmt.Sprintf("%s", wp.Name)}) + data = append(data, []string{"WordPress", wp.Name}) } } diff --git a/scanner/redhatbase.go b/scanner/redhatbase.go index 18f0347d..11b95fa2 100644 --- a/scanner/redhatbase.go +++ b/scanner/redhatbase.go @@ -614,12 +614,7 @@ func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error func (o *redhatBase) isExecYumPS() bool { switch o.Distro.Family { - case constant.Oracle, - constant.OpenSUSE, - constant.OpenSUSELeap, - constant.SUSEEnterpriseServer, - constant.SUSEEnterpriseDesktop, - constant.SUSEOpenstackCloud: + case constant.Oracle: return false } return !o.getServerInfo().Mode.IsFast() @@ -627,13 +622,12 @@ func (o *redhatBase) isExecYumPS() bool { func (o *redhatBase) isExecNeedsRestarting() bool { switch o.Distro.Family { - case constant.OpenSUSE, - constant.OpenSUSELeap, - constant.SUSEEnterpriseServer, - constant.SUSEEnterpriseDesktop, - constant.SUSEOpenstackCloud: - // TODO zypper ps - // https://github.com/future-architect/vuls/issues/696 + case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop: + if o.getServerInfo().Mode.IsOffline() { + return false + } else if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() { + return true + } return false case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky, constant.Oracle: majorVersion, err := o.Distro.MajorVersion() @@ -644,8 +638,7 @@ func (o *redhatBase) isExecNeedsRestarting() bool { if o.getServerInfo().Mode.IsOffline() { return false - } else if o.getServerInfo().Mode.IsFastRoot() || - o.getServerInfo().Mode.IsDeep() { + } else if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() { return true } return false @@ -793,7 +786,14 @@ func (o *redhatBase) rpmQa() string { const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"` const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"` switch o.Distro.Family { - case constant.SUSEEnterpriseServer: + case constant.OpenSUSE: + if o.Distro.Release == "tumbleweed" { + return new + } + return old + case constant.OpenSUSELeap: + return new + case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop: if v, _ := o.Distro.MajorVersion(); v < 12 { return old } @@ -810,7 +810,14 @@ func (o *redhatBase) rpmQf() string { const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" ` const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" ` switch o.Distro.Family { - case constant.SUSEEnterpriseServer: + case constant.OpenSUSE: + if o.Distro.Release == "tumbleweed" { + return new + } + return old + case constant.OpenSUSELeap: + return new + case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop: if v, _ := o.Distro.MajorVersion(); v < 12 { return old } diff --git a/scanner/suse.go b/scanner/suse.go index 71676d21..eca253ce 100644 --- a/scanner/suse.go +++ b/scanner/suse.go @@ -19,7 +19,7 @@ type suse struct { redhatBase } -// NewRedhat is constructor +// newSUSE is constructor func newSUSE(c config.ServerInfo) *suse { r := &suse{ redhatBase: redhatBase{ @@ -43,6 +43,10 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) { if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() { s := newSUSE(c) name, ver := s.parseOSRelease(r.Stdout) + if name == "" || ver == "" { + s.setErrs([]error{xerrors.Errorf("Failed to parse /etc/os-release: %s", r.Stdout)}) + return true, s + } s.setDistro(name, ver) return true, s } @@ -50,11 +54,10 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) { } else if r := exec(c, "ls /etc/SuSE-release", noSudo); r.isSuccess() { if r := exec(c, "zypper -V", noSudo); r.isSuccess() { if r := exec(c, "cat /etc/SuSE-release", noSudo); r.isSuccess() { + s := newSUSE(c) re := regexp.MustCompile(`openSUSE (\d+\.\d+|\d+)`) result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout)) if len(result) == 2 { - //TODO check opensuse or opensuse.leap - s := newSUSE(c) s.setDistro(constant.OpenSUSE, result[1]) return true, s } @@ -66,14 +69,12 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) { re = regexp.MustCompile(`PATCHLEVEL = (\d+)`) result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout)) if len(result) == 2 { - s := newSUSE(c) - s.setDistro(constant.SUSEEnterpriseServer, - fmt.Sprintf("%s.%s", version, result[1])) + s.setDistro(constant.SUSEEnterpriseServer, fmt.Sprintf("%s.%s", version, result[1])) return true, s } } - logging.Log.Warnf("Failed to parse SUSE Linux version: %s", r) - return true, newSUSE(c) + s.setErrs([]error{xerrors.Errorf("Failed to parse /etc/SuSE-release: %s", r.Stdout)}) + return true, s } } } @@ -82,23 +83,24 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) { } func (o *suse) parseOSRelease(content string) (name string, ver string) { - if strings.Contains(content, "ID=opensuse") { - //TODO check opensuse or opensuse.leap + if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:opensuse`) { name = constant.OpenSUSE - } else if strings.Contains(content, `NAME="SLES"`) { - name = constant.SUSEEnterpriseServer - } else if strings.Contains(content, `NAME="SLES_SAP"`) { + } else if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:tumbleweed`) { + return constant.OpenSUSE, "tumbleweed" + } else if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:leap`) { + name = constant.OpenSUSELeap + } else if strings.Contains(content, `CPE_NAME="cpe:/o:suse:sles`) { name = constant.SUSEEnterpriseServer + } else if strings.Contains(content, `CPE_NAME="cpe:/o:suse:sled`) { + name = constant.SUSEEnterpriseDesktop } else { - logging.Log.Warnf("Failed to parse SUSE edition: %s", content) - return "unknown", "unknown" + return "", "" } re := regexp.MustCompile(`VERSION_ID=\"(.+)\"`) result := re.FindStringSubmatch(strings.TrimSpace(content)) if len(result) != 2 { - logging.Log.Warnf("Failed to parse SUSE Linux version: %s", content) - return "unknown", "unknown" + return "", "" } return name, result[1] } @@ -108,14 +110,60 @@ func (o *suse) checkScanMode() error { } func (o *suse) checkDeps() error { - o.log.Infof("Dependencies... No need") - return nil + if o.getServerInfo().Mode.IsFast() { + return o.execCheckDeps(o.depsFast()) + } else if o.getServerInfo().Mode.IsFastRoot() { + return o.execCheckDeps(o.depsFastRoot()) + } else if o.getServerInfo().Mode.IsDeep() { + return o.execCheckDeps(o.depsDeep()) + } + return xerrors.New("Unknown scan mode") +} + +func (o *suse) depsFast() []string { + return []string{} +} + +func (o *suse) depsFastRoot() []string { + return []string{} +} + +func (o *suse) depsDeep() []string { + return o.depsFastRoot() } func (o *suse) checkIfSudoNoPasswd() error { - // SUSE doesn't need root privilege - o.log.Infof("sudo ... No need") - return nil + if o.getServerInfo().Mode.IsFast() { + return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsFast()) + } else if o.getServerInfo().Mode.IsFastRoot() { + return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsFastRoot()) + } else { + return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsDeep()) + } +} + +func (o *suse) sudoNoPasswdCmdsFast() []cmd { + return []cmd{} +} + +func (o *suse) sudoNoPasswdCmdsDeep() []cmd { + return o.sudoNoPasswdCmdsFastRoot() +} + +func (o *suse) sudoNoPasswdCmdsFastRoot() []cmd { + if !o.ServerInfo.IsContainer() { + return []cmd{ + {"zypper ps -s", exitStatusZero}, + {"which which", exitStatusZero}, + {"stat /proc/1/exe", exitStatusZero}, + {"ls -l /proc/1/exe", exitStatusZero}, + {"cat /proc/1/maps", exitStatusZero}, + {"lsof -i -P -n", exitStatusZero}, + } + } + return []cmd{ + {"zypper ps -s", exitStatusZero}, + } } func (o *suse) scanPackages() error { @@ -176,13 +224,14 @@ func (o *suse) scanUpdatablePackages() (models.Packages, error) { return o.parseZypperLULines(r.Stdout) } +var warnRepoPattern = regexp.MustCompile(`Warning: Repository '.+' appears to be outdated\. Consider using a different mirror or server\.`) + func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) { updatables := models.Packages{} scanner := bufio.NewScanner(strings.NewReader(stdout)) for scanner.Scan() { line := scanner.Text() - if strings.Index(line, "S | Repository") != -1 || - strings.Index(line, "--+----------------") != -1 { + if strings.Contains(line, "S | Repository") || strings.Contains(line, "--+----------------") || warnRepoPattern.MatchString(line) { continue } pack, err := o.parseZypperLUOneLine(line) @@ -213,3 +262,107 @@ func (o *suse) hasZypperColorOption() bool { r := o.exec(util.PrependProxyEnv(cmd), noSudo) return len(r.Stdout) > 0 } + +func (o *suse) postScan() error { + if o.isExecYumPS() { + if err := o.pkgPs(o.getOwnerPkgs); err != nil { + err = xerrors.Errorf("Failed to execute zypper-ps: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } + } + + if o.isExecNeedsRestarting() { + if err := o.needsRestarting(); err != nil { + err = xerrors.Errorf("Failed to execute need-restarting: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } + } + return nil +} + +func (o *suse) needsRestarting() error { + initName, err := o.detectInitSystem() + if err != nil { + o.log.Warn(err) + // continue scanning + } + + cmd := "LANGUAGE=en_US.UTF-8 zypper ps -s" + r := o.exec(cmd, sudo) + if !r.isSuccess() { + return xerrors.Errorf("Failed to SSH: %w", r) + } + procs := o.parseNeedsRestarting(r.Stdout) + for _, proc := range procs { + //TODO refactor + fqpn, err := o.procPathToFQPN(proc.Path) + if err != nil { + o.log.Warnf("Failed to detect a package name of need restarting process from the command path: %s, %s", + proc.Path, err) + continue + } + pack, err := o.Packages.FindByFQPN(fqpn) + if err != nil { + return err + } + if initName == systemd { + name, err := o.detectServiceName(proc.PID) + if err != nil { + o.log.Warn(err) + // continue scanning + } + proc.ServiceName = name + proc.InitSystem = systemd + } + pack.NeedRestartProcs = append(pack.NeedRestartProcs, proc) + o.Packages[pack.Name] = *pack + } + return nil +} + +func (o *suse) parseNeedsRestarting(stdout string) []models.NeedRestartProcess { + procs := []models.NeedRestartProcess{} + + // PID | PPID | UID | User | Command | Service + // ----+------+-----+------+---------+----------- + // 9 | 7 | 0 | root | bash | containerd + // 53 | 9 | 0 | root | zypper | containerd + // 55 | 53 | 0 | root | lsof | + + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + ss := strings.Split(line, " | ") + if len(ss) < 6 { + continue + } + pid := strings.TrimSpace(ss[0]) + if strings.HasPrefix(pid, "PID") { + continue + } + // https://unix.stackexchange.com/a/419375 + if pid == "1" { + continue + } + + cmd := strings.TrimSpace(ss[4]) + whichCmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 which %s", cmd) + r := o.exec(whichCmd, sudo) + if !r.isSuccess() { + o.log.Debugf("Failed to exec which %s: %s", cmd, r) + continue + } + path := strings.TrimSpace(r.Stdout) + + procs = append(procs, models.NeedRestartProcess{ + PID: pid, + Path: path, + HasInit: true, + }) + } + return procs +} diff --git a/scanner/suse_test.go b/scanner/suse_test.go index ca5ecd69..29ca264b 100644 --- a/scanner/suse_test.go +++ b/scanner/suse_test.go @@ -105,28 +105,41 @@ func TestParseOSRelease(t *testing.T) { ver string }{ { - in: `NAME="openSUSE Leap" -ID=opensuse -VERSION_ID="42.3.4"`, + in: `CPE_NAME="cpe:/o:opensuse:opensuse:13.2" +VERSION_ID="13.2"`, name: constant.OpenSUSE, + ver: "13.2", + }, + { + in: `CPE_NAME="cpe:/o:opensuse:tumbleweed:20220124" +VERSION_ID="20220124"`, + name: constant.OpenSUSE, + ver: "tumbleweed", + }, + { + in: `CPE_NAME="cpe:/o:opensuse:leap:42.3" +VERSION_ID="42.3.4"`, + name: constant.OpenSUSELeap, ver: "42.3.4", }, { - in: `NAME="SLES" -VERSION="12-SP1" -VERSION_ID="12.1" -ID="sles"`, + in: `CPE_NAME="cpe:/o:suse:sles:12:sp1" +VERSION_ID="12.1"`, name: constant.SUSEEnterpriseServer, ver: "12.1", }, { - in: `NAME="SLES_SAP" -VERSION="12-SP1" -VERSION_ID="12.1.0.1" -ID="sles"`, + in: `CPE_NAME="cpe:/o:suse:sles_sap:12:sp1" +VERSION_ID="12.1.0.1"`, name: constant.SUSEEnterpriseServer, ver: "12.1.0.1", }, + { + in: `CPE_NAME="cpe:/o:suse:sled:15" +VERSION_ID="15"`, + name: constant.SUSEEnterpriseDesktop, + ver: "15", + }, } r := newSUSE(config.ServerInfo{}) diff --git a/scanner/utils.go b/scanner/utils.go index 5bc38fda..05fea072 100644 --- a/scanner/utils.go +++ b/scanner/utils.go @@ -16,7 +16,7 @@ import ( func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) { switch family { - case constant.SUSEEnterpriseServer: + case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop: if pack.Name == "kernel-default" { // Remove the last period and later because uname don't show that. ss := strings.Split(pack.Release, ".") diff --git a/tui/tui.go b/tui/tui.go index a4e650c9..4e2feebf 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -719,8 +719,7 @@ func setChangelogLayout(g *gocui.Gui) error { if len(pack.AffectedProcs) != 0 { for _, p := range pack.AffectedProcs { if len(p.ListenPortStats) == 0 { - lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: []", - p.PID, p.Name)) + lines = append(lines, fmt.Sprintf(" * PID: %s %s", p.PID, p.Name)) continue } @@ -733,8 +732,7 @@ func setChangelogLayout(g *gocui.Gui) error { } } - lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s", - p.PID, p.Name, ports)) + lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s", p.PID, p.Name, ports)) } } }