Skip to content

Commit bff6b75

Browse files
authored
refactor(scanner/redhatbase): strictly parse updatable package line (#2218)
1 parent 6fca8d9 commit bff6b75

File tree

2 files changed

+110
-61
lines changed

2 files changed

+110
-61
lines changed

scanner/redhatbase.go

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -768,21 +768,21 @@ func (o *redhatBase) yumMakeCache() error {
768768
}
769769

770770
func (o *redhatBase) scanUpdatablePackages() (models.Packages, error) {
771-
cmd := `repoquery --all --pkgnarrow=updates --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPO}'`
771+
cmd := `repoquery --all --pkgnarrow=updates --qf='"%{NAME}" "%{EPOCH}" "%{VERSION}" "%{RELEASE}" "%{REPO}"'`
772772
switch o.getDistro().Family {
773773
case constant.Fedora:
774774
v, _ := o.getDistro().MajorVersion()
775775
switch {
776776
case v < 41:
777777
if o.exec(util.PrependProxyEnv(`repoquery --version | grep dnf`), o.sudo.repoquery()).isSuccess() {
778-
cmd = `repoquery --upgrades --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPONAME}' -q`
778+
cmd = `repoquery --upgrades --qf='"%{NAME}" "%{EPOCH}" "%{VERSION}" "%{RELEASE}" "%{REPONAME}"' -q`
779779
}
780780
default:
781-
cmd = `repoquery --upgrades --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPONAME}' -q`
781+
cmd = `repoquery --upgrades --qf='"%{NAME}" "%{EPOCH}" "%{VERSION}" "%{RELEASE}" "%{REPONAME}"' -q`
782782
}
783783
default:
784784
if o.exec(util.PrependProxyEnv(`repoquery --version | grep dnf`), o.sudo.repoquery()).isSuccess() {
785-
cmd = `repoquery --upgrades --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPONAME}' -q`
785+
cmd = `repoquery --upgrades --qf='"%{NAME}" "%{EPOCH}" "%{VERSION}" "%{RELEASE}" "%{REPONAME}"' -q`
786786
}
787787
}
788788
for _, repo := range o.getServerInfo().Enablerepo {
@@ -801,11 +801,8 @@ func (o *redhatBase) scanUpdatablePackages() (models.Packages, error) {
801801
// parseUpdatablePacksLines parse the stdout of repoquery to get package name, candidate version
802802
func (o *redhatBase) parseUpdatablePacksLines(stdout string) (models.Packages, error) {
803803
updatable := models.Packages{}
804-
lines := strings.Split(stdout, "\n")
805-
for _, line := range lines {
806-
if len(strings.TrimSpace(line)) == 0 {
807-
continue
808-
} else if strings.HasPrefix(line, "Loading") {
804+
for line := range strings.SplitSeq(stdout, "\n") {
805+
if len(strings.TrimSpace(line)) == 0 || strings.HasPrefix(line, "Loading") {
809806
continue
810807
}
811808
pack, err := o.parseUpdatablePacksLine(line)
@@ -818,28 +815,30 @@ func (o *redhatBase) parseUpdatablePacksLines(stdout string) (models.Packages, e
818815
}
819816

820817
func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error) {
821-
fields := strings.Split(line, " ")
822-
if len(fields) < 5 {
823-
return models.Package{}, xerrors.Errorf("Unknown format: %s, fields: %s", line, fields)
824-
}
825-
826-
ver := ""
827-
epoch := fields[1]
828-
if epoch == "0" {
829-
ver = fields[2]
830-
} else {
831-
ver = fmt.Sprintf("%s:%s", epoch, fields[2])
818+
_, rhs, ok := strings.Cut(line, "[y/N]: ")
819+
if ok {
820+
line = rhs
832821
}
833822

834-
repos := strings.Join(fields[4:], " ")
835-
836-
p := models.Package{
837-
Name: fields[0],
838-
NewVersion: ver,
839-
NewRelease: fields[3],
840-
Repository: repos,
823+
switch fields := strings.Split(line, "\" \""); len(fields) {
824+
case 5:
825+
if !strings.HasPrefix(fields[0], "\"") {
826+
return models.Package{}, xerrors.Errorf("unexpected format. expected: %q, actual: %q", "\"<name>\" \"<epoch>\" \"<version>\" \"<release>\" \"<repository>\"", line)
827+
}
828+
return models.Package{
829+
Name: strings.TrimPrefix(fields[0], "\""),
830+
NewVersion: func() string {
831+
if fields[1] == "0" {
832+
return fields[2]
833+
}
834+
return fmt.Sprintf("%s:%s", fields[1], fields[2])
835+
}(),
836+
NewRelease: fields[3],
837+
Repository: strings.TrimSuffix(fields[4], "\""),
838+
}, nil
839+
default:
840+
return models.Package{}, xerrors.Errorf("unexpected format. expected: %q, actual: %q", "\"<name>\" \"<epoch>\" \"<version>\" \"<release>\" \"<repository>\"", line)
841841
}
842-
return p, nil
843842
}
844843

845844
func (o *redhatBase) isExecYumPS() bool {

scanner/redhatbase_test.go

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"reflect"
55
"testing"
66

7-
"github.com/k0kubun/pp"
8-
97
"github.com/future-architect/vuls/config"
108
"github.com/future-architect/vuls/constant"
119
"github.com/future-architect/vuls/logging"
@@ -596,44 +594,97 @@ func Test_redhatBase_parseInstalledPackagesLineFromRepoquery(t *testing.T) {
596594
}
597595
}
598596

599-
func TestParseYumCheckUpdateLine(t *testing.T) {
600-
r := newCentOS(config.ServerInfo{})
601-
r.Distro = config.Distro{Family: "centos"}
602-
var tests = []struct {
603-
in string
604-
out models.Package
597+
func Test_redhatBase_parseUpdatablePacksLine(t *testing.T) {
598+
type fields struct {
599+
base base
600+
sudo rootPriv
601+
}
602+
type args struct {
603+
line string
604+
}
605+
tests := []struct {
606+
name string
607+
fields fields
608+
args args
609+
want models.Package
610+
wantErr bool
605611
}{
606612
{
607-
"zlib 0 1.2.7 17.el7 rhui-REGION-rhel-server-releases",
608-
models.Package{
613+
name: `centos 7.0: "zlib" "0" "1.2.7" "17.el7" "rhui-REGION-rhel-server-releases"`,
614+
fields: fields{
615+
base: base{
616+
Distro: config.Distro{
617+
Family: constant.CentOS,
618+
Release: "7.0",
619+
},
620+
},
621+
},
622+
args: args{
623+
line: `"zlib" "0" "1.2.7" "17.el7" "rhui-REGION-rhel-server-releases"`,
624+
},
625+
want: models.Package{
609626
Name: "zlib",
610627
NewVersion: "1.2.7",
611628
NewRelease: "17.el7",
612629
Repository: "rhui-REGION-rhel-server-releases",
613630
},
614631
},
615632
{
616-
"shadow-utils 2 4.1.5.1 24.el7 rhui-REGION-rhel-server-releases",
617-
models.Package{
633+
name: `centos 7.0: "shadow-utils" "2" "4.1.5.1 24.el7" "rhui-REGION-rhel-server-releases"`,
634+
fields: fields{
635+
base: base{
636+
Distro: config.Distro{
637+
Family: constant.CentOS,
638+
Release: "7.0",
639+
},
640+
},
641+
},
642+
args: args{
643+
line: `"shadow-utils" "2" "4.1.5.1" "24.el7" "rhui-REGION-rhel-server-releases"`,
644+
},
645+
want: models.Package{
618646
Name: "shadow-utils",
619647
NewVersion: "2:4.1.5.1",
620648
NewRelease: "24.el7",
621649
Repository: "rhui-REGION-rhel-server-releases",
622650
},
623651
},
652+
{
653+
name: `amazon 2023: Is this ok [y/N]: "dnf" "0" "4.14.0" "1.amzn2023.0.6" "amazonlinux"`,
654+
fields: fields{
655+
base: base{
656+
Distro: config.Distro{
657+
Family: constant.Amazon,
658+
Release: "2023.7.20250512",
659+
},
660+
},
661+
},
662+
args: args{
663+
line: `Is this ok [y/N]: "dnf" "0" "4.14.0" "1.amzn2023.0.6" "amazonlinux"`,
664+
},
665+
want: models.Package{
666+
Name: "dnf",
667+
NewVersion: "4.14.0",
668+
NewRelease: "1.amzn2023.0.6",
669+
Repository: "amazonlinux",
670+
},
671+
},
624672
}
625-
626673
for _, tt := range tests {
627-
aPack, err := r.parseUpdatablePacksLine(tt.in)
628-
if err != nil {
629-
t.Errorf("Error has occurred, err: %+v\ntt.in: %v", err, tt.in)
630-
return
631-
}
632-
if !reflect.DeepEqual(tt.out, aPack) {
633-
e := pp.Sprintf("%v", tt.out)
634-
a := pp.Sprintf("%v", aPack)
635-
t.Errorf("expected %s, actual %s", e, a)
636-
}
674+
t.Run(tt.name, func(t *testing.T) {
675+
o := &redhatBase{
676+
base: tt.fields.base,
677+
sudo: tt.fields.sudo,
678+
}
679+
got, err := o.parseUpdatablePacksLine(tt.args.line)
680+
if (err != nil) != tt.wantErr {
681+
t.Errorf("redhatBase.parseUpdatablePacksLine() error = %v, wantErr %v", err, tt.wantErr)
682+
return
683+
}
684+
if !reflect.DeepEqual(got, tt.want) {
685+
t.Errorf("redhatBase.parseUpdatablePacksLine() = %v, want %v", got, tt.want)
686+
}
687+
})
637688
}
638689
}
639690

@@ -672,13 +723,12 @@ func Test_redhatBase_parseUpdatablePacksLines(t *testing.T) {
672723
},
673724
},
674725
args: args{
675-
stdout: `audit-libs 0 2.3.7 5.el6 base
676-
bash 0 4.1.2 33.el6_7.1 updates
677-
python-libs 0 2.6.6 64.el6 rhui-REGION-rhel-server-releases
678-
python-ordereddict 0 1.1 3.el6ev installed
679-
bind-utils 30 9.3.6 25.P1.el5_11.8 updates
680-
pytalloc 0 2.0.7 2.el6 @CentOS 6.5/6.5`,
681-
},
726+
stdout: `"audit-libs" "0" "2.3.7" "5.el6" "base"
727+
"bash" "0" "4.1.2" "33.el6_7.1" "updates"
728+
"python-libs" "0" "2.6.6" "64.el6" "rhui-REGION-rhel-server-releases"
729+
"python-ordereddict" "0" "1.1" "3.el6ev" "installed"
730+
"bind-utils" "30" "9.3.6" "25.P1.el5_11.8" "updates"
731+
"pytalloc" "0" "2.0.7" "2.el6" "@CentOS 6.5/6.5"`},
682732
want: models.Packages{
683733
"audit-libs": {
684734
Name: "audit-libs",
@@ -735,9 +785,9 @@ pytalloc 0 2.0.7 2.el6 @CentOS 6.5/6.5`,
735785
},
736786
},
737787
args: args{
738-
stdout: `bind-libs 32 9.8.2 0.37.rc1.45.amzn1 amzn-main
739-
java-1.7.0-openjdk 0 1.7.0.95 2.6.4.0.65.amzn1 amzn-main
740-
if-not-architecture 0 100 200 amzn-main`,
788+
stdout: `"bind-libs" "32" "9.8.2" "0.37.rc1.45.amzn1" "amzn-main"
789+
"java-1.7.0-openjdk" "0" "1.7.0.95" "2.6.4.0.65.amzn1" "amzn-main"
790+
"if-not-architecture" "0" "100" "200" "amzn-main"`,
741791
},
742792
want: models.Packages{
743793
"bind-libs": {

0 commit comments

Comments
 (0)