From f8c0b387163871e58907fd5b0b321e3781165a8c Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Tue, 2 Jul 2019 14:55:46 +0900 Subject: [PATCH] feat(fast-root): get running procs for each pkgs (all RHEL, CentOS, AmazonLinux, Ubuntu, Debian) (#855) * fix(scan): exec yum-plugin-ps on RHEL6 and 7 * feat(yumps): get affected procs on RHEL6 and RHEL8 * feat(scan): get affected processes for each packages * tuning * feat(scan): get running procs for each pkgs on Debian, Ubuntu --- scan/amazon.go | 9 +- scan/base.go | 59 +++++++++ scan/base_test.go | 74 +++++++++++ scan/centos.go | 16 ++- scan/debian.go | 98 +++++++++++++++ scan/debian_test.go | 35 ++++++ scan/oracle.go | 21 +++- scan/redhatbase.go | 189 +++++++++++----------------- scan/redhatbase_test.go | 269 ---------------------------------------- scan/rhel.go | 16 ++- scan/utils.go | 21 +++- 11 files changed, 402 insertions(+), 405 deletions(-) diff --git a/scan/amazon.go b/scan/amazon.go index 2bcb8e62..75fbce58 100644 --- a/scan/amazon.go +++ b/scan/amazon.go @@ -80,10 +80,11 @@ func (o *amazon) sudoNoPasswdCmdsFast() []cmd { func (o *amazon) sudoNoPasswdCmdsFastRoot() []cmd { return []cmd{ - {"yum -q ps all --color=never", exitStatusZero}, - {"stat /proc/1/exe", exitStatusZero}, {"needs-restarting", exitStatusZero}, {"which which", exitStatusZero}, + {"stat /proc/1/exe", exitStatusZero}, + {"ls -l /proc/1/exe", exitStatusZero}, + {"cat /proc/1/maps", exitStatusZero}, } } @@ -100,3 +101,7 @@ func (o rootPrivAmazon) repoquery() bool { func (o rootPrivAmazon) yumMakeCache() bool { return false } + +func (o rootPrivAmazon) yumPS() bool { + return false +} diff --git a/scan/base.go b/scan/base.go index 87575177..ae1a55e1 100644 --- a/scan/base.go +++ b/scan/base.go @@ -32,6 +32,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" + "github.com/future-architect/vuls/util" "github.com/sirupsen/logrus" "golang.org/x/xerrors" @@ -722,3 +723,61 @@ func (l *base) detectWpPlugins() ([]models.WpPackage, error) { } return plugins, nil } + +func (l *base) ps() (stdout string, err error) { + cmd := `LANGUAGE=en_US.UTF-8 ps --no-headers --ppid 2 -p 2 --deselect -o pid,comm | awk '{print $1,$2}'` + r := l.exec(util.PrependProxyEnv(cmd), noSudo) + if !r.isSuccess() { + return "", xerrors.Errorf("Failed to SSH: %s", r) + } + return r.Stdout, nil +} + +func (l *base) parsePs(stdout string) map[string]string { + pidNames := map[string]string{} + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + ss := strings.Fields(line) + if len(ss) < 2 { + continue + } + pidNames[ss[0]] = ss[1] + } + return pidNames +} + +func (l *base) lsProcExe(pid string) (stdout string, err error) { + cmd := fmt.Sprintf("ls -l /proc/%s/exe", pid) + r := l.exec(util.PrependProxyEnv(cmd), sudo) + if !r.isSuccess() { + return "", xerrors.Errorf("Failed to SSH: %s", r) + } + return r.Stdout, nil +} + +func (l *base) parseLsProcExe(stdout string) (string, error) { + ss := strings.Fields(stdout) + if len(ss) < 11 { + return "", xerrors.Errorf("Unknown format: %s", stdout) + } + return ss[10], nil +} + +func (l *base) grepProcMap(pid string) (stdout string, err error) { + cmd := fmt.Sprintf(`cat /proc/%s/maps 2>/dev/null | grep -v " 00:00 " | awk '{print $6}' | sort -n | uniq`, pid) + r := l.exec(util.PrependProxyEnv(cmd), sudo) + if !r.isSuccess() { + return "", xerrors.Errorf("Failed to SSH: %s", r) + } + return r.Stdout, nil +} + +func (l *base) parseGrepProcMap(stdout string) (soPaths []string) { + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + soPaths = append(soPaths, line) + } + return soPaths +} diff --git a/scan/base_test.go b/scan/base_test.go index 39cecf0b..1ce90da5 100644 --- a/scan/base_test.go +++ b/scan/base_test.go @@ -22,6 +22,13 @@ import ( "testing" "github.com/future-architect/vuls/config" + _ "github.com/knqyf263/fanal/analyzer/library/bundler" + _ "github.com/knqyf263/fanal/analyzer/library/cargo" + _ "github.com/knqyf263/fanal/analyzer/library/composer" + _ "github.com/knqyf263/fanal/analyzer/library/npm" + _ "github.com/knqyf263/fanal/analyzer/library/pipenv" + _ "github.com/knqyf263/fanal/analyzer/library/poetry" + _ "github.com/knqyf263/fanal/analyzer/library/yarn" ) func TestParseDockerPs(t *testing.T) { @@ -178,3 +185,70 @@ func TestParseSystemctlStatus(t *testing.T) { } } } + +func Test_base_parseLsProcExe(t *testing.T) { + type args struct { + stdout string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "systemd", + args: args{ + stdout: "lrwxrwxrwx 1 root root 0 Jun 29 17:13 /proc/1/exe -> /lib/systemd/systemd", + }, + want: "/lib/systemd/systemd", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := &base{} + got, err := l.parseLsProcExe(tt.args.stdout) + if (err != nil) != tt.wantErr { + t.Errorf("base.parseLsProcExe() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("base.parseLsProcExe() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_base_parseGrepProcMap(t *testing.T) { + type args struct { + stdout string + } + tests := []struct { + name string + args args + wantSoPaths []string + }{ + { + name: "systemd", + args: args{ + `/etc/selinux/targeted/contexts/files/file_contexts.bin +/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin +/usr/lib64/libdl-2.28.so`, + }, + wantSoPaths: []string{ + "/etc/selinux/targeted/contexts/files/file_contexts.bin", + "/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin", + "/usr/lib64/libdl-2.28.so", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := &base{} + if gotSoPaths := l.parseGrepProcMap(tt.args.stdout); !reflect.DeepEqual(gotSoPaths, tt.wantSoPaths) { + t.Errorf("base.parseGrepProcMap() = %v, want %v", gotSoPaths, tt.wantSoPaths) + } + }) + } +} diff --git a/scan/centos.go b/scan/centos.go index 4f89ed2c..dcb946fd 100644 --- a/scan/centos.go +++ b/scan/centos.go @@ -88,19 +88,19 @@ func (o *centos) sudoNoPasswdCmdsFast() []cmd { } func (o *centos) sudoNoPasswdCmdsFastRoot() []cmd { - if o.getServerInfo().Mode.IsOffline() { - // yum ps needs internet connection + if !o.ServerInfo.IsContainer() { return []cmd{ - {"stat /proc/1/exe", exitStatusZero}, + {"repoquery -h", exitStatusZero}, {"needs-restarting", exitStatusZero}, {"which which", exitStatusZero}, + {"stat /proc/1/exe", exitStatusZero}, + {"ls -l /proc/1/exe", exitStatusZero}, + {"cat /proc/1/maps", exitStatusZero}, } } return []cmd{ - {"yum -q ps all --color=never", exitStatusZero}, - {"stat /proc/1/exe", exitStatusZero}, + {"repoquery -h", exitStatusZero}, {"needs-restarting", exitStatusZero}, - {"which which", exitStatusZero}, } } @@ -117,3 +117,7 @@ func (o rootPrivCentos) repoquery() bool { func (o rootPrivCentos) yumMakeCache() bool { return false } + +func (o rootPrivCentos) yumPS() bool { + return false +} diff --git a/scan/debian.go b/scan/debian.go index bb2fe5b2..cf76e248 100644 --- a/scan/debian.go +++ b/scan/debian.go @@ -149,6 +149,8 @@ func (o *debian) checkIfSudoNoPasswd() error { cmds := []string{ "checkrestart", "stat /proc/1/exe", + "ls -l /proc/1/exe", + "cat /proc/1/maps", } if !o.getServerInfo().Mode.IsOffline() { @@ -262,6 +264,15 @@ func (o *debian) preCure() error { } func (o *debian) postScan() error { + if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() { + if err := o.dpkgPs(); err != nil { + err = xerrors.Errorf("Failed to dpkg-ps: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } + } + if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() { if err := o.checkrestart(); err != nil { err = xerrors.Errorf("Failed to scan need-restarting processes: %w", err) @@ -1109,3 +1120,90 @@ func (o *debian) parseCheckRestart(stdout string) (models.Packages, []string) { } return packs, unknownServices } + +func (o *debian) dpkgPs() error { + stdout, err := o.ps() + if err != nil { + return xerrors.Errorf("Failed to ps: %w", err) + } + pidNames := o.parsePs(stdout) + pidLoadedFiles := map[string][]string{} + // for pid, name := range pidNames { + for pid := range pidNames { + stdout := "" + stdout, err = o.lsProcExe(pid) + if err != nil { + o.log.Debugf("Failed to exec /proc/%s/exe err: %s", pid, err) + continue + } + s, err := o.parseLsProcExe(stdout) + if err != nil { + o.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err) + continue + } + pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s) + + stdout, err = o.grepProcMap(pid) + if err != nil { + o.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err) + continue + } + ss := o.parseGrepProcMap(stdout) + pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...) + } + + for pid, loadedFiles := range pidLoadedFiles { + o.log.Debugf("dpkg -S %#v", loadedFiles) + pkgNames, err := o.getPkgName(loadedFiles) + if err != nil { + o.log.Debugf("Failed to get package name by file path: %s, err: %s", pkgNames, err) + continue + } + + procName := "" + if _, ok := pidNames[pid]; ok { + procName = pidNames[pid] + } + proc := models.AffectedProcess{ + PID: pid, + Name: procName, + } + + for _, n := range pkgNames { + p, ok := o.Packages[n] + if !ok { + return xerrors.Errorf("pkg not found %s", n) + } + p.AffectedProcs = append(p.AffectedProcs, proc) + o.Packages[p.Name] = p + } + } + return nil +} + +func (o *debian) getPkgName(paths []string) (pkgNames []string, err error) { + cmd := "dpkg -S " + strings.Join(paths, " ") + r := o.exec(util.PrependProxyEnv(cmd), noSudo) + if !r.isSuccess(0, 1) { + return nil, xerrors.Errorf("Failed to SSH: %s", r) + } + return o.parseGetPkgName(r.Stdout), nil +} + +func (o *debian) parseGetPkgName(stdout string) (pkgNames []string) { + uniq := map[string]struct{}{} + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + ss := strings.Fields(line) + if len(ss) < 2 || ss[1] == "no" { + continue + } + s := strings.Split(ss[0], ":")[0] + uniq[s] = struct{}{} + } + for n := range uniq { + pkgNames = append(pkgNames, n) + } + return pkgNames +} diff --git a/scan/debian_test.go b/scan/debian_test.go index 9a47b7bf..222153f9 100644 --- a/scan/debian_test.go +++ b/scan/debian_test.go @@ -726,3 +726,38 @@ util-linux: } } } + +func Test_debian_parseGetPkgName(t *testing.T) { + type args struct { + stdout string + } + tests := []struct { + name string + args args + wantPkgNames []string + }{ + { + name: "success", + args: args{ + stdout: `udev: /lib/systemd/systemd-udevd +dpkg-query: no path found matching pattern /lib/modules/3.16.0-6-amd64/modules.alias.bin +udev: /lib/systemd/systemd-udevd +dpkg-query: no path found matching pattern /lib/udev/hwdb.bin +libuuid1:amd64: /lib/x86_64-linux-gnu/libuuid.so.1.3.0`, + }, + wantPkgNames: []string{ + "udev", + "libuuid1", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &debian{} + gotPkgNames := o.parseGetPkgName(tt.args.stdout) + if !reflect.DeepEqual(gotPkgNames, tt.wantPkgNames) { + t.Errorf("debian.parseGetPkgName() = %v, want %v", gotPkgNames, tt.wantPkgNames) + } + }) + } +} diff --git a/scan/oracle.go b/scan/oracle.go index 2b6d876b..a57080dd 100644 --- a/scan/oracle.go +++ b/scan/oracle.go @@ -74,11 +74,20 @@ func (o *oracle) sudoNoPasswdCmdsFast() []cmd { } func (o *oracle) sudoNoPasswdCmdsFastRoot() []cmd { - cmds := []cmd{{"needs-restarting", exitStatusZero}} - if o.getServerInfo().Mode.IsOffline() { - return cmds + if !o.ServerInfo.IsContainer() { + return []cmd{ + {"repoquery -h", exitStatusZero}, + {"needs-restarting", exitStatusZero}, + {"which which", exitStatusZero}, + {"stat /proc/1/exe", exitStatusZero}, + {"ls -l /proc/1/exe", exitStatusZero}, + {"cat /proc/1/maps", exitStatusZero}, + } + } + return []cmd{ + {"repoquery -h", exitStatusZero}, + {"needs-restarting", exitStatusZero}, } - return append(cmds, cmd{"repoquery -h", exitStatusZero}) } func (o *oracle) sudoNoPasswdCmdsDeep() []cmd { @@ -94,3 +103,7 @@ func (o rootPrivOracle) repoquery() bool { func (o rootPrivOracle) yumMakeCache() bool { return true } + +func (o rootPrivOracle) yumPS() bool { + return true +} diff --git a/scan/redhatbase.go b/scan/redhatbase.go index 6fe41786..a6b80e0e 100644 --- a/scan/redhatbase.go +++ b/scan/redhatbase.go @@ -142,6 +142,7 @@ type redhatBase struct { type rootPriv interface { repoquery() bool yumMakeCache() bool + yumPS() bool } type cmd struct { @@ -189,7 +190,7 @@ func (o *redhatBase) preCure() error { func (o *redhatBase) postScan() error { if o.isExecYumPS() { - if err := o.yumPS(); err != nil { + if err := o.yumPs(); err != nil { err = xerrors.Errorf("Failed to execute yum-ps: %w", err) o.log.Warnf("err: %+v", err) o.warns = append(o.warns, err) @@ -431,24 +432,9 @@ func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error return p, nil } -func (o *redhatBase) isExecScanUsingYum() bool { - if o.getServerInfo().Mode.IsOffline() { - return false - } - if o.Distro.Family == config.CentOS { - // CentOS doesn't have security channel - return false - } - if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() { - return true - } - return true -} - func (o *redhatBase) isExecYumPS() bool { - // RedHat has no yum-ps switch o.Distro.Family { - case config.RedHat, + case config.Oracle, config.OpenSUSE, config.OpenSUSELeap, config.SUSEEnterpriseServer, @@ -456,12 +442,7 @@ func (o *redhatBase) isExecYumPS() bool { config.SUSEOpenstackCloud: return false } - - // yum ps needs internet connection - if o.getServerInfo().Mode.IsOffline() || o.getServerInfo().Mode.IsFast() { - return false - } - return true + return !o.getServerInfo().Mode.IsFast() } func (o *redhatBase) isExecNeedsRestarting() bool { @@ -496,107 +477,69 @@ func (o *redhatBase) isExecNeedsRestarting() bool { return true } -func (o *redhatBase) yumPS() error { - cmd := "LANGUAGE=en_US.UTF-8 yum info yum" - r := o.exec(util.PrependProxyEnv(cmd), noSudo) - if !r.isSuccess() { - return xerrors.Errorf("Failed to SSH: %s", r) +func (o *redhatBase) yumPs() error { + stdout, err := o.ps() + if err != nil { + return xerrors.Errorf("Failed to yum ps: %w", err) } - if !o.checkYumPsInstalled(r.Stdout) { - switch o.Distro.Family { - case config.RedHat, config.Oracle: - return nil - default: - return xerrors.New("yum-plugin-ps is not installed") + pidNames := o.parsePs(stdout) + pidLoadedFiles := map[string][]string{} + // for pid, name := range pidNames { + for pid := range pidNames { + stdout := "" + stdout, err = o.lsProcExe(pid) + if err != nil { + o.log.Debugf("Failed to exec /proc/%s/exe err: %s", pid, err) + continue } - } - - cmd = "LANGUAGE=en_US.UTF-8 yum -q ps all --color=never" - r = o.exec(util.PrependProxyEnv(cmd), sudo) - if !r.isSuccess() { - return xerrors.Errorf("Failed to SSH: %s", r) - } - packs := o.parseYumPS(r.Stdout) - for name, pack := range packs { - p := o.Packages[name] - p.AffectedProcs = pack.AffectedProcs - o.Packages[name] = p - } - return nil -} - -func (o *redhatBase) checkYumPsInstalled(stdout string) bool { - scanner := bufio.NewScanner(strings.NewReader(stdout)) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if strings.HasPrefix(line, "Loaded plugins: ") { - if strings.Contains(line, " ps,") || strings.HasSuffix(line, " ps") { - return true - } - return false + s, err := o.parseLsProcExe(stdout) + if err != nil { + o.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err) + continue } - } - return false -} + pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s) -func (o *redhatBase) parseYumPS(stdout string) models.Packages { - packs := models.Packages{} - scanner := bufio.NewScanner(strings.NewReader(stdout)) - isPackageLine, needToParseProcline := false, false - currentPackName := "" - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) == 0 || - len(fields) == 1 && fields[0] == "ps" || - len(fields) == 6 && fields[0] == "pid" { + stdout, err = o.grepProcMap(pid) + if err != nil { + o.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err) + continue + } + ss := o.parseGrepProcMap(stdout) + pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...) + } + + for pid, loadedFiles := range pidLoadedFiles { + o.log.Debugf("rpm -qf: %#v", loadedFiles) + pkgNames, err := o.getPkgName(loadedFiles) + if err != nil { + o.log.Debugf("Failed to get package name by file path: %s, err: %s", pkgNames, err) continue } - isPackageLine = !strings.HasPrefix(line, " ") - if isPackageLine { - if 1 < len(fields) && fields[1] == "Upgrade" { - needToParseProcline = true + uniq := map[string]struct{}{} + for _, name := range pkgNames { + uniq[name] = struct{}{} + } - // Search o.Packages to divide into name, version, release - name, pack, found := o.Packages.FindOne(func(p models.Package) bool { - var epochNameVerRel string - if index := strings.Index(p.Version, ":"); 0 < index { - epoch := p.Version[0:index] - ver := p.Version[index+1 : len(p.Version)] - epochNameVerRel = fmt.Sprintf("%s:%s-%s-%s.%s", - epoch, p.Name, ver, p.Release, p.Arch) - } else { - epochNameVerRel = fmt.Sprintf("%s-%s-%s.%s", - p.Name, p.Version, p.Release, p.Arch) - } - return strings.HasPrefix(fields[0], epochNameVerRel) - }) - if !found { - o.log.Errorf("`yum ps` package is not found: %s", line) - continue - } - packs[name] = pack - currentPackName = name - } else { - needToParseProcline = false - } - } else if needToParseProcline { - if 6 < len(fields) { - proc := models.AffectedProcess{ - PID: fields[0], - Name: fields[1], - } - pack := packs[currentPackName] - pack.AffectedProcs = append(pack.AffectedProcs, proc) - packs[currentPackName] = pack - } else { - o.log.Errorf("`yum ps` Unknown Format: %s", line) - continue + procName := "" + if _, ok := pidNames[pid]; ok { + procName = pidNames[pid] + } + proc := models.AffectedProcess{ + PID: pid, + Name: procName, + } + + for fqpn := range uniq { + p, err := o.Packages.FindByFQPN(fqpn) + if err != nil { + return err } + p.AffectedProcs = append(p.AffectedProcs, proc) + o.Packages[p.Name] = *p } } - return packs + return nil } func (o *redhatBase) needsRestarting() error { @@ -690,8 +633,22 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) { return strings.Replace(fqpn, "-(none):", "-", -1), nil } -func (o *redhatBase) hasYumColorOption() bool { - cmd := "yum --help | grep color" +func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) { + cmd := rpmQf(o.Distro) + strings.Join(paths, " ") r := o.exec(util.PrependProxyEnv(cmd), noSudo) - return len(r.Stdout) > 0 + if !r.isSuccess() { + return nil, xerrors.Errorf("Failed to SSH: %s", r) + } + + scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) + for scanner.Scan() { + line := scanner.Text() + pack, err := o.parseInstalledPackagesLine(line) + if err != nil { + o.log.Debugf("Failed to parse rpm -qf line: %s, r: %s. continue", line, r) + continue + } + pkgNames = append(pkgNames, pack.FQPN()) + } + return pkgNames, nil } diff --git a/scan/redhatbase_test.go b/scan/redhatbase_test.go index 6f358e90..b516da56 100644 --- a/scan/redhatbase_test.go +++ b/scan/redhatbase_test.go @@ -329,229 +329,6 @@ if-not-architecture 0 100 200 amzn-main` } } -func TestCheckYumPsInstalled(t *testing.T) { - r := newCentOS(config.ServerInfo{}) - var tests = []struct { - in string - out bool - }{ - { - in: `Loaded plugins: changelog, fastestmirror, ps, remove-with-leaves, show-leaves -Loading mirror speeds from cached hostfile - * base: ftp.tsukuba.wide.ad.jp - * extras: ftp.tsukuba.wide.ad.jp - * updates: ftp.tsukuba.wide.ad.jp -Installed Packages -Name : yum -Arch : noarch -Version : 3.4.3 -Release : 150.el7.centos -Size : 5.5 M -Repo : installed -From repo : anaconda -Summary : RPM package installer/updater/manager -URL : http://yum.baseurl.org/ -License : GPLv2+ -Description : Yum is a utility that can check for and automatically download and - : install updated RPM packages. Dependencies are obtained and downloaded - : automatically, prompting the user for permission as necessary. - -Available Packages -Name : yum -Arch : noarch -Version : 3.4.3 -Release : 154.el7.centos.1 -Size : 1.2 M -Repo : updates/7/x86_64 -Summary : RPM package installer/updater/manager -URL : http://yum.baseurl.org/ -License : GPLv2+ -Description : Yum is a utility that can check for and automatically download and - : install updated RPM packages. Dependencies are obtained and downloaded - : automatically, prompting the user for permission as necessary.`, - out: true, - }, - { - in: `Failed to set locale, defaulting to C -Loaded plugins: amazon-id, rhui-lb, search-disabled-repos -Installed Packages -Name : yum -Arch : noarch -Version : 3.4.3 -Release : 154.el7 -Size : 5.5 M -Repo : installed -From repo : rhui-REGION-rhel-server-releases -Summary : RPM package installer/updater/manager -URL : http://yum.baseurl.org/ -License : GPLv2+ -Description : Yum is a utility that can check for and automatically download and - : install updated RPM packages. Dependencies are obtained and downloaded - : automatically, prompting the user for permission as necessary.`, - out: false, - }, - } - - for _, tt := range tests { - ok := r.checkYumPsInstalled(tt.in) - if ok != tt.out { - t.Errorf("expected: %v\nactual: %v", tt.out, ok) - } - } -} - -func TestParseYumPS(t *testing.T) { - r := newCentOS(config.ServerInfo{}) - r.Distro = config.Distro{Family: "centos"} - r.Packages = models.NewPackages( - models.Package{ - Name: "python", - Version: "2.7.5", - Release: "34.el7", - Arch: "x86_64", - }, - models.Package{ - Name: "util-linux", - Version: "2.23.2", - Release: "26.el7", - Arch: "x86_64", - }, - models.Package{ - Name: "wpa_supplicant", - Version: "1:2.0", - Release: "17.el7_1", - Arch: "x86_64", - }, - models.Package{ - Name: "yum", - Version: "3.4.3", - Release: "150.el7.centos", - Arch: "noarch", - }, - ) - - var tests = []struct { - in string - out models.Packages - }{ - { - ` pid proc CPU RSS State uptime -python-2.7.5-34.el7.x86_64 Upgrade 2.7.5-48.el7.x86_64 - 741 tuned 1:54 16 MB Sleeping: 14 day(s) 21:52:32 - 38755 yum 0:00 42 MB Running: 00:00 -util-linux-2.23.2-26.el7.x86_64 Upgrade 2.23.2-33.el7_3.2.x86_64 - 626 agetty 0:00 848 kB Sleeping: 14 day(s) 21:52:37 - 628 agetty 0:00 848 kB Sleeping: 14 day(s) 21:52:37 -1:wpa_supplicant-2.0-17.el7_1.x86_64 Upgrade 1:2.0-21.el7_3.x86_64 - 638 wpa_supplicant 0:00 2.6 MB Sleeping: 14 day(s) 21:52:37 -yum-3.4.3-150.el7.centos.noarch - 38755 yum 0:00 42 MB Running: 00:00 -ps - `, - models.NewPackages( - models.Package{ - Name: "python", - Version: "2.7.5", - Release: "34.el7", - Arch: "x86_64", - // NewVersion: "2.7.5-", - // NewRelease: "48.el7.x86_64", - AffectedProcs: []models.AffectedProcess{ - { - PID: "741", - Name: "tuned", - }, - { - PID: "38755", - Name: "yum", - }, - }, - }, - models.Package{ - Name: "util-linux", - Version: "2.23.2", - Release: "26.el7", - Arch: "x86_64", - // NewVersion: "2.7.5", - // NewRelease: "48.el7.x86_64", - AffectedProcs: []models.AffectedProcess{ - { - PID: "626", - Name: "agetty", - }, - { - PID: "628", - Name: "agetty", - }, - }, - }, - models.Package{ - Name: "wpa_supplicant", - Version: "1:2.0", - Release: "17.el7_1", - Arch: "x86_64", - // NewVersion: "1:2.0", - // NewRelease: "21.el7_3.x86_64", - AffectedProcs: []models.AffectedProcess{ - { - PID: "638", - Name: "wpa_supplicant", - }, - }, - }, - ), - }, - { - ` pid proc CPU RSS State uptime -acpid-2.0.19-6.7.amzn1.x86_64 - 2388 acpid 0:00 1.4 MB Sleeping: 21:08 -at-3.1.10-48.15.amzn1.x86_64 - 2546 atd 0:00 164 kB Sleeping: 21:06 -cronie-anacron-1.4.4-15.8.amzn1.x86_64 - 2637 anacron 0:00 1.5 MB Sleeping: 13:14 -12:dhclient-4.1.1-51.P1.26.amzn1.x86_64 - 2061 dhclient 0:00 1.4 MB Sleeping: 21:10 - 2193 dhclient 0:00 2.1 MB Sleeping: 21:08 -mingetty-1.08-5.9.amzn1.x86_64 - 2572 mingetty 0:00 1.4 MB Sleeping: 21:06 - 2575 mingetty 0:00 1.4 MB Sleeping: 21:06 - 2578 mingetty 0:00 1.5 MB Sleeping: 21:06 - 2580 mingetty 0:00 1.4 MB Sleeping: 21:06 - 2582 mingetty 0:00 1.4 MB Sleeping: 21:06 - 2584 mingetty 0:00 1.4 MB Sleeping: 21:06 -openssh-server-6.6.1p1-33.66.amzn1.x86_64 - 2481 sshd 0:00 2.6 MB Sleeping: 21:07 -python27-2.7.12-2.120.amzn1.x86_64 - 2649 yum 0:00 35 MB Running: 00:01 -rsyslog-5.8.10-9.26.amzn1.x86_64 - 2261 rsyslogd 0:00 2.6 MB Sleeping: 21:08 -udev-173-4.13.amzn1.x86_64 - 1528 udevd 0:00 2.5 MB Sleeping: 21:12 - 1652 udevd 0:00 2.1 MB Sleeping: 21:12 - 1653 udevd 0:00 2.0 MB Sleeping: 21:12 -upstart-0.6.5-13.3.13.amzn1.x86_64 - 1 init 0:00 2.5 MB Sleeping: 21:13 -util-linux-2.23.2-33.28.amzn1.x86_64 - 2569 agetty 0:00 1.6 MB Sleeping: 21:06 -yum-3.4.3-150.70.amzn1.noarch - 2649 yum 0:00 35 MB Running: 00:01 -`, - models.Packages{}, - }, - } - - for _, tt := range tests { - packages := r.parseYumPS(tt.in) - for name, ePack := range tt.out { - if !reflect.DeepEqual(ePack, packages[name]) { - e := pp.Sprintf("%v", ePack) - a := pp.Sprintf("%v", packages[name]) - t.Errorf("expected %s, actual %s", e, a) - } - } - } -} - func TestParseNeedsRestarting(t *testing.T) { r := newCentOS(config.ServerInfo{}) r.Distro = config.Distro{Family: "centos"} @@ -580,49 +357,3 @@ func TestParseNeedsRestarting(t *testing.T) { } } } - -func TestIsExecScanUsingYum(t *testing.T) { - r := newRHEL(config.ServerInfo{}) - var tests = []struct { - modes []byte - family string - out bool - }{ - { - modes: []byte{config.Offline}, - out: false, - }, - { - modes: []byte{}, - family: config.CentOS, - out: false, - }, - { - family: config.Amazon, - modes: []byte{config.FastRoot}, - out: true, - }, - { - family: config.Amazon, - modes: []byte{config.Deep}, - out: true, - }, - } - - for i, tt := range tests { - r.Distro.Family = tt.family - mode := config.ScanMode{} - for _, m := range tt.modes { - mode.Set(m) - } - - si := r.getServerInfo() - si.Mode = mode - r.setServerInfo(si) - - out := r.isExecScanUsingYum() - if out != tt.out { - t.Errorf("[%d] expected %#v, actual %#v", i, tt.out, out) - } - } -} diff --git a/scan/rhel.go b/scan/rhel.go index 29181ca8..9d1177cf 100644 --- a/scan/rhel.go +++ b/scan/rhel.go @@ -81,14 +81,14 @@ func (o *rhel) sudoNoPasswdCmdsFast() []cmd { } func (o *rhel) sudoNoPasswdCmdsFastRoot() []cmd { - if o.getServerInfo().Mode.IsOffline() { - return []cmd{} - } - - majorVersion, _ := o.Distro.MajorVersion() - if majorVersion < 6 { + if !o.ServerInfo.IsContainer() { return []cmd{ {"repoquery -h", exitStatusZero}, + {"needs-restarting", exitStatusZero}, + {"which which", exitStatusZero}, + {"stat /proc/1/exe", exitStatusZero}, + {"ls -l /proc/1/exe", exitStatusZero}, + {"cat /proc/1/maps", exitStatusZero}, } } return []cmd{ @@ -110,3 +110,7 @@ func (o rootPrivRHEL) repoquery() bool { func (o rootPrivRHEL) yumMakeCache() bool { return true } + +func (o rootPrivRHEL) yumPS() bool { + return true +} diff --git a/scan/utils.go b/scan/utils.go index 37f77fb9..7a8896c9 100644 --- a/scan/utils.go +++ b/scan/utils.go @@ -52,8 +52,25 @@ func isRunningKernel(pack models.Package, family string, kernel models.Kernel) ( } func rpmQa(distro config.Distro) string { - const old = "rpm -qa --queryformat \"%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n\"" - const new = "rpm -qa --queryformat \"%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n\"" + const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"` + const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"` + switch distro.Family { + case config.SUSEEnterpriseServer: + if v, _ := distro.MajorVersion(); v < 12 { + return old + } + return new + default: + if v, _ := distro.MajorVersion(); v < 6 { + return old + } + return new + } +} + +func rpmQf(distro config.Distro) string { + const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" ` + const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" ` switch distro.Family { case config.SUSEEnterpriseServer: if v, _ := distro.MajorVersion(); v < 12 {