Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b91a7b75e2 | ||
|
|
333eae06ea | ||
|
|
93d401c70c | ||
|
|
99dc8e892f | ||
|
|
fb904f0543 | ||
|
|
d4d33fc81d | ||
|
|
a1d3fbf66f | ||
|
|
2cdfbe3bb4 | ||
|
|
ac8290119d |
@@ -127,6 +127,9 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"9": {StandardSupportUntil: time.Date(2022, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"10": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"11": {StandardSupportUntil: time.Date(2026, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
// "12": {StandardSupportUntil: time.Date(2028, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
// "13": {StandardSupportUntil: time.Date(2030, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
// "14": {StandardSupportUntil: time.Date(2032, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case constant.Raspbian:
|
||||
// Not found
|
||||
|
||||
@@ -425,20 +425,20 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logO
|
||||
}
|
||||
}()
|
||||
|
||||
logging.Log.Debugf("Check if oval fetched: %s %s", r.Family, r.Release)
|
||||
ok, err := client.CheckIfOvalFetched(r.Family, r.Release)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
switch r.Family {
|
||||
case constant.Debian, constant.Ubuntu:
|
||||
logging.Log.Infof("Skip OVAL and Scan with gost alone.")
|
||||
logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0)
|
||||
return nil
|
||||
case constant.Windows, constant.FreeBSD, constant.ServerTypePseudo:
|
||||
return nil
|
||||
default:
|
||||
switch r.Family {
|
||||
case constant.Debian, constant.Raspbian, constant.Ubuntu:
|
||||
logging.Log.Infof("Skip OVAL and Scan with gost alone.")
|
||||
logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0)
|
||||
return nil
|
||||
case constant.Windows, constant.FreeBSD, constant.ServerTypePseudo:
|
||||
return nil
|
||||
default:
|
||||
logging.Log.Debugf("Check if oval fetched: %s %s", r.Family, r.Release)
|
||||
ok, err := client.CheckIfOvalFetched(r.Family, r.Release)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/vulsio/goval-dictionary#usage`", r.Family, r.Release)
|
||||
}
|
||||
}
|
||||
@@ -473,7 +473,7 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts l
|
||||
nCVEs, err := client.DetectCVEs(r, true)
|
||||
if err != nil {
|
||||
switch r.Family {
|
||||
case constant.Debian, constant.Ubuntu, constant.Windows:
|
||||
case constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Windows:
|
||||
return xerrors.Errorf("Failed to detect CVEs with gost: %w", err)
|
||||
default:
|
||||
return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
|
||||
@@ -481,7 +481,7 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts l
|
||||
}
|
||||
|
||||
switch r.Family {
|
||||
case constant.Debian, constant.Ubuntu, constant.Windows:
|
||||
case constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Windows:
|
||||
logging.Log.Infof("%s: %d CVEs are detected with gost", r.FormatServerName(), nCVEs)
|
||||
default:
|
||||
logging.Log.Infof("%s: %d unfixed CVEs are detected with gost", r.FormatServerName(), nCVEs)
|
||||
|
||||
@@ -12,7 +12,9 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/future-architect/vuls/errof"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@@ -222,9 +224,9 @@ func DetectGitHubDependencyGraph(r *models.ScanResult, owner, repo, token string
|
||||
// recursive function
|
||||
func fetchDependencyGraph(r *models.ScanResult, httpClient *http.Client, owner, repo, after, dependenciesAfter string) (err error) {
|
||||
const queryFmt = `{"query":
|
||||
"query { repository(owner:\"%s\", name:\"%s\") { url dependencyGraphManifests(first: %d, withDependencies: true%s) { pageInfo { endCursor hasNextPage } edges { node { blobPath filename repository { url } parseable exceedsMaxSize dependenciesCount dependencies%s { pageInfo { endCursor hasNextPage } edges { node { packageName packageManager repository { url } requirements hasDependencies } } } } } } } }"}`
|
||||
"query { repository(owner:\"%s\", name:\"%s\") { url dependencyGraphManifests(first: %d, withDependencies: true%s) { pageInfo { endCursor hasNextPage } edges { node { blobPath filename repository { url } parseable exceedsMaxSize dependenciesCount dependencies(first: %d%s) { pageInfo { endCursor hasNextPage } edges { node { packageName packageManager repository { url } requirements hasDependencies } } } } } } } }"}`
|
||||
|
||||
queryStr := fmt.Sprintf(queryFmt, owner, repo, 100, after, dependenciesAfter)
|
||||
queryStr := fmt.Sprintf(queryFmt, owner, repo, 50, after, 100, dependenciesAfter)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||
"https://api.github.com/graphql",
|
||||
@@ -240,25 +242,44 @@ func fetchDependencyGraph(r *models.ScanResult, httpClient *http.Client, owner,
|
||||
req.Header.Set("Accept", "application/vnd.github.hawkgirl-preview+json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph := DependencyGraph{}
|
||||
if err := json.Unmarshal(body, &graph); err != nil {
|
||||
count, retryMax := 0, 10
|
||||
countCheck := func(err error) error {
|
||||
if count == retryMax {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
operation := func() error {
|
||||
count++
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return countCheck(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if graph.Data.Repository.URL == "" {
|
||||
return errof.New(errof.ErrFailedToAccessGithubAPI,
|
||||
fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body)))
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return countCheck(err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &graph); err != nil {
|
||||
return countCheck(err)
|
||||
}
|
||||
|
||||
if len(graph.Errors) > 0 || graph.Data.Repository.URL == "" {
|
||||
return countCheck(errof.New(errof.ErrFailedToAccessGithubAPI,
|
||||
fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body))))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
logging.Log.Warnf("Failed trial (count: %d). retrying in %s. err: %+v", count, t, err)
|
||||
}
|
||||
|
||||
if err = backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dependenciesAfter = ""
|
||||
@@ -283,7 +304,7 @@ func fetchDependencyGraph(r *models.ScanResult, httpClient *http.Client, owner,
|
||||
r.GitHubManifests[m.Node.BlobPath] = manifest
|
||||
|
||||
if m.Node.Dependencies.PageInfo.HasNextPage {
|
||||
dependenciesAfter = fmt.Sprintf(`(after: \"%s\")`, m.Node.Dependencies.PageInfo.EndCursor)
|
||||
dependenciesAfter = fmt.Sprintf(`, after: \"%s\"`, m.Node.Dependencies.PageInfo.EndCursor)
|
||||
}
|
||||
}
|
||||
if dependenciesAfter != "" {
|
||||
@@ -340,4 +361,13 @@ type DependencyGraph struct {
|
||||
} `json:"dependencyGraphManifests"`
|
||||
} `json:"repository"`
|
||||
} `json:"data"`
|
||||
Errors []struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Path []interface{} `json:"path,omitempty"`
|
||||
Locations []struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
} `json:"locations,omitempty"`
|
||||
Message string `json:"message"`
|
||||
} `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -17,6 +17,7 @@ require (
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-smtp v0.14.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/subcommands v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gosnmp/gosnmp v1.35.0
|
||||
@@ -98,7 +99,6 @@ require (
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-containerregistry v0.12.0 // indirect
|
||||
github.com/google/licenseclassifier/v2 v2.0.0-pre6 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
|
||||
|
||||
390
gost/debian.go
390
gost/debian.go
@@ -5,8 +5,12 @@ package gost
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
debver "github.com/knqyf263/go-deb-version"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/logging"
|
||||
@@ -20,19 +24,16 @@ type Debian struct {
|
||||
Base
|
||||
}
|
||||
|
||||
type packCves struct {
|
||||
packName string
|
||||
isSrcPack bool
|
||||
cves []models.CveContent
|
||||
fixes models.PackageFixStatuses
|
||||
}
|
||||
|
||||
func (deb Debian) supported(major string) bool {
|
||||
_, ok := map[string]string{
|
||||
"7": "wheezy",
|
||||
"8": "jessie",
|
||||
"9": "stretch",
|
||||
"10": "buster",
|
||||
"11": "bullseye",
|
||||
// "12": "bookworm",
|
||||
// "13": "trixie",
|
||||
// "14": "forky",
|
||||
}[major]
|
||||
return ok
|
||||
}
|
||||
@@ -45,199 +46,218 @@ func (deb Debian) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Add linux and set the version of running kernel to search Gost.
|
||||
if r.Container.ContainerID == "" {
|
||||
if r.RunningKernel.Version != "" {
|
||||
newVer := ""
|
||||
if p, ok := r.Packages["linux-image-"+r.RunningKernel.Release]; ok {
|
||||
newVer = p.NewVersion
|
||||
}
|
||||
r.Packages["linux"] = models.Package{
|
||||
Name: "linux",
|
||||
Version: r.RunningKernel.Version,
|
||||
NewVersion: newVer,
|
||||
}
|
||||
} else {
|
||||
logging.Log.Warnf("Since the exact kernel version is not available, the vulnerability in the linux package is not detected.")
|
||||
if r.RunningKernel.Release == "" {
|
||||
logging.Log.Warnf("Since the exact kernel release is not available, the vulnerability in the kernel package is not detected.")
|
||||
}
|
||||
}
|
||||
|
||||
var stashLinuxPackage models.Package
|
||||
if linux, ok := r.Packages["linux"]; ok {
|
||||
stashLinuxPackage = linux
|
||||
}
|
||||
nFixedCVEs, err := deb.detectCVEsWithFixState(r, "resolved")
|
||||
fixedCVEs, err := deb.detectCVEsWithFixState(r, true)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect fixed CVEs. err: %w", err)
|
||||
}
|
||||
|
||||
if stashLinuxPackage.Name != "" {
|
||||
r.Packages["linux"] = stashLinuxPackage
|
||||
}
|
||||
nUnfixedCVEs, err := deb.detectCVEsWithFixState(r, "open")
|
||||
unfixedCVEs, err := deb.detectCVEsWithFixState(r, false)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect unfixed CVEs. err: %w", err)
|
||||
}
|
||||
|
||||
return (nFixedCVEs + nUnfixedCVEs), nil
|
||||
return len(unique(append(fixedCVEs, unfixedCVEs...))), nil
|
||||
}
|
||||
|
||||
func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string) (nCVEs int, err error) {
|
||||
if fixStatus != "resolved" && fixStatus != "open" {
|
||||
return 0, xerrors.Errorf(`Failed to detectCVEsWithFixState. fixStatus is not allowed except "open" and "resolved"(actual: fixStatus -> %s).`, fixStatus)
|
||||
}
|
||||
|
||||
packCvesList := []packCves{}
|
||||
func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]string, error) {
|
||||
detects := map[string]cveContent{}
|
||||
if deb.driver == nil {
|
||||
url, err := util.URLPathJoin(deb.baseURL, "debian", major(r.Release), "pkgs")
|
||||
urlPrefix, err := util.URLPathJoin(deb.baseURL, "debian", major(r.Release), "pkgs")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
return nil, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
|
||||
s := "unfixed-cves"
|
||||
if s == "resolved" {
|
||||
s = "fixed-cves"
|
||||
s := "fixed-cves"
|
||||
if !fixed {
|
||||
s = "unfixed-cves"
|
||||
}
|
||||
responses, err := getCvesWithFixStateViaHTTP(r, url, s)
|
||||
responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, s)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get CVEs via HTTP. err: %w", err)
|
||||
return nil, xerrors.Errorf("Failed to get CVEs via HTTP. err: %w", err)
|
||||
}
|
||||
|
||||
for _, res := range responses {
|
||||
debCves := map[string]gostmodels.DebianCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &debCves); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
if !res.request.isSrcPack {
|
||||
continue
|
||||
}
|
||||
cves := []models.CveContent{}
|
||||
fixes := []models.PackageFixStatus{}
|
||||
for _, debcve := range debCves {
|
||||
cves = append(cves, *deb.ConvertToModel(&debcve))
|
||||
fixes = append(fixes, checkPackageFixStatus(&debcve)...)
|
||||
}
|
||||
packCvesList = append(packCvesList, packCves{
|
||||
packName: res.request.packName,
|
||||
isSrcPack: res.request.isSrcPack,
|
||||
cves: cves,
|
||||
fixes: fixes,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, pack := range r.Packages {
|
||||
cves, fixes, err := deb.getCvesDebianWithfixStatus(fixStatus, major(r.Release), pack.Name)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get CVEs for Package. err: %w", err)
|
||||
}
|
||||
packCvesList = append(packCvesList, packCves{
|
||||
packName: pack.Name,
|
||||
isSrcPack: false,
|
||||
cves: cves,
|
||||
fixes: fixes,
|
||||
})
|
||||
}
|
||||
|
||||
// SrcPack
|
||||
for _, pack := range r.SrcPackages {
|
||||
cves, fixes, err := deb.getCvesDebianWithfixStatus(fixStatus, major(r.Release), pack.Name)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get CVEs for SrcPackage. err: %w", err)
|
||||
}
|
||||
packCvesList = append(packCvesList, packCves{
|
||||
packName: pack.Name,
|
||||
isSrcPack: true,
|
||||
cves: cves,
|
||||
fixes: fixes,
|
||||
})
|
||||
}
|
||||
}
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-latest", "linux", "-amd64", "", "-arm64", "", "-i386", "").Replace(res.request.packName)
|
||||
|
||||
delete(r.Packages, "linux")
|
||||
|
||||
for _, p := range packCvesList {
|
||||
for i, cve := range p.cves {
|
||||
v, ok := r.ScannedCves[cve.CveID]
|
||||
if ok {
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.NewCveContents(cve)
|
||||
} else {
|
||||
v.CveContents[models.DebianSecurityTracker] = []models.CveContent{cve}
|
||||
v.Confidences = models.Confidences{models.DebianSecurityTrackerMatch}
|
||||
}
|
||||
} else {
|
||||
v = models.VulnInfo{
|
||||
CveID: cve.CveID,
|
||||
CveContents: models.NewCveContents(cve),
|
||||
Confidences: models.Confidences{models.DebianSecurityTrackerMatch},
|
||||
}
|
||||
|
||||
if fixStatus == "resolved" {
|
||||
versionRelease := ""
|
||||
if p.isSrcPack {
|
||||
versionRelease = r.SrcPackages[p.packName].Version
|
||||
} else {
|
||||
versionRelease = r.Packages[p.packName].FormatVer()
|
||||
}
|
||||
|
||||
if versionRelease == "" {
|
||||
if deb.isKernelSourcePackage(n) {
|
||||
isRunning := false
|
||||
for _, bn := range r.SrcPackages[res.request.packName].BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
|
||||
affected, err := isGostDefAffected(versionRelease, p.fixes[i].FixedIn)
|
||||
if err != nil {
|
||||
logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s",
|
||||
err, versionRelease, p.fixes[i].FixedIn)
|
||||
continue
|
||||
}
|
||||
|
||||
if !affected {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
nCVEs++
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
if p.isSrcPack {
|
||||
if srcPack, ok := r.SrcPackages[p.packName]; ok {
|
||||
for _, binName := range srcPack.BinaryNames {
|
||||
if _, ok := r.Packages[binName]; ok {
|
||||
names = append(names, binName)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if p.packName == "linux" {
|
||||
names = append(names, "linux-image-"+r.RunningKernel.Release)
|
||||
} else {
|
||||
names = append(names, p.packName)
|
||||
// To detect vulnerabilities in running kernels only, skip if the kernel is not running.
|
||||
if !isRunning {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if fixStatus == "resolved" {
|
||||
for _, name := range names {
|
||||
v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
|
||||
Name: name,
|
||||
FixedIn: p.fixes[i].FixedIn,
|
||||
})
|
||||
cs := map[string]gostmodels.DebianCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &cs); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
}
|
||||
for _, content := range deb.detect(cs, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, models.Kernel{Release: r.RunningKernel.Release, Version: r.Packages[fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)].Version}) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
} else {
|
||||
for _, name := range names {
|
||||
v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
|
||||
Name: name,
|
||||
FixState: "open",
|
||||
NotFixedYet: true,
|
||||
})
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, p := range r.SrcPackages {
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-latest", "linux", "-amd64", "", "-arm64", "", "-i386", "").Replace(p.Name)
|
||||
|
||||
if deb.isKernelSourcePackage(n) {
|
||||
isRunning := false
|
||||
for _, bn := range p.BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// To detect vulnerabilities in running kernels only, skip if the kernel is not running.
|
||||
if !isRunning {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
r.ScannedCves[cve.CveID] = v
|
||||
var f func(string, string) (map[string]gostmodels.DebianCVE, error) = deb.driver.GetFixedCvesDebian
|
||||
if !fixed {
|
||||
f = deb.driver.GetUnfixedCvesDebian
|
||||
}
|
||||
cs, err := f(major(r.Release), n)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to get CVEs. release: %s, src package: %s, err: %w", major(r.Release), p.Name, err)
|
||||
}
|
||||
for _, content := range deb.detect(cs, p, models.Kernel{Release: r.RunningKernel.Release, Version: r.Packages[fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)].Version}) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nCVEs, nil
|
||||
for _, content := range detects {
|
||||
v, ok := r.ScannedCves[content.cveContent.CveID]
|
||||
if ok {
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.NewCveContents(content.cveContent)
|
||||
} else {
|
||||
v.CveContents[models.DebianSecurityTracker] = []models.CveContent{content.cveContent}
|
||||
}
|
||||
v.Confidences.AppendIfMissing(models.DebianSecurityTrackerMatch)
|
||||
} else {
|
||||
v = models.VulnInfo{
|
||||
CveID: content.cveContent.CveID,
|
||||
CveContents: models.NewCveContents(content.cveContent),
|
||||
Confidences: models.Confidences{models.DebianSecurityTrackerMatch},
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range content.fixStatuses {
|
||||
v.AffectedPackages = v.AffectedPackages.Store(s)
|
||||
}
|
||||
r.ScannedCves[content.cveContent.CveID] = v
|
||||
}
|
||||
|
||||
return maps.Keys(detects), nil
|
||||
}
|
||||
|
||||
func isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) {
|
||||
func (deb Debian) isKernelSourcePackage(pkgname string) bool {
|
||||
switch ss := strings.Split(pkgname, "-"); len(ss) {
|
||||
case 1:
|
||||
return pkgname == "linux"
|
||||
case 2:
|
||||
if ss[0] != "linux" {
|
||||
return false
|
||||
}
|
||||
switch ss[1] {
|
||||
case "grsec":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[1], 64)
|
||||
return err == nil
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (deb Debian) detect(cves map[string]gostmodels.DebianCVE, srcPkg models.SrcPackage, runningKernel models.Kernel) []cveContent {
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-latest", "linux", "-amd64", "", "-arm64", "", "-i386", "").Replace(srcPkg.Name)
|
||||
|
||||
var contents []cveContent
|
||||
for _, cve := range cves {
|
||||
c := cveContent{
|
||||
cveContent: *(Debian{}).ConvertToModel(&cve),
|
||||
}
|
||||
|
||||
for _, p := range cve.Package {
|
||||
for _, r := range p.Release {
|
||||
switch r.Status {
|
||||
case "open", "undetermined":
|
||||
for _, bn := range srcPkg.BinaryNames {
|
||||
if deb.isKernelSourcePackage(n) && bn != fmt.Sprintf("linux-image-%s", runningKernel.Release) {
|
||||
continue
|
||||
}
|
||||
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
||||
Name: bn,
|
||||
FixState: r.Status,
|
||||
NotFixedYet: true,
|
||||
})
|
||||
}
|
||||
case "resolved":
|
||||
installedVersion := srcPkg.Version
|
||||
patchedVersion := r.FixedVersion
|
||||
|
||||
if deb.isKernelSourcePackage(n) {
|
||||
installedVersion = runningKernel.Version
|
||||
}
|
||||
|
||||
affected, err := deb.isGostDefAffected(installedVersion, patchedVersion)
|
||||
if err != nil {
|
||||
logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s", err, installedVersion, patchedVersion)
|
||||
continue
|
||||
}
|
||||
|
||||
if affected {
|
||||
for _, bn := range srcPkg.BinaryNames {
|
||||
if deb.isKernelSourcePackage(n) && bn != fmt.Sprintf("linux-image-%s", runningKernel.Release) {
|
||||
continue
|
||||
}
|
||||
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
||||
Name: bn,
|
||||
FixedIn: patchedVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
logging.Log.Debugf("Failed to check vulnerable CVE. err: unknown status: %s", r.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.fixStatuses) > 0 {
|
||||
contents = append(contents, c)
|
||||
}
|
||||
}
|
||||
return contents
|
||||
}
|
||||
|
||||
func (deb Debian) isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) {
|
||||
vera, err := debver.NewVersion(versionRelease)
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", versionRelease, err)
|
||||
@@ -249,27 +269,6 @@ func isGostDefAffected(versionRelease, gostVersion string) (affected bool, err e
|
||||
return vera.LessThan(verb), nil
|
||||
}
|
||||
|
||||
func (deb Debian) getCvesDebianWithfixStatus(fixStatus, release, pkgName string) ([]models.CveContent, []models.PackageFixStatus, error) {
|
||||
var f func(string, string) (map[string]gostmodels.DebianCVE, error)
|
||||
if fixStatus == "resolved" {
|
||||
f = deb.driver.GetFixedCvesDebian
|
||||
} else {
|
||||
f = deb.driver.GetUnfixedCvesDebian
|
||||
}
|
||||
debCves, err := f(release, pkgName)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("Failed to get CVEs. fixStatus: %s, release: %s, src package: %s, err: %w", fixStatus, release, pkgName, err)
|
||||
}
|
||||
|
||||
cves := []models.CveContent{}
|
||||
fixes := []models.PackageFixStatus{}
|
||||
for _, devbCve := range debCves {
|
||||
cves = append(cves, *deb.ConvertToModel(&devbCve))
|
||||
fixes = append(fixes, checkPackageFixStatus(&devbCve)...)
|
||||
}
|
||||
return cves, fixes, nil
|
||||
}
|
||||
|
||||
// ConvertToModel converts gost model to vuls model
|
||||
func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
|
||||
severity := ""
|
||||
@@ -279,34 +278,17 @@ func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
|
||||
break
|
||||
}
|
||||
}
|
||||
var optinal map[string]string
|
||||
if cve.Scope != "" {
|
||||
optinal = map[string]string{"attack range": cve.Scope}
|
||||
}
|
||||
return &models.CveContent{
|
||||
Type: models.DebianSecurityTracker,
|
||||
CveID: cve.CveID,
|
||||
Summary: cve.Description,
|
||||
Cvss2Severity: severity,
|
||||
Cvss3Severity: severity,
|
||||
SourceLink: "https://security-tracker.debian.org/tracker/" + cve.CveID,
|
||||
Optional: map[string]string{
|
||||
"attack range": cve.Scope,
|
||||
},
|
||||
SourceLink: fmt.Sprintf("https://security-tracker.debian.org/tracker/%s", cve.CveID),
|
||||
Optional: optinal,
|
||||
}
|
||||
}
|
||||
|
||||
func checkPackageFixStatus(cve *gostmodels.DebianCVE) []models.PackageFixStatus {
|
||||
fixes := []models.PackageFixStatus{}
|
||||
for _, p := range cve.Package {
|
||||
for _, r := range p.Release {
|
||||
f := models.PackageFixStatus{Name: p.PackageName}
|
||||
|
||||
if r.Status == "open" {
|
||||
f.NotFixedYet = true
|
||||
} else {
|
||||
f.FixedIn = r.FixedVersion
|
||||
}
|
||||
|
||||
fixes = append(fixes, f)
|
||||
}
|
||||
}
|
||||
|
||||
return fixes
|
||||
}
|
||||
|
||||
@@ -3,69 +3,344 @@
|
||||
|
||||
package gost
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
gostmodels "github.com/vulsio/gost/models"
|
||||
)
|
||||
|
||||
func TestDebian_Supported(t *testing.T) {
|
||||
type fields struct {
|
||||
Base Base
|
||||
}
|
||||
type args struct {
|
||||
major string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
args string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "7 is supported",
|
||||
args: "7",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "8 is supported",
|
||||
args: args{
|
||||
major: "8",
|
||||
},
|
||||
args: "8",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "9 is supported",
|
||||
args: args{
|
||||
major: "9",
|
||||
},
|
||||
args: "9",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "10 is supported",
|
||||
args: args{
|
||||
major: "10",
|
||||
},
|
||||
args: "10",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "11 is supported",
|
||||
args: args{
|
||||
major: "11",
|
||||
},
|
||||
args: "11",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "12 is not supported yet",
|
||||
args: args{
|
||||
major: "12",
|
||||
},
|
||||
args: "12",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "13 is not supported yet",
|
||||
args: "13",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "14 is not supported yet",
|
||||
args: "14",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty string is not supported yet",
|
||||
args: args{
|
||||
major: "",
|
||||
},
|
||||
args: "",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deb := Debian{}
|
||||
if got := deb.supported(tt.args.major); got != tt.want {
|
||||
if got := (Debian{}).supported(tt.args); got != tt.want {
|
||||
t.Errorf("Debian.Supported() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebian_ConvertToModel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args gostmodels.DebianCVE
|
||||
want models.CveContent
|
||||
}{
|
||||
{
|
||||
name: "gost Debian.ConvertToModel",
|
||||
args: gostmodels.DebianCVE{
|
||||
CveID: "CVE-2022-39260",
|
||||
Scope: "local",
|
||||
Description: "Git is an open source, scalable, distributed revision control system. `git shell` is a restricted login shell that can be used to implement Git's push/pull functionality via SSH. In versions prior to 2.30.6, 2.31.5, 2.32.4, 2.33.5, 2.34.5, 2.35.5, 2.36.3, and 2.37.4, the function that splits the command arguments into an array improperly uses an `int` to represent the number of entries in the array, allowing a malicious actor to intentionally overflow the return value, leading to arbitrary heap writes. Because the resulting array is then passed to `execv()`, it is possible to leverage this attack to gain remote code execution on a victim machine. Note that a victim must first allow access to `git shell` as a login shell in order to be vulnerable to this attack. This problem is patched in versions 2.30.6, 2.31.5, 2.32.4, 2.33.5, 2.34.5, 2.35.5, 2.36.3, and 2.37.4 and users are advised to upgrade to the latest version. Disabling `git shell` access via remote logins is a viable short-term workaround.",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "git",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bookworm",
|
||||
Status: "resolved",
|
||||
FixedVersion: "1:2.38.1-1",
|
||||
Urgency: "not yet assigned",
|
||||
Version: "1:2.39.2-1.1",
|
||||
},
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "resolved",
|
||||
FixedVersion: "1:2.30.2-1+deb11u1",
|
||||
Urgency: "not yet assigned",
|
||||
Version: "1:2.30.2-1",
|
||||
},
|
||||
{
|
||||
ProductName: "buster",
|
||||
Status: "resolved",
|
||||
FixedVersion: "1:2.20.1-2+deb10u5",
|
||||
Urgency: "not yet assigned",
|
||||
Version: "1:2.20.1-2+deb10u3",
|
||||
},
|
||||
{
|
||||
ProductName: "sid",
|
||||
Status: "resolved",
|
||||
FixedVersion: "1:2.38.1-1",
|
||||
Urgency: "not yet assigned",
|
||||
Version: "1:2.40.0-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: models.CveContent{
|
||||
Type: models.DebianSecurityTracker,
|
||||
CveID: "CVE-2022-39260",
|
||||
Summary: "Git is an open source, scalable, distributed revision control system. `git shell` is a restricted login shell that can be used to implement Git's push/pull functionality via SSH. In versions prior to 2.30.6, 2.31.5, 2.32.4, 2.33.5, 2.34.5, 2.35.5, 2.36.3, and 2.37.4, the function that splits the command arguments into an array improperly uses an `int` to represent the number of entries in the array, allowing a malicious actor to intentionally overflow the return value, leading to arbitrary heap writes. Because the resulting array is then passed to `execv()`, it is possible to leverage this attack to gain remote code execution on a victim machine. Note that a victim must first allow access to `git shell` as a login shell in order to be vulnerable to this attack. This problem is patched in versions 2.30.6, 2.31.5, 2.32.4, 2.33.5, 2.34.5, 2.35.5, 2.36.3, and 2.37.4 and users are advised to upgrade to the latest version. Disabling `git shell` access via remote logins is a viable short-term workaround.",
|
||||
Cvss2Severity: "not yet assigned",
|
||||
Cvss3Severity: "not yet assigned",
|
||||
SourceLink: "https://security-tracker.debian.org/tracker/CVE-2022-39260",
|
||||
Optional: map[string]string{"attack range": "local"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := (Debian{}).ConvertToModel(&tt.args); !reflect.DeepEqual(got, &tt.want) {
|
||||
t.Errorf("Debian.ConvertToModel() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebian_detect(t *testing.T) {
|
||||
type args struct {
|
||||
cves map[string]gostmodels.DebianCVE
|
||||
srcPkg models.SrcPackage
|
||||
runningKernel models.Kernel
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []cveContent
|
||||
}{
|
||||
{
|
||||
name: "fixed",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.DebianCVE{
|
||||
"CVE-0000-0000": {
|
||||
CveID: "CVE-0000-0000",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "resolved",
|
||||
FixedVersion: "0.0.0-0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-0000-0001": {
|
||||
CveID: "CVE-0000-0001",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "resolved",
|
||||
FixedVersion: "0.0.0-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
srcPkg: models.SrcPackage{Name: "pkg", Version: "0.0.0-1", BinaryNames: []string{"pkg"}},
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.DebianSecurityTracker, CveID: "CVE-0000-0001", SourceLink: "https://security-tracker.debian.org/tracker/CVE-0000-0001"},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "pkg",
|
||||
FixedIn: "0.0.0-2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unfixed",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.DebianCVE{
|
||||
"CVE-0000-0000": {
|
||||
CveID: "CVE-0000-0000",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "open",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-0000-0001": {
|
||||
CveID: "CVE-0000-0001",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "undetermined",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
srcPkg: models.SrcPackage{Name: "pkg", Version: "0.0.0-1", BinaryNames: []string{"pkg"}},
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.DebianSecurityTracker, CveID: "CVE-0000-0000", SourceLink: "https://security-tracker.debian.org/tracker/CVE-0000-0000"},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "pkg",
|
||||
FixState: "open",
|
||||
NotFixedYet: true,
|
||||
}},
|
||||
},
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.DebianSecurityTracker, CveID: "CVE-0000-0001", SourceLink: "https://security-tracker.debian.org/tracker/CVE-0000-0001"},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "pkg",
|
||||
FixState: "undetermined",
|
||||
NotFixedYet: true,
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "linux-signed-amd64",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.DebianCVE{
|
||||
"CVE-0000-0000": {
|
||||
CveID: "CVE-0000-0000",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "linux",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "resolved",
|
||||
FixedVersion: "0.0.0-0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-0000-0001": {
|
||||
CveID: "CVE-0000-0001",
|
||||
Package: []gostmodels.DebianPackage{
|
||||
{
|
||||
PackageName: "linux",
|
||||
Release: []gostmodels.DebianRelease{
|
||||
{
|
||||
ProductName: "bullseye",
|
||||
Status: "resolved",
|
||||
FixedVersion: "0.0.0-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
srcPkg: models.SrcPackage{Name: "linux-signed-amd64", Version: "0.0.0+1", BinaryNames: []string{"linux-image-5.10.0-20-amd64"}},
|
||||
runningKernel: models.Kernel{Release: "5.10.0-20-amd64", Version: "0.0.0-1"},
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.DebianSecurityTracker, CveID: "CVE-0000-0001", SourceLink: "https://security-tracker.debian.org/tracker/CVE-0000-0001"},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "linux-image-5.10.0-20-amd64",
|
||||
FixedIn: "0.0.0-2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := (Debian{}).detect(tt.args.cves, tt.args.srcPkg, tt.args.runningKernel); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Debian.detect() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebian_isKernelSourcePackage(t *testing.T) {
|
||||
tests := []struct {
|
||||
pkgname string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
pkgname: "linux",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "apt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-5.10",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-grsec",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-base",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pkgname, func(t *testing.T) {
|
||||
if got := (Debian{}).isKernelSourcePackage(tt.pkgname); got != tt.want {
|
||||
t.Errorf("Debian.isKernelSourcePackage() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
270
gost/ubuntu.go
270
gost/ubuntu.go
@@ -6,9 +6,11 @@ package gost
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
debver "github.com/knqyf263/go-deb-version"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/logging"
|
||||
@@ -68,25 +70,46 @@ type cveContent struct {
|
||||
fixStatuses models.PackageFixStatuses
|
||||
}
|
||||
|
||||
var kernelSourceNamePattern = regexp.MustCompile(`^linux((-(ti-omap4|armadaxp|mako|manta|flo|goldfish|joule|raspi2?|snapdragon|aws|azure|bluefield|dell300x|gcp|gke(op)?|ibm|intel|lowlatency|kvm|oem|oracle|euclid|lts-xenial|hwe|riscv))?(-(edge|fde|iotg|hwe|osp1))?(-[\d\.]+)?)?$`)
|
||||
|
||||
// DetectCVEs fills cve information that has in Gost
|
||||
func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
|
||||
ubuReleaseVer := strings.Replace(r.Release, ".", "", 1)
|
||||
if !ubu.supported(ubuReleaseVer) {
|
||||
if !ubu.supported(strings.Replace(r.Release, ".", "", 1)) {
|
||||
logging.Log.Warnf("Ubuntu %s is not supported yet", r.Release)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if r.Container.ContainerID == "" {
|
||||
if r.RunningKernel.Release == "" {
|
||||
logging.Log.Warnf("Since the exact kernel release is not available, the vulnerability in the kernel package is not detected.")
|
||||
}
|
||||
}
|
||||
|
||||
fixedCVEs, err := ubu.detectCVEsWithFixState(r, true)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect fixed CVEs. err: %w", err)
|
||||
}
|
||||
|
||||
unfixedCVEs, err := ubu.detectCVEsWithFixState(r, false)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect unfixed CVEs. err: %w", err)
|
||||
}
|
||||
|
||||
return len(unique(append(fixedCVEs, unfixedCVEs...))), nil
|
||||
}
|
||||
|
||||
func (ubu Ubuntu) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]string, error) {
|
||||
detects := map[string]cveContent{}
|
||||
if ubu.driver == nil {
|
||||
urlPrefix, err := util.URLPathJoin(ubu.baseURL, "ubuntu", ubuReleaseVer, "pkgs")
|
||||
urlPrefix, err := util.URLPathJoin(ubu.baseURL, "ubuntu", strings.Replace(r.Release, ".", "", 1), "pkgs")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
return nil, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, "fixed-cves")
|
||||
s := "fixed-cves"
|
||||
if !fixed {
|
||||
s = "unfixed-cves"
|
||||
}
|
||||
responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, s)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get fixed CVEs via HTTP. err: %w", err)
|
||||
return nil, xerrors.Errorf("Failed to get fixed CVEs via HTTP. err: %w", err)
|
||||
}
|
||||
|
||||
for _, res := range responses {
|
||||
@@ -96,61 +119,25 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
|
||||
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(res.request.packName)
|
||||
|
||||
if kernelSourceNamePattern.MatchString(n) {
|
||||
isDetect := false
|
||||
if ubu.isKernelSourcePackage(n) {
|
||||
isRunning := false
|
||||
for _, bn := range r.SrcPackages[res.request.packName].BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isDetect = true
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDetect {
|
||||
// To detect vulnerabilities in running kernels only, skip if the kernel is not running.
|
||||
if !isRunning {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fixeds := map[string]gostmodels.UbuntuCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &fixeds); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
cs := map[string]gostmodels.UbuntuCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &cs); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
}
|
||||
for _, content := range detect(fixeds, true, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
}
|
||||
|
||||
responses, err = getCvesWithFixStateViaHTTP(r, urlPrefix, "unfixed-cves")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get unfixed CVEs via HTTP. err: %w", err)
|
||||
}
|
||||
for _, res := range responses {
|
||||
if !res.request.isSrcPack {
|
||||
continue
|
||||
}
|
||||
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(res.request.packName)
|
||||
|
||||
if kernelSourceNamePattern.MatchString(n) {
|
||||
isDetect := false
|
||||
for _, bn := range r.SrcPackages[res.request.packName].BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isDetect = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDetect {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
unfixeds := map[string]gostmodels.UbuntuCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &unfixeds); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
}
|
||||
for _, content := range detect(unfixeds, false, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
for _, content := range ubu.detect(cs, fixed, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
@@ -159,39 +146,32 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, pack := range r.SrcPackages {
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(pack.Name)
|
||||
for _, p := range r.SrcPackages {
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(p.Name)
|
||||
|
||||
if kernelSourceNamePattern.MatchString(n) {
|
||||
isDetect := false
|
||||
for _, bn := range pack.BinaryNames {
|
||||
if ubu.isKernelSourcePackage(n) {
|
||||
isRunning := false
|
||||
for _, bn := range p.BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isDetect = true
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDetect {
|
||||
// To detect vulnerabilities in running kernels only, skip if the kernel is not running.
|
||||
if !isRunning {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fixeds, err := ubu.driver.GetFixedCvesUbuntu(ubuReleaseVer, n)
|
||||
var f func(string, string) (map[string]gostmodels.UbuntuCVE, error) = ubu.driver.GetFixedCvesUbuntu
|
||||
if !fixed {
|
||||
f = ubu.driver.GetUnfixedCvesUbuntu
|
||||
}
|
||||
cs, err := f(strings.Replace(r.Release, ".", "", 1), n)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get fixed CVEs for SrcPackage. err: %w", err)
|
||||
return nil, xerrors.Errorf("Failed to get CVEs. release: %s, src package: %s, err: %w", major(r.Release), p.Name, err)
|
||||
}
|
||||
for _, content := range detect(fixeds, true, pack, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
|
||||
unfixeds, err := ubu.driver.GetUnfixedCvesUbuntu(ubuReleaseVer, n)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get unfixed CVEs for SrcPackage. err: %w", err)
|
||||
}
|
||||
for _, content := range detect(unfixeds, false, pack, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
for _, content := range ubu.detect(cs, fixed, p, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
@@ -208,8 +188,8 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
|
||||
v.CveContents = models.NewCveContents(content.cveContent)
|
||||
} else {
|
||||
v.CveContents[models.UbuntuAPI] = []models.CveContent{content.cveContent}
|
||||
v.Confidences = models.Confidences{models.UbuntuAPIMatch}
|
||||
}
|
||||
v.Confidences.AppendIfMissing(models.UbuntuAPIMatch)
|
||||
} else {
|
||||
v = models.VulnInfo{
|
||||
CveID: content.cveContent.CveID,
|
||||
@@ -224,10 +204,10 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
|
||||
r.ScannedCves[content.cveContent.CveID] = v
|
||||
}
|
||||
|
||||
return len(detects), nil
|
||||
return maps.Keys(detects), nil
|
||||
}
|
||||
|
||||
func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcPackage, runningKernelBinaryPkgName string) []cveContent {
|
||||
func (ubu Ubuntu) detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcPackage, runningKernelBinaryPkgName string) []cveContent {
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(srcPkg.Name)
|
||||
|
||||
var contents []cveContent
|
||||
@@ -243,7 +223,7 @@ func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcP
|
||||
patchedVersion := rp.Note
|
||||
|
||||
// https://git.launchpad.net/ubuntu-cve-tracker/tree/scripts/generate-oval#n384
|
||||
if kernelSourceNamePattern.MatchString(n) && strings.HasPrefix(srcPkg.Name, "linux-meta") {
|
||||
if ubu.isKernelSourcePackage(n) && strings.HasPrefix(srcPkg.Name, "linux-meta") {
|
||||
// 5.15.0.1026.30~20.04.16 -> 5.15.0.1026
|
||||
ss := strings.Split(installedVersion, ".")
|
||||
if len(ss) >= 4 {
|
||||
@@ -257,7 +237,7 @@ func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcP
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := isGostDefAffected(installedVersion, patchedVersion)
|
||||
affected, err := ubu.isGostDefAffected(installedVersion, patchedVersion)
|
||||
if err != nil {
|
||||
logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s", err, installedVersion, patchedVersion)
|
||||
continue
|
||||
@@ -265,7 +245,7 @@ func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcP
|
||||
|
||||
if affected {
|
||||
for _, bn := range srcPkg.BinaryNames {
|
||||
if kernelSourceNamePattern.MatchString(n) && bn != runningKernelBinaryPkgName {
|
||||
if ubu.isKernelSourcePackage(n) && bn != runningKernelBinaryPkgName {
|
||||
continue
|
||||
}
|
||||
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
||||
@@ -278,7 +258,7 @@ func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcP
|
||||
}
|
||||
} else {
|
||||
for _, bn := range srcPkg.BinaryNames {
|
||||
if kernelSourceNamePattern.MatchString(n) && bn != runningKernelBinaryPkgName {
|
||||
if ubu.isKernelSourcePackage(n) && bn != runningKernelBinaryPkgName {
|
||||
continue
|
||||
}
|
||||
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
||||
@@ -296,6 +276,18 @@ func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcP
|
||||
return contents
|
||||
}
|
||||
|
||||
func (ubu Ubuntu) isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) {
|
||||
vera, err := debver.NewVersion(versionRelease)
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", versionRelease, err)
|
||||
}
|
||||
verb, err := debver.NewVersion(gostVersion)
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", gostVersion, err)
|
||||
}
|
||||
return vera.LessThan(verb), nil
|
||||
}
|
||||
|
||||
// ConvertToModel converts gost model to vuls model
|
||||
func (ubu Ubuntu) ConvertToModel(cve *gostmodels.UbuntuCVE) *models.CveContent {
|
||||
references := []models.Reference{}
|
||||
@@ -323,8 +315,118 @@ func (ubu Ubuntu) ConvertToModel(cve *gostmodels.UbuntuCVE) *models.CveContent {
|
||||
Summary: cve.Description,
|
||||
Cvss2Severity: cve.Priority,
|
||||
Cvss3Severity: cve.Priority,
|
||||
SourceLink: "https://ubuntu.com/security/" + cve.Candidate,
|
||||
SourceLink: fmt.Sprintf("https://ubuntu.com/security/%s", cve.Candidate),
|
||||
References: references,
|
||||
Published: cve.PublicDate,
|
||||
}
|
||||
}
|
||||
|
||||
// https://git.launchpad.net/ubuntu-cve-tracker/tree/scripts/cve_lib.py#n931
|
||||
func (ubu Ubuntu) isKernelSourcePackage(pkgname string) bool {
|
||||
switch ss := strings.Split(pkgname, "-"); len(ss) {
|
||||
case 1:
|
||||
return pkgname == "linux"
|
||||
case 2:
|
||||
if ss[0] != "linux" {
|
||||
return false
|
||||
}
|
||||
switch ss[1] {
|
||||
case "armadaxp", "mako", "manta", "flo", "goldfish", "joule", "raspi", "raspi2", "snapdragon", "aws", "azure", "bluefield", "dell300x", "gcp", "gke", "gkeop", "ibm", "lowlatency", "kvm", "oem", "oracle", "euclid", "hwe", "riscv":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[1], 64)
|
||||
return err == nil
|
||||
}
|
||||
case 3:
|
||||
if ss[0] != "linux" {
|
||||
return false
|
||||
}
|
||||
switch ss[1] {
|
||||
case "ti":
|
||||
return ss[2] == "omap4"
|
||||
case "raspi", "raspi2", "gke", "gkeop", "ibm", "oracle", "riscv":
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
case "aws":
|
||||
switch ss[2] {
|
||||
case "hwe", "edge":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
}
|
||||
case "azure":
|
||||
switch ss[2] {
|
||||
case "fde", "edge":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
}
|
||||
case "gcp":
|
||||
switch ss[2] {
|
||||
case "edge":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
}
|
||||
case "intel":
|
||||
switch ss[2] {
|
||||
case "iotg":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
}
|
||||
case "oem":
|
||||
switch ss[2] {
|
||||
case "osp1":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
}
|
||||
case "lts":
|
||||
return ss[2] == "xenial"
|
||||
case "hwe":
|
||||
switch ss[2] {
|
||||
case "edge":
|
||||
return true
|
||||
default:
|
||||
_, err := strconv.ParseFloat(ss[2], 64)
|
||||
return err == nil
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case 4:
|
||||
if ss[0] != "linux" {
|
||||
return false
|
||||
}
|
||||
switch ss[1] {
|
||||
case "azure":
|
||||
if ss[2] != "fde" {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseFloat(ss[3], 64)
|
||||
return err == nil
|
||||
case "intel":
|
||||
if ss[2] != "iotg" {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseFloat(ss[3], 64)
|
||||
return err == nil
|
||||
case "lowlatency":
|
||||
if ss[2] != "hwe" {
|
||||
return false
|
||||
}
|
||||
_, err := strconv.ParseFloat(ss[3], 64)
|
||||
return err == nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,68 +10,51 @@ import (
|
||||
)
|
||||
|
||||
func TestUbuntu_Supported(t *testing.T) {
|
||||
type args struct {
|
||||
ubuReleaseVer string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
args string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "14.04 is supported",
|
||||
args: args{
|
||||
ubuReleaseVer: "1404",
|
||||
},
|
||||
args: "1404",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "16.04 is supported",
|
||||
args: args{
|
||||
ubuReleaseVer: "1604",
|
||||
},
|
||||
args: "1604",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "18.04 is supported",
|
||||
args: args{
|
||||
ubuReleaseVer: "1804",
|
||||
},
|
||||
args: "1804",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "20.04 is supported",
|
||||
args: args{
|
||||
ubuReleaseVer: "2004",
|
||||
},
|
||||
args: "2004",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "20.10 is supported",
|
||||
args: args{
|
||||
ubuReleaseVer: "2010",
|
||||
},
|
||||
args: "2010",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "21.04 is supported",
|
||||
args: args{
|
||||
ubuReleaseVer: "2104",
|
||||
},
|
||||
args: "2104",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty string is not supported yet",
|
||||
args: args{
|
||||
ubuReleaseVer: "",
|
||||
},
|
||||
args: "",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ubu := Ubuntu{}
|
||||
if got := ubu.supported(tt.args.ubuReleaseVer); got != tt.want {
|
||||
if got := ubu.supported(tt.args); got != tt.want {
|
||||
t.Errorf("Ubuntu.Supported() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
@@ -289,9 +272,60 @@ func Test_detect(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := detect(tt.args.cves, tt.args.fixed, tt.args.srcPkg, tt.args.runningKernelBinaryPkgName); !reflect.DeepEqual(got, tt.want) {
|
||||
if got := (Ubuntu{}).detect(tt.args.cves, tt.args.fixed, tt.args.srcPkg, tt.args.runningKernelBinaryPkgName); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("detect() = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUbuntu_isKernelSourcePackage(t *testing.T) {
|
||||
tests := []struct {
|
||||
pkgname string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
pkgname: "linux",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "apt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-aws",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-5.9",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-base",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
pkgname: "apt-utils",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-aws-edge",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-aws-5.15",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pkgname: "linux-lowlatency-hwe-5.15",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pkgname, func(t *testing.T) {
|
||||
if got := (Ubuntu{}).isKernelSourcePackage(tt.pkgname); got != tt.want {
|
||||
t.Errorf("Ubuntu.isKernelSourcePackage() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
21
gost/util.go
21
gost/util.go
@@ -80,10 +80,9 @@ func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
|
||||
}
|
||||
|
||||
type request struct {
|
||||
osMajorVersion string
|
||||
packName string
|
||||
isSrcPack bool
|
||||
cveID string
|
||||
packName string
|
||||
isSrcPack bool
|
||||
cveID string
|
||||
}
|
||||
|
||||
func getCvesWithFixStateViaHTTP(r *models.ScanResult, urlPrefix, fixState string) (responses []response, err error) {
|
||||
@@ -98,16 +97,14 @@ func getCvesWithFixStateViaHTTP(r *models.ScanResult, urlPrefix, fixState string
|
||||
go func() {
|
||||
for _, pack := range r.Packages {
|
||||
reqChan <- request{
|
||||
osMajorVersion: major(r.Release),
|
||||
packName: pack.Name,
|
||||
isSrcPack: false,
|
||||
packName: pack.Name,
|
||||
isSrcPack: false,
|
||||
}
|
||||
}
|
||||
for _, pack := range r.SrcPackages {
|
||||
reqChan <- request{
|
||||
osMajorVersion: major(r.Release),
|
||||
packName: pack.Name,
|
||||
isSrcPack: true,
|
||||
packName: pack.Name,
|
||||
isSrcPack: true,
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -142,11 +139,11 @@ func getCvesWithFixStateViaHTTP(r *models.ScanResult, urlPrefix, fixState string
|
||||
case err := <-errChan:
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
return nil, xerrors.New("Timeout Fetching OVAL")
|
||||
return nil, xerrors.New("Timeout Fetching Gost")
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs)
|
||||
return nil, xerrors.Errorf("Failed to fetch Gost. err: %w", errs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Submodule integration updated: fbd9e815f6...36de7ed9e8
163
oval/debian.go
163
oval/debian.go
@@ -4,13 +4,9 @@
|
||||
package oval
|
||||
|
||||
import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
ovaldb "github.com/vulsio/goval-dictionary/db"
|
||||
ovalmodels "github.com/vulsio/goval-dictionary/models"
|
||||
)
|
||||
|
||||
// DebianBase is the base struct of Debian and Ubuntu
|
||||
@@ -18,102 +14,6 @@ type DebianBase struct {
|
||||
Base
|
||||
}
|
||||
|
||||
func (o DebianBase) update(r *models.ScanResult, defpacks defPacks) {
|
||||
for _, cve := range defpacks.def.Advisory.Cves {
|
||||
ovalContent := o.convertToModel(cve.CveID, &defpacks.def)
|
||||
if ovalContent == nil {
|
||||
continue
|
||||
}
|
||||
vinfo, ok := r.ScannedCves[cve.CveID]
|
||||
if !ok {
|
||||
logging.Log.Debugf("%s is newly detected by OVAL", cve.CveID)
|
||||
vinfo = models.VulnInfo{
|
||||
CveID: cve.CveID,
|
||||
Confidences: []models.Confidence{models.OvalMatch},
|
||||
CveContents: models.NewCveContents(*ovalContent),
|
||||
}
|
||||
} else {
|
||||
cveContents := vinfo.CveContents
|
||||
if _, ok := vinfo.CveContents[ovalContent.Type]; ok {
|
||||
logging.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
|
||||
} else {
|
||||
logging.Log.Debugf("%s is also detected by OVAL", cve.CveID)
|
||||
cveContents = models.CveContents{}
|
||||
}
|
||||
vinfo.Confidences.AppendIfMissing(models.OvalMatch)
|
||||
cveContents[ovalContent.Type] = []models.CveContent{*ovalContent}
|
||||
vinfo.CveContents = cveContents
|
||||
}
|
||||
|
||||
// uniq(vinfo.AffectedPackages[].Name + defPacks.binpkgFixstat(map[string(=package name)]fixStat{}))
|
||||
collectBinpkgFixstat := defPacks{
|
||||
binpkgFixstat: map[string]fixStat{},
|
||||
}
|
||||
for packName, fixStatus := range defpacks.binpkgFixstat {
|
||||
collectBinpkgFixstat.binpkgFixstat[packName] = fixStatus
|
||||
}
|
||||
|
||||
for _, pack := range vinfo.AffectedPackages {
|
||||
collectBinpkgFixstat.binpkgFixstat[pack.Name] = fixStat{
|
||||
notFixedYet: pack.NotFixedYet,
|
||||
fixedIn: pack.FixedIn,
|
||||
isSrcPack: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Update package status of source packages.
|
||||
// In the case of Debian based Linux, sometimes source package name is defined as affected package in OVAL.
|
||||
// To display binary package name showed in apt-get, need to convert source name to binary name.
|
||||
for binName := range defpacks.binpkgFixstat {
|
||||
if srcPack, ok := r.SrcPackages.FindByBinName(binName); ok {
|
||||
for _, p := range defpacks.def.AffectedPacks {
|
||||
if p.Name == srcPack.Name {
|
||||
collectBinpkgFixstat.binpkgFixstat[binName] = fixStat{
|
||||
notFixedYet: p.NotFixedYet,
|
||||
fixedIn: p.Version,
|
||||
isSrcPack: true,
|
||||
srcPackName: srcPack.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vinfo.AffectedPackages = collectBinpkgFixstat.toPackStatuses()
|
||||
vinfo.AffectedPackages.Sort()
|
||||
r.ScannedCves[cve.CveID] = vinfo
|
||||
}
|
||||
}
|
||||
|
||||
func (o DebianBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent {
|
||||
refs := make([]models.Reference, 0, len(def.References))
|
||||
for _, r := range def.References {
|
||||
refs = append(refs, models.Reference{
|
||||
Link: r.RefURL,
|
||||
Source: r.Source,
|
||||
RefID: r.RefID,
|
||||
})
|
||||
}
|
||||
|
||||
for _, cve := range def.Advisory.Cves {
|
||||
if cve.CveID != cveID {
|
||||
continue
|
||||
}
|
||||
|
||||
return &models.CveContent{
|
||||
Type: models.NewCveContentType(o.family),
|
||||
CveID: cve.CveID,
|
||||
Title: def.Title,
|
||||
Summary: def.Description,
|
||||
Cvss2Severity: def.Advisory.Severity,
|
||||
Cvss3Severity: def.Advisory.Severity,
|
||||
References: refs,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Debian is the interface for Debian OVAL
|
||||
type Debian struct {
|
||||
DebianBase
|
||||
@@ -133,67 +33,8 @@ func NewDebian(driver ovaldb.DB, baseURL string) Debian {
|
||||
}
|
||||
|
||||
// FillWithOval returns scan result after updating CVE info by OVAL
|
||||
func (o Debian) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
|
||||
|
||||
//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
|
||||
linuxImage := "linux-image-" + r.RunningKernel.Release
|
||||
|
||||
// Add linux and set the version of running kernel to search OVAL.
|
||||
if r.Container.ContainerID == "" {
|
||||
if r.RunningKernel.Version != "" {
|
||||
newVer := ""
|
||||
if p, ok := r.Packages[linuxImage]; ok {
|
||||
newVer = p.NewVersion
|
||||
}
|
||||
r.Packages["linux"] = models.Package{
|
||||
Name: "linux",
|
||||
Version: r.RunningKernel.Version,
|
||||
NewVersion: newVer,
|
||||
}
|
||||
} else {
|
||||
logging.Log.Warnf("Since the exact kernel version is not available, the vulnerability in the linux package is not detected.")
|
||||
}
|
||||
}
|
||||
|
||||
var relatedDefs ovalResult
|
||||
if o.driver == nil {
|
||||
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.baseURL); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get Definitions via HTTP. err: %w", err)
|
||||
}
|
||||
} else {
|
||||
if relatedDefs, err = getDefsByPackNameFromOvalDB(r, o.driver); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get Definitions from DB. err: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
delete(r.Packages, "linux")
|
||||
|
||||
for _, defPacks := range relatedDefs.entries {
|
||||
// Remove "linux" added above for oval search
|
||||
// linux is not a real package name (key of affected packages in OVAL)
|
||||
if notFixedYet, ok := defPacks.binpkgFixstat["linux"]; ok {
|
||||
defPacks.binpkgFixstat[linuxImage] = notFixedYet
|
||||
delete(defPacks.binpkgFixstat, "linux")
|
||||
for i, p := range defPacks.def.AffectedPacks {
|
||||
if p.Name == "linux" {
|
||||
p.Name = linuxImage
|
||||
defPacks.def.AffectedPacks[i] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o.update(r, defPacks)
|
||||
}
|
||||
|
||||
for _, vuln := range r.ScannedCves {
|
||||
if conts, ok := vuln.CveContents[models.Debian]; ok {
|
||||
for i, cont := range conts {
|
||||
cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
|
||||
vuln.CveContents[models.Debian][i] = cont
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(relatedDefs.entries), nil
|
||||
func (o Debian) FillWithOval(_ *models.ScanResult) (nCVEs int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Ubuntu is the interface for Debian OVAL
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
//go:build !scanner
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
ovalmodels "github.com/vulsio/goval-dictionary/models"
|
||||
)
|
||||
|
||||
func TestPackNamesOfUpdateDebian(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in models.ScanResult
|
||||
defPacks defPacks
|
||||
out models.ScanResult
|
||||
}{
|
||||
{
|
||||
in: models.ScanResult{
|
||||
ScannedCves: models.VulnInfos{
|
||||
"CVE-2000-1000": models.VulnInfo{
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{Name: "packA"},
|
||||
{Name: "packC"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defPacks: defPacks{
|
||||
def: ovalmodels.Definition{
|
||||
Advisory: ovalmodels.Advisory{
|
||||
Cves: []ovalmodels.Cve{{CveID: "CVE-2000-1000"}},
|
||||
},
|
||||
},
|
||||
binpkgFixstat: map[string]fixStat{
|
||||
"packB": {
|
||||
notFixedYet: true,
|
||||
fixedIn: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: models.ScanResult{
|
||||
ScannedCves: models.VulnInfos{
|
||||
"CVE-2000-1000": models.VulnInfo{
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{Name: "packA"},
|
||||
{Name: "packB", NotFixedYet: true, FixedIn: "1.0.0"},
|
||||
{Name: "packC"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: models.ScanResult{
|
||||
ScannedCves: models.VulnInfos{
|
||||
"CVE-2000-1000": models.VulnInfo{
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{Name: "packA"},
|
||||
},
|
||||
},
|
||||
"CVE-2000-1001": models.VulnInfo{
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{Name: "packC"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defPacks: defPacks{
|
||||
def: ovalmodels.Definition{
|
||||
Advisory: ovalmodels.Advisory{
|
||||
Cves: []ovalmodels.Cve{
|
||||
{
|
||||
CveID: "CVE-2000-1000",
|
||||
},
|
||||
{
|
||||
CveID: "CVE-2000-1001",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
binpkgFixstat: map[string]fixStat{
|
||||
"packB": {
|
||||
notFixedYet: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
out: models.ScanResult{
|
||||
ScannedCves: models.VulnInfos{
|
||||
"CVE-2000-1000": models.VulnInfo{
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{Name: "packA"},
|
||||
{Name: "packB", NotFixedYet: false},
|
||||
},
|
||||
},
|
||||
"CVE-2000-1001": models.VulnInfo{
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{Name: "packB", NotFixedYet: false},
|
||||
{Name: "packC"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// util.Log = util.NewCustomLogger()
|
||||
for i, tt := range tests {
|
||||
Debian{}.update(&tt.in, tt.defPacks)
|
||||
for cveid := range tt.out.ScannedCves {
|
||||
e := tt.out.ScannedCves[cveid].AffectedPackages
|
||||
a := tt.in.ScannedCves[cveid].AffectedPackages
|
||||
if !reflect.DeepEqual(a, e) {
|
||||
t.Errorf("[%d] expected: %v\n actual: %v\n", i, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func (w GoogleChatWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
re := regexp.MustCompile(w.Cnf.ServerNameRegexp)
|
||||
|
||||
for _, r := range rs {
|
||||
if re.Match([]byte(r.FormatServerName())) {
|
||||
if re.MatchString(r.FormatServerName()) {
|
||||
continue
|
||||
}
|
||||
msgs := []string{fmt.Sprintf("*%s*\n%s\t%s\t%s",
|
||||
@@ -73,11 +73,10 @@ func (w GoogleChatWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
}
|
||||
|
||||
func (w GoogleChatWriter) postMessage(message string) error {
|
||||
uri := fmt.Sprintf("%s", w.Cnf.WebHookURL)
|
||||
payload := `{"text": "` + message + `" }`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, bytes.NewBuffer([]byte(payload)))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, w.Cnf.WebHookURL, bytes.NewBuffer([]byte(payload)))
|
||||
defer cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -88,7 +87,7 @@ func (w GoogleChatWriter) postMessage(message string) error {
|
||||
return err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if checkResponse(resp) != nil && err != nil {
|
||||
if w.checkResponse(resp) != nil && err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
@@ -75,14 +75,14 @@ func (w TelegramWriter) sendMessage(chatID, token, message string) error {
|
||||
return err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if checkResponse(resp) != nil && err != nil {
|
||||
if w.checkResponse(resp) != nil && err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(r *http.Response) error {
|
||||
func (w TelegramWriter) checkResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
@@ -50,12 +54,26 @@ func (o *amazon) depsFast() []string {
|
||||
return []string{}
|
||||
}
|
||||
// repoquery
|
||||
return []string{"yum-utils"}
|
||||
switch s := strings.Fields(o.getDistro().Release)[0]; s {
|
||||
case "1", "2":
|
||||
return []string{"yum-utils"}
|
||||
default:
|
||||
if _, err := time.Parse("2006.01", s); err == nil {
|
||||
return []string{"yum-utils"}
|
||||
}
|
||||
return []string{"dnf-utils"}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *amazon) depsFastRoot() []string {
|
||||
return []string{
|
||||
"yum-utils",
|
||||
switch s := strings.Fields(o.getDistro().Release)[0]; s {
|
||||
case "1", "2":
|
||||
return []string{"yum-utils"}
|
||||
default:
|
||||
if _, err := time.Parse("2006.01", s); err == nil {
|
||||
return []string{"yum-utils"}
|
||||
}
|
||||
return []string{"dnf-utils"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ func (o *debian) rebootRequired() (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
const dpkgQuery = `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Source},\${source:Version}\n"`
|
||||
const dpkgQuery = `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${source:Package},\${source:Version}\n"`
|
||||
|
||||
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, models.SrcPackages, error) {
|
||||
updatable := models.Packages{}
|
||||
@@ -417,29 +417,19 @@ func (o *debian) parseInstalledPackages(stdout string) (models.Packages, models.
|
||||
Version: version,
|
||||
}
|
||||
|
||||
if srcName != "" && srcName != name {
|
||||
if pack, ok := srcPacks[srcName]; ok {
|
||||
pack.AddBinaryName(name)
|
||||
srcPacks[srcName] = pack
|
||||
} else {
|
||||
srcPacks[srcName] = models.SrcPackage{
|
||||
Name: srcName,
|
||||
Version: srcVersion,
|
||||
BinaryNames: []string{name},
|
||||
}
|
||||
if pack, ok := srcPacks[srcName]; ok {
|
||||
pack.AddBinaryName(name)
|
||||
srcPacks[srcName] = pack
|
||||
} else {
|
||||
srcPacks[srcName] = models.SrcPackage{
|
||||
Name: srcName,
|
||||
Version: srcVersion,
|
||||
BinaryNames: []string{name},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove "linux"
|
||||
// kernel-related packages are showed "linux" as source package name
|
||||
// If "linux" is left, oval detection will cause trouble, so delete.
|
||||
delete(srcPacks, "linux")
|
||||
// Remove duplicate
|
||||
for name := range installed {
|
||||
delete(srcPacks, name)
|
||||
}
|
||||
return installed, srcPacks, nil
|
||||
}
|
||||
|
||||
@@ -454,8 +444,20 @@ func (o *debian) parseScannedPackagesLine(line string) (name, status, version, s
|
||||
status = strings.TrimSpace(ss[1])
|
||||
version = ss[2]
|
||||
// remove version. ex: tar (1.27.1-2)
|
||||
|
||||
// Source name and version are computed from binary package name and version in dpkg.
|
||||
// Source package name:
|
||||
// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n338
|
||||
srcName = strings.Split(ss[3], " ")[0]
|
||||
if srcName == "" {
|
||||
srcName = name
|
||||
}
|
||||
// Source package version:
|
||||
// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-show.c#n428
|
||||
srcVersion = ss[4]
|
||||
if srcVersion == "" {
|
||||
srcVersion = version
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -304,10 +304,6 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
// Amazon Linux AMI release 2017.09
|
||||
// Amazon Linux AMI release 2018.03
|
||||
release = "1"
|
||||
case strings.HasPrefix(r.Stdout, "Amazon Linux 2"), strings.HasPrefix(r.Stdout, "Amazon Linux release 2"):
|
||||
// Amazon Linux 2 (Karoo)
|
||||
// Amazon Linux release 2 (Karoo)
|
||||
release = "2"
|
||||
case strings.HasPrefix(r.Stdout, "Amazon Linux 2022"), strings.HasPrefix(r.Stdout, "Amazon Linux release 2022"):
|
||||
// Amazon Linux 2022 (Amazon Linux)
|
||||
// Amazon Linux release 2022 (Amazon Linux)
|
||||
@@ -316,6 +312,10 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
// Amazon Linux 2023 (Amazon Linux)
|
||||
// Amazon Linux release 2023 (Amazon Linux)
|
||||
release = "2023"
|
||||
case strings.HasPrefix(r.Stdout, "Amazon Linux 2"), strings.HasPrefix(r.Stdout, "Amazon Linux release 2"):
|
||||
// Amazon Linux 2 (Karoo)
|
||||
// Amazon Linux release 2 (Karoo)
|
||||
release = "2"
|
||||
default:
|
||||
fields := strings.Fields(r.Stdout)
|
||||
if len(fields) == 5 {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
debver "github.com/knqyf263/go-deb-version"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -230,16 +229,6 @@ func ViaHTTP(header http.Header, body string, toLocalFile bool) (models.ScanResu
|
||||
}
|
||||
|
||||
kernelVersion := header.Get("X-Vuls-Kernel-Version")
|
||||
if family == constant.Debian {
|
||||
if kernelVersion == "" {
|
||||
logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
|
||||
} else {
|
||||
if _, err := debver.NewVersion(kernelVersion); err != nil {
|
||||
logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
|
||||
kernelVersion = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distro := config.Distro{
|
||||
Family: family,
|
||||
|
||||
@@ -552,7 +552,6 @@ func parseRegistry(stdout, arch string) (osInfo, error) {
|
||||
}
|
||||
|
||||
func detectOSName(osInfo osInfo) (string, error) {
|
||||
|
||||
osName, err := detectOSNameFromOSInfo(osInfo)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("Failed to detect OS Name from OSInfo: %+v, err: %w", osInfo, err)
|
||||
|
||||
Reference in New Issue
Block a user