From c92d2d064af3325caea25fb6b1e46b32947a0d27 Mon Sep 17 00:00:00 2001 From: Masahiro Ono Date: Tue, 19 Jul 2016 18:51:02 +0900 Subject: [PATCH] Support high-speed scanning for CentOS --- scan/redhat.go | 154 ++++++++++++++++++++- scan/redhat_test.go | 316 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 466 insertions(+), 4 deletions(-) diff --git a/scan/redhat.go b/scan/redhat.go index fb833ee5..b5668cbb 100644 --- a/scan/redhat.go +++ b/scan/redhat.go @@ -300,12 +300,31 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) PackInfo models.PackageInfo CveIDs []string } + + var rpm2changelog map[string]*string + if !config.Conf.SSHExternal { + allChangelog, err := o.getAllChangelog(packInfoList) + if err != nil { + o.log.Errorf("Failed to getAllchangelog. err: %s", err) + return nil, err + } + rpm2changelog, err = o.parseAllChangelog(allChangelog) + if err != nil { + return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err) + } + } + var results []PackInfoCveIDs for i, packInfo := range packInfoList { - changelog, err := o.getChangelog(packInfo.Name) - if err != nil { - o.log.Errorf("Failed to collect CVE IDs. err: %s", err) - return nil, err + changelog := "" + if !config.Conf.SSHExternal { + changelog = o.getChangelogCVELines(rpm2changelog, packInfo) + } else { + changelog, err = o.getChangelog(packInfo.Name) + if err != nil { + o.log.Errorf("Failed to collect CVE IDs. err: %s", err) + return nil, err + } } // Collect unique set of CVE-ID in each changelog @@ -469,6 +488,129 @@ func (o *redhat) getChangelog(packageNames string) (stdout string, err error) { return r.Stdout, nil } +func (o *redhat) mkPstring() *string { + str := "" + return &str +} + +func (o *redhat) regexpReplace(src string, pat string, rep string) string { + r := regexp.MustCompile(pat) + return r.ReplaceAllString(src, rep) +} + +func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo models.PackageInfo) string { + rpm := fmt.Sprintf("%s-%s-%s", packInfo.Name, o.regexpReplace(packInfo.NewVersion, `^[0-9]+:`, ""), packInfo.NewRelease) + retLine := "" + if rpm2changelog[rpm] != nil { + lines := strings.Split(*rpm2changelog[rpm], "\n") + for _, line := range lines { + match, _ := regexp.MatchString("CVE-[0-9]+-[0-9]+", line) + if match { + retLine += fmt.Sprintf("%s\n", line) + } + } + } + return retLine +} + +func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) { + var majorVersion int + if 0 < len(o.Release) && o.Family == "centos" { + majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0]) + } else { + return nil, fmt.Errorf( + "Not implemented yet. family: %s, release: %s", + o.Family, o.Release) + } + + orglines := strings.Split(allChangelog, "\n") + tmpline := "" + var lines []string + var prev, now bool + for i, _ := range orglines { + if majorVersion == 5 { + /* for CentOS5 (yum-util < 1.1.20) */ + prev = false + now = false + if i > 0 { + prev, _ = o.isRpmPackageNameLine(orglines[i-1]) + } + now, _ = o.isRpmPackageNameLine(orglines[i]) + if prev && now { + tmpline = fmt.Sprintf("%s, %s", tmpline, orglines[i]) + continue + } + if !prev && now { + tmpline = fmt.Sprintf("%s%s", tmpline, orglines[i]) + continue + } + if tmpline != "" { + lines = append(lines, fmt.Sprintf("%s", tmpline)) + tmpline = "" + } + lines = append(lines, fmt.Sprintf("%s", orglines[i])) + } else { + /* for CentOS6,7 (yum-util >= 1.1.20) */ + line := orglines[i] + line = o.regexpReplace(line, `^ChangeLog for: `, "") + line = o.regexpReplace(line, `^\*\*\sNo\sChangeLog\sfor:.*`, "") + lines = append(lines, line) + } + } + + rpm2changelog := make(map[string]*string) + writePointer := o.mkPstring() + for _, line := range lines { + match, _ := o.isRpmPackageNameLine(line) + if match { + rpms := strings.Split(line, ",") + pNewString := o.mkPstring() + writePointer = pNewString + for _, rpm := range rpms { + rpm = strings.TrimSpace(rpm) + rpm = o.regexpReplace(rpm, `^[0-9]+:`, "") + rpm = o.regexpReplace(rpm, `\.centos([.0-9]+)?\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "") + rpm = o.regexpReplace(rpm, `\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "") + rpm2changelog[rpm] = pNewString + } + } else { + stop, _ := regexp.MatchString("^Dependencies Resolved", line) + if stop { + return rpm2changelog, nil + } else { + *writePointer += fmt.Sprintf("%s\n", line) + } + } + } + return rpm2changelog, nil +} + +func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout string, err error) { + packageNames := "" + for _, packInfo := range packInfoList { + packageNames += fmt.Sprintf("%s ", packInfo.Name) + } + + command := "" + if o.ServerInfo.User == "root" { + command = "echo N | " + } + if 0 < len(config.Conf.HTTPProxy) { + command += util.ProxyEnv() + } + + // yum update --changelog doesn't have --color option. + command += fmt.Sprintf(" yum update --changelog %s", packageNames) + + r := o.ssh(command, sudo) + if !r.isSuccess(0, 1) { + return "", fmt.Errorf( + "Failed to get changelog. status: %d, stdout: %s, stderr: %s", + r.ExitStatus, r.Stdout, r.Stderr) + } + return r.Stdout, nil +} + type distroAdvisoryCveIDs struct { DistroAdvisory models.DistroAdvisory CveIDs []string @@ -694,6 +836,10 @@ func (o *redhat) isHorizontalRule(line string) (bool, error) { return regexp.MatchString("^=+$", line) } +func (o *redhat) isRpmPackageNameLine(line string) (bool, error) { + return regexp.MatchString("^[^ ]+(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)", line) +} + // see test case func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) { pkgs := strings.Split(strings.TrimSpace(line), ",") diff --git a/scan/redhat_test.go b/scan/redhat_test.go index 61fc1a64..6477af91 100644 --- a/scan/redhat_test.go +++ b/scan/redhat_test.go @@ -302,6 +302,42 @@ func TestIsDescriptionLine(t *testing.T) { } } +func TestIsRpmPackageNameLine(t *testing.T) { + r := newRedhat(config.ServerInfo{}) + var tests = []struct { + in string + found bool + }{ + { + "stunnel-4.15-2.el5.2.i386", + true, + }, + { + "iproute-2.6.18-15.el5.i386", + true, + }, + { + "1:yum-updatesd-0.9-6.el5_10.noarch", + true, + }, + { + "glibc-2.12-1.192.el6.x86_64", + true, + }, + { + " glibc-2.12-1.192.el6.x86_64", + false, + }, + } + + for i, tt := range tests { + found, err := r.isRpmPackageNameLine(tt.in) + if tt.found != found { + t.Errorf("[%d] line: %s, expected %t, actual %t, err %v", i, tt.in, tt.found, found, err) + } + } +} + func TestParseYumUpdateinfoToGetSeverity(t *testing.T) { r := newRedhat(config.ServerInfo{}) var tests = []struct { @@ -844,3 +880,283 @@ func TestExtractPackNameVerRel(t *testing.T) { } } + +const ( + /* for CentOS6,7 (yum-util >= 1.1.20) */ + stdoutCentos6 = `---> Package libaio.x86_64 0:0.3.107-10.el6 will be installed +--> Finished Dependency Resolution + +Changes in packages about to be updated: + +ChangeLog for: binutils-2.20.51.0.2-5.44.el6.x86_64 +* Mon Dec 7 21:00:00 2015 Nick Clifton - 2.20.51.0.2-5.44 +- Backport upstream RELRO fixes. (#1227839) + +** No ChangeLog for: chkconfig-1.3.49.5-1.el6.x86_64 + +ChangeLog for: coreutils-8.4-43.el6.x86_64, coreutils-libs-8.4-43.el6.x86_64 +* Wed Feb 10 21:00:00 2016 Ondrej Vasik - 8.4-43 +- sed should actually be /bin/sed (related #1222140) + +* Wed Jan 6 21:00:00 2016 Ondrej Vasik - 8.4-41 +- colorls.sh,colorls.csh - call utilities with complete path (#1222140) +- mkdir, mkfifo, mknod - respect default umask/acls when + COREUTILS_CHILD_DEFAULT_ACLS envvar is set (to match rhel 7 behaviour, + +ChangeLog for: centos-release-6-8.el6.centos.12.3.x86_64 +* Wed May 18 21:00:00 2016 Johnny Hughes 6-8.el6.centos.12.3 +- CentOS-6.8 Released +- TESTSTRING CVE-0000-0000 + +ChangeLog for: 12:dhclient-4.1.1-51.P1.el6.centos.x86_64, 12:dhcp-common-4.1.1-51.P1.el6.centos.x86_64 +* Tue May 10 21:00:00 2016 Johnny Hughes - 12:4.1.1-51.P1 +- created patch 1000 for CentOS Branding +- replaced vvendor variable with CentOS in the SPEC file +- TESTSTRING CVE-1111-1111 + +* Mon Jan 11 21:00:00 2016 Jiri Popelka - 12:4.1.1-51.P1 +- send unicast request/release via correct interface (#1297445) + +* Thu Dec 3 21:00:00 2015 Jiri Popelka - 12:4.1.1-50.P1 +- Lease table overflow crash. (#1133917) +- Add ignore-client-uids option. (#1196768) +- dhclient-script: it's OK if the arping reply comes from our system. (#1204095) +- VLAN ID is only bottom 12-bits of TCI. (#1259552) +- dhclient: Make sure link-local address is ready in stateless mode. (#1263466) +- dhclient-script: make_resolv_conf(): Keep old nameservers + if server sends domain-name/search, but no nameservers. (#1269595) + +ChangeLog for: file-5.04-30.el6.x86_64, file-libs-5.04-30.el6.x86_64 +* Tue Feb 16 21:00:00 2016 Jan Kaluza 5.04-30 +- fix CVE-2014-3538 (unrestricted regular expression matching) + +* Tue Jan 5 21:00:00 2016 Jan Kaluza 5.04-29 +- fix #1284826 - try to read ELF header to detect corrupted one + +* Wed Dec 16 21:00:00 2015 Jan Kaluza 5.04-28 +- fix #1263987 - fix bugs found by coverity in the patch + +* Thu Nov 26 21:00:00 2015 Jan Kaluza 5.04-27 +- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571) +- fix CVE-2014-3710 (out-of-bounds read in elf note headers) +- fix CVE-2014-8116 (multiple DoS issues (resource consumption)) +- fix CVE-2014-8117 (denial of service issue (resource consumption)) +- fix CVE-2014-9620 (limit the number of ELF notes processed) +- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory) + + +Dependencies Resolved + +` + /* for CentOS5 (yum-util < 1.1.20) */ + stdoutCentos5 = `---> Package portmap.i386 0:4.0-65.2.2.1 set to be updated +--> Finished Dependency Resolution + +Changes in packages about to be updated: + +libuser-0.54.7-3.el5.i386 +nss_db-2.2-38.el5_11.i386 +* Thu Nov 20 23:00:00 2014 Nalin Dahyabhai - 2.2-38 +- build without strict aliasing (internal build tooling) + +* Sat Nov 15 23:00:00 2014 Nalin Dahyabhai - 2.2-37 +- pull in fix for a memory leak in nss_db (#1163493) + +acpid-1.0.4-12.el5.i386 +* Thu Oct 6 00:00:00 2011 Jiri Skala - 1.0.4-12 +- Resolves: #729769 - acpid dumping useless info to log + +nash-5.1.19.6-82.el5.i386, mkinitrd-5.1.19.6-82.el5.i386 +* Tue Apr 15 00:00:00 2014 Brian C. Lane 5.1.19.6-82 +- Use ! instead of / when searching sysfs for ccis device + Resolves: rhbz#988020 +- Always include ahci module (except on s390) (bcl) + Resolves: rhbz#978245 +- Prompt to recreate default initrd (karsten) + Resolves: rhbz#472764 + +util-linux-2.13-0.59.el5_8.i386 +* Wed Oct 17 00:00:00 2012 Karel Zak 2.13-0.59.el5_8 +- fix #865791 - fdisk fails to partition disk not in use + +* Wed Dec 21 23:00:00 2011 Karel Zak 2.13-0.59 +- fix #768382 - CVE-2011-1675 CVE-2011-1677 util-linux various flaws + +* Wed Oct 26 00:00:00 2011 Karel Zak 2.13-0.58 +- fix #677452 - util-linux fails to build with gettext-0.17 + +30:bind-utils-9.3.6-25.P1.el5_11.8.i386, 30:bind-libs-9.3.6-25.P1.el5_11.8.i386 +* Mon Mar 14 23:00:00 2016 Tomas Hozza - 30:9.3.6-25.P1.8 +- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite + +* Wed Mar 9 23:00:00 2016 Tomas Hozza - 30:9.3.6-25.P1.7 +- Fix CVE-2016-1285 and CVE-2016-1286 + +* Mon Jan 18 23:00:00 2016 Tomas Hozza - 30:9.3.6-25.P1.6 +- Fix CVE-2015-8704 + +* Thu Sep 3 00:00:00 2015 Tomas Hozza - 30:9.3.6-25.P1.5 +- Fix CVE-2015-8000 + + +Dependencies Resolved + +` +) + +func TestGetChangelogCVELines(t *testing.T) { + var testsCentos6 = []struct { + in models.PackageInfo + out string + }{ + { + models.PackageInfo{ + Name: "binutils", + NewVersion: "2.20.51.0.2", + NewRelease: "5.44.el6", + }, + "", + }, + { + models.PackageInfo{ + Name: "centos-release", + NewVersion: "6", + NewRelease: "8.el6", + }, + `- TESTSTRING CVE-0000-0000 +`, + }, + { + models.PackageInfo{ + Name: "dhclient", + NewVersion: "12:4.1.1", + NewRelease: "51.P1.el6", + }, + `- TESTSTRING CVE-1111-1111 +`, + }, + { + models.PackageInfo{ + Name: "coreutils-libs", + NewVersion: "8.4", + NewRelease: "43.el6", + }, + "", + }, + { + models.PackageInfo{ + Name: "file", + NewVersion: "5.04", + NewRelease: "30.el6", + }, + `- fix CVE-2014-3538 (unrestricted regular expression matching) +- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571) +- fix CVE-2014-3710 (out-of-bounds read in elf note headers) +- fix CVE-2014-8116 (multiple DoS issues (resource consumption)) +- fix CVE-2014-8117 (denial of service issue (resource consumption)) +- fix CVE-2014-9620 (limit the number of ELF notes processed) +- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory) +`, + }, + { + models.PackageInfo{ + Name: "file-libs", + NewVersion: "5.04", + NewRelease: "30.el6", + }, + `- fix CVE-2014-3538 (unrestricted regular expression matching) +- fix CVE-2014-3587 (incomplete fix for CVE-2012-1571) +- fix CVE-2014-3710 (out-of-bounds read in elf note headers) +- fix CVE-2014-8116 (multiple DoS issues (resource consumption)) +- fix CVE-2014-8117 (denial of service issue (resource consumption)) +- fix CVE-2014-9620 (limit the number of ELF notes processed) +- fix CVE-2014-9653 (malformed elf file causes access to uninitialized memory) +`, + }, + } + + r := newRedhat(config.ServerInfo{}) + r.Family = "centos" + r.Release = "6.7" + for _, tt := range testsCentos6 { + rpm2changelog, err := r.parseAllChangelog(stdoutCentos6) + if err != nil { + t.Errorf("err: %s", err) + } + changelog := r.getChangelogCVELines(rpm2changelog, tt.in) + if tt.out != changelog { + t.Errorf("line: expected %s, actual %s", tt.out, changelog) + } + } + + var testsCentos5 = []struct { + in models.PackageInfo + out string + }{ + { + models.PackageInfo{ + Name: "libuser", + NewVersion: "0.54.7", + NewRelease: "3.el5", + }, + "", + }, + { + models.PackageInfo{ + Name: "nss_db", + NewVersion: "2.2", + NewRelease: "38.el5_11", + }, + "", + }, + { + models.PackageInfo{ + Name: "acpid", + NewVersion: "1.0.4", + NewRelease: "82.el5", + }, + "", + }, + { + models.PackageInfo{ + Name: "mkinitrd", + NewVersion: "5.1.19.6", + NewRelease: "82.el5", + }, + "", + }, + { + models.PackageInfo{ + Name: "util-linux", + NewVersion: "2.13", + NewRelease: "0.59.el5_8", + }, + `- fix #768382 - CVE-2011-1675 CVE-2011-1677 util-linux various flaws +`, + }, + { + models.PackageInfo{ + Name: "bind-libs", + NewVersion: "30:9.3.6", + NewRelease: "25.P1.el5_11.8", + }, + `- Fix issue with patch for CVE-2016-1285 and CVE-2016-1286 found by test suite +- Fix CVE-2016-1285 and CVE-2016-1286 +- Fix CVE-2015-8704 +- Fix CVE-2015-8000 +`, + }, + } + + r.Release = "5.6" + for _, tt := range testsCentos5 { + rpm2changelog, err := r.parseAllChangelog(stdoutCentos5) + if err != nil { + t.Errorf("err: %s", err) + } + changelog := r.getChangelogCVELines(rpm2changelog, tt.in) + if tt.out != changelog { + t.Errorf("line: expected %s, actual %s", tt.out, changelog) + } + } +}