Skip to content

Commit

Permalink
feat!(detector): use vuls2 for redhat/alma/rocky
Browse files Browse the repository at this point in the history
Co-authored-by: MaineK00n <[email protected]>
  • Loading branch information
shino and MaineK00n committed Dec 8, 2024
1 parent 5502e1c commit 05e7188
Show file tree
Hide file tree
Showing 10 changed files with 997 additions and 22 deletions.
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Config struct {
Metasploit MetasploitConf `json:"metasploit,omitempty"`
KEVuln KEVulnConf `json:"kevuln,omitempty"`
Cti CtiConf `json:"cti,omitempty"`
Vuls2 Vuls2DictConf `json:"vuls2Dict,omitempty"`

Slack SlackConf `json:"-"`
EMail SMTPConf `json:"-"`
Expand Down
6 changes: 6 additions & 0 deletions config/vulnDictConf.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,9 @@ func (cnf *CtiConf) Init() {
cnf.setDefault("go-cti.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}

type Vuls2DictConf struct {
Repository string
Path string
SkipUpdate bool
}
13 changes: 11 additions & 2 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
"github.com/future-architect/vuls/cwe"
"github.com/future-architect/vuls/detector/vuls2"
"github.com/future-architect/vuls/gost"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
Expand Down Expand Up @@ -49,7 +50,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
}

if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.LogOpts); err != nil {
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}

Expand Down Expand Up @@ -317,14 +318,19 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {

// DetectPkgCves detects OS pkg cves
// pass 2 configs
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, logOpts logging.LogOpts) error {
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, vuls2Cnf config.Vuls2DictConf, logOpts logging.LogOpts, noProgress bool) error {
// Pkg Scan
if isPkgCvesDetactable(r) {
// OVAL, gost(Debian Security Tracker) does not support Package for Raspbian, so skip it.
if r.Family == constant.Raspbian {
r = r.RemoveRaspbianPackFromResult()
}

// Vuls2
if err := vuls2.Detect(r, vuls2Cnf, noProgress); err != nil {
return xerrors.Errorf("Failed to detect CVE with Vuls2: %w", err)
}

// OVAL
if err := detectPkgsCvesWithOval(ovalCnf, r, logOpts); err != nil {
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
Expand Down Expand Up @@ -537,6 +543,9 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logO
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.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
logging.Log.Debugf("Skip OVAL and Scan by Vuls2")
return nil
case constant.Windows, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.FreeBSD, constant.ServerTypePseudo:
return nil
default:
Expand Down
195 changes: 195 additions & 0 deletions detector/vuls2/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package vuls2

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"

"github.com/future-architect/vuls/logging"
"github.com/klauspost/compress/zstd"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
progressbar "github.com/schollz/progressbar/v3"
"golang.org/x/xerrors"
oras "oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras-go/v2/registry/remote"

db "github.com/MaineK00n/vuls2/pkg/db/common"
)

const (
SchemaVersion = 0
dbMediaType = "application/vnd.vulsio.vuls.db.layer.v1+zstd"
)

var (
// DefaultGHCRRepository is GitHub Container Registry for vuls2 db for vuls0
DefaultGHCRRepository = fmt.Sprintf("%s:%d", "ghcr.io/vulsio/vuls-nightly-db", SchemaVersion)

// DefaultPath is the path for vuls2 db file
DefaultPath = func() string {
wd, _ := os.Getwd()
return filepath.Join(wd, "vuls.db")
}()
)

type Config struct {
Repository string
Path string
SkipUpdate bool
Quiet bool
}

func (c Config) Refresh() error {

lastModified, fileExists, err := c.loadLastModified()
if err != nil {
return xerrors.Errorf("Failed to load vuls2 db metadata. err: %w", err)
}

if fileExists && time.Now().Before(lastModified.Add(6*time.Hour)) {
return nil
}

if c.SkipUpdate {
if !fileExists {
return xerrors.New("Vuls2 db not found, cannot skip update")
}
return nil
}

logging.Log.Infof("Downloading vuls2 db. repository: %s", c.Repository)
if err := c.fetch(c.Repository); err != nil {
return xerrors.Errorf("Failed to fetch vuls2 db. repository: %s, err: %w", c.Repository, err)
}

return nil
}

func (c Config) New() (db.DB, error) {
vuls2Config := db.Config{
Type: "boltdb",
Path: c.Path,
}

dbc, err := vuls2Config.New()
if err != nil {
return nil, xerrors.Errorf("Failed to new vuls2 db. err: %w", err)
}

return dbc, nil
}

func (c Config) fetch(repoPath string) error {
logging.Log.Infof("Fetch vuls.db from %s", repoPath)

ctx := context.TODO()

ms := memory.New()

repo, err := remote.NewRepository(repoPath)
if err != nil {
return xerrors.Errorf("Failed to create repository client. repository: %s, err: %w", repoPath, err)
}

manifestDescriptor, err := oras.Copy(ctx, repo, "latest", ms, "latest", oras.DefaultCopyOptions)
if err != nil {
return xerrors.Errorf("Failed to copy. repository: %s, err: %w", repoPath, err)
}

r, err := ms.Fetch(ctx, manifestDescriptor)
if err != nil {
return xerrors.Errorf("Failed to fetch manifest. err: %w", err)
}
defer r.Close()

var manifest ocispec.Manifest
if err := json.NewDecoder(content.NewVerifyReader(r, manifestDescriptor)).Decode(&manifest); err != nil {
return xerrors.Errorf("Failed to decode manifest. err: %w", err)
}

l := func() *ocispec.Descriptor {
for _, l := range manifest.Layers {
if l.MediaType == dbMediaType {
return &l
}
}
return nil
}()
if l == nil {
return xerrors.Errorf("Failed to find digest and filename from layers. actual layers: %#v", manifest.Layers)
}

r, err = repo.Fetch(ctx, *l)
if err != nil {
return xerrors.Errorf("Failed to fetch content. err: %w", err)
}
defer r.Close()

d, err := zstd.NewReader(content.NewVerifyReader(r, *l))
if err != nil {
return errors.Wrap(err, "new zstd reader")
}
defer d.Close()

if err := os.MkdirAll(filepath.Dir(c.Path), 0755); err != nil {
return errors.Wrapf(err, "mkdir %s", filepath.Dir(c.Path))
}

f, err := os.Create(c.Path)
if err != nil {
return errors.Wrapf(err, "create %s", c.Path)
}
defer f.Close()

var pb *progressbar.ProgressBar
pb = progressbar.DefaultBytesSilent(-1)
if !c.Quiet {
pb = progressbar.DefaultBytes(-1, "downloading")
}
if _, err := d.WriteTo(io.MultiWriter(f, pb)); err != nil {
return xerrors.Errorf("Failed to write. filename: %s. err: %w", f.Name(), err)
}
_ = pb.Finish()

return nil
}

func (c Config) loadLastModified() (time.Time, bool, error) {
if _, err := os.Stat(c.Path); errors.Is(err, os.ErrNotExist) {
return time.Time{}, false, nil
}

conf := db.Config{
Type: "boltdb",
Path: c.Path,
}

dbc, err := conf.New()
if err != nil {
return time.Time{}, false, xerrors.Errorf("Failed to new vuls2 db. path: %s, err: %w", c.Path, err)
}

if err := dbc.Open(); err != nil {
return time.Time{}, false, xerrors.Errorf("Failed to open vuls2 db. path: %s, err: %w", c.Path, err)
}
defer func() {
_ = dbc.Close()
}()

metadata, err := dbc.GetMetadata()
if err != nil {
return time.Time{}, false, xerrors.Errorf("Failed to get vuls2 db metadata. path: %s, err: %w", c.Path, err)
}
if metadata == nil {
return time.Time{}, false, xerrors.Errorf("vuls2 db metadata is nil. path: %s", c.Path)
}

return metadata.LastModified, true, nil
}
124 changes: 124 additions & 0 deletions detector/vuls2/vendor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package vuls2

import (
"fmt"
"slices"
"strings"

dataTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data"
"github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion"
"github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion/versioncriterion/fixstatus"
referenceTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/reference"
vulnerabilityTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/vulnerability"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/models"
rpm "github.com/knqyf263/go-rpm-version"
)

func resolveCveContentList(family string, rootIDBase, rootID1 dataTypes.RootID, ccListBase, ccList1 []models.CveContent) []models.CveContent {
switch family {
// This logic is from old vuls's oval resolution. Maybe more widely applicable than these four distros.
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
if rootIDBase < rootID1 {
return ccList1
}
return ccListBase
default:
return ccListBase
}
}

func resolveAffectedPackage(family string, rootIDBase, rootID1 dataTypes.RootID, statusBase, status1 models.PackageFixStatus) models.PackageFixStatus {
switch family {
// This logic is from old vuls's oval resolution. Maybe more widely applicable than these four distros.
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
former, latter := func() (models.PackageFixStatus, models.PackageFixStatus) {
if rootIDBase < rootID1 {
return statusBase, status1
}
return status1, statusBase
}()

if latter.NotFixedYet {
former.NotFixedYet = true
return former
}
return latter
default:
return statusBase
}
}

func ignoresCriterion(family string, cn criterion.FilteredCriterion) bool {
switch family {
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
if cn.Criterion.Version.FixStatus != nil && cn.Criterion.Version.FixStatus.Class == fixstatus.ClassUnfixed {
switch cn.Criterion.Version.FixStatus.Vendor {
case "Will not fix", "Under investigation":
return true
}
}
return false
default:
return false
}
}

func ignoresWholeCriteria(family string, cn criterion.FilteredCriterion) bool {
switch family {
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
// Ignore whole criteria from root if kpatch-patch-* package is included.
return strings.HasPrefix(cn.Criterion.Version.Package.Name, "kpatch-patch-")
default:
return false
}
}

func selectFixedIn(family string, fixed []string) string {
if len(fixed) == 0 {
return ""
}

switch family {
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
return slices.MaxFunc(fixed, func(x, y string) int {
return rpm.NewVersion(x).Compare(rpm.NewVersion(y))
})
default:
return fixed[0]
}
}

func advisoryReferenceSource(family string, r referenceTypes.Reference) string {
switch family {
case constant.RedHat:
return "RHSA"
default:
return r.Source
}
}

func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vulnerability) string {
switch ccType {
case models.RedHat:
return fmt.Sprintf("https://access.redhat.com/security/cve/%s", v.Content.ID)
default:
return ""
}
}

func discardsNewVulnInfoBySourceID(family, baseSourceID, newSourceID string) bool {
switch family {
case constant.RedHat, constant.CentOS:
switch newSourceID {
case "redhat-csaf":
return false
case "redhat-ovalv2":
return baseSourceID == "redhat-ovalv1"
default:
return true
}
default:
return true
}
}
Loading

0 comments on commit 05e7188

Please sign in to comment.