Skip to content

Commit eb331a4

Browse files
committed
Detect processes affected by update using yum-ps
1 parent 6fa9f6d commit eb331a4

File tree

3 files changed

+277
-14
lines changed

3 files changed

+277
-14
lines changed

models/packages.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (ps Packages) FormatUpdatablePacksSummary() string {
7171
return fmt.Sprintf("%d updatable packages", nUpdatable)
7272
}
7373

74-
// FindOne search a element by name-newver-newrel-arch
74+
// FindOne search a element
7575
func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
7676
for key, p := range ps {
7777
if f(p) {
@@ -83,14 +83,15 @@ func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
8383

8484
// Package has installed packages.
8585
type Package struct {
86-
Name string
87-
Version string
88-
Release string
89-
NewVersion string
90-
NewRelease string
91-
Arch string
92-
Repository string
93-
Changelog Changelog
86+
Name string
87+
Version string
88+
Release string
89+
NewVersion string
90+
NewRelease string
91+
Arch string
92+
Repository string
93+
Changelog Changelog
94+
AffectedProcs []AffectedProc `json:",omitempty"`
9495
}
9596

9697
// FormatVer returns package version-release
@@ -151,3 +152,13 @@ type Changelog struct {
151152
Contents string
152153
Method DetectionMethod
153154
}
155+
156+
// AffectedProc keep a processes information affected by software update
157+
type AffectedProc struct {
158+
PID string
159+
ProcName string
160+
CPU string
161+
RSS string
162+
State string
163+
Uptime string
164+
}

scan/redhat.go

+85-5
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,16 @@ func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
402402

403403
func (o *redhat) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
404404
if config.Conf.Deep {
405-
//TODO Cache changelogs to bolt
405+
packs, err := o.yumPS()
406+
if err != nil {
407+
return nil, err
408+
}
409+
for name, pack := range packs {
410+
p := o.Packages[name]
411+
p.AffectedProcs = pack.AffectedProcs
412+
o.Packages[name] = p
413+
}
414+
406415
if err := o.fillChangelogs(updatable); err != nil {
407416
return nil, err
408417
}
@@ -521,11 +530,9 @@ func (o *redhat) fillDiffChangelogs(packNames []string) error {
521530
if index := strings.Index(p.NewVersion, ":"); 0 < index {
522531
epoch := p.NewVersion[0:index]
523532
ver := p.NewVersion[index+1 : len(p.NewVersion)]
524-
epochNameVerRel = fmt.Sprintf("%s:%s-%s",
525-
epoch, p.Name, ver)
533+
epochNameVerRel = fmt.Sprintf("%s:%s-%s", epoch, p.Name, ver)
526534
} else {
527-
epochNameVerRel = fmt.Sprintf("%s-%s",
528-
p.Name, p.NewVersion)
535+
epochNameVerRel = fmt.Sprintf("%s-%s", p.Name, p.NewVersion)
529536
}
530537
return strings.HasPrefix(s, epochNameVerRel)
531538
})
@@ -1038,3 +1045,76 @@ func (o *redhat) sudo() bool {
10381045
return config.Conf.Deep
10391046
}
10401047
}
1048+
1049+
func (o *redhat) yumPS() (models.Packages, error) {
1050+
cmd := "LANGUAGE=en_US.UTF-8 yum --color=never -q ps all"
1051+
r := o.exec(util.PrependProxyEnv(cmd), sudo)
1052+
if !r.isSuccess() {
1053+
return nil, fmt.Errorf("Failed to SSH: %s", r)
1054+
}
1055+
return o.parseYumPS(r.Stdout), nil
1056+
}
1057+
1058+
func (o *redhat) parseYumPS(stdout string) models.Packages {
1059+
packs := models.Packages{}
1060+
scanner := bufio.NewScanner(strings.NewReader(stdout))
1061+
isPackageLine, needToParseProcline := false, false
1062+
currentPackName := ""
1063+
for scanner.Scan() {
1064+
line := scanner.Text()
1065+
fields := strings.Fields(line)
1066+
if len(fields) == 0 ||
1067+
len(fields) == 1 && fields[0] == "ps" ||
1068+
len(fields) == 6 && fields[0] == "pid" {
1069+
continue
1070+
}
1071+
1072+
isPackageLine = !strings.HasPrefix(line, " ")
1073+
if isPackageLine {
1074+
if 1 < len(fields) && fields[1] == "Upgrade" {
1075+
needToParseProcline = true
1076+
1077+
// Search o.Packages to divide into name, version, release
1078+
name, pack, found := o.Packages.FindOne(func(p models.Package) bool {
1079+
var epochNameVerRel string
1080+
if index := strings.Index(p.Version, ":"); 0 < index {
1081+
epoch := p.Version[0:index]
1082+
ver := p.Version[index+1 : len(p.Version)]
1083+
epochNameVerRel = fmt.Sprintf("%s:%s-%s-%s.%s",
1084+
epoch, p.Name, ver, p.Release, p.Arch)
1085+
} else {
1086+
epochNameVerRel = fmt.Sprintf("%s-%s-%s.%s",
1087+
p.Name, p.Version, p.Release, p.Arch)
1088+
}
1089+
return strings.HasPrefix(fields[0], epochNameVerRel)
1090+
})
1091+
if !found {
1092+
o.log.Errorf("`yum ps` Package is not found: %s", line)
1093+
continue
1094+
}
1095+
packs[name] = pack
1096+
currentPackName = name
1097+
} else {
1098+
needToParseProcline = false
1099+
}
1100+
} else if needToParseProcline {
1101+
if 6 < len(fields) {
1102+
proc := models.AffectedProc{
1103+
PID: fields[0],
1104+
ProcName: fields[1],
1105+
CPU: fields[2],
1106+
RSS: fields[3] + " " + fields[4],
1107+
State: strings.TrimSuffix(fields[5], ":"),
1108+
Uptime: strings.Join(fields[6:len(fields)], " "),
1109+
}
1110+
pack := packs[currentPackName]
1111+
pack.AffectedProcs = append(pack.AffectedProcs, proc)
1112+
packs[currentPackName] = pack
1113+
} else {
1114+
o.log.Errorf("`yum ps` Unknown Format: %s", line)
1115+
continue
1116+
}
1117+
}
1118+
}
1119+
return packs
1120+
}

scan/redhat_test.go

+172
Original file line numberDiff line numberDiff line change
@@ -1297,3 +1297,175 @@ func TestDivideChangelogsIntoEachPackages(t *testing.T) {
12971297
}
12981298

12991299
}
1300+
1301+
func TestParseYumPS(t *testing.T) {
1302+
r := newRedhat(config.ServerInfo{})
1303+
r.Distro = config.Distro{Family: "centos"}
1304+
r.Packages = models.NewPackages(
1305+
models.Package{
1306+
Name: "python",
1307+
Version: "2.7.5",
1308+
Release: "34.el7",
1309+
Arch: "x86_64",
1310+
},
1311+
models.Package{
1312+
Name: "util-linux",
1313+
Version: "2.23.2",
1314+
Release: "26.el7",
1315+
Arch: "x86_64",
1316+
},
1317+
models.Package{
1318+
Name: "wpa_supplicant",
1319+
Version: "1:2.0",
1320+
Release: "17.el7_1",
1321+
Arch: "x86_64",
1322+
},
1323+
models.Package{
1324+
Name: "yum",
1325+
Version: "3.4.3",
1326+
Release: "150.el7.centos",
1327+
Arch: "noarch",
1328+
},
1329+
)
1330+
1331+
var tests = []struct {
1332+
in string
1333+
out models.Packages
1334+
}{
1335+
{
1336+
` pid proc CPU RSS State uptime
1337+
python-2.7.5-34.el7.x86_64 Upgrade 2.7.5-48.el7.x86_64
1338+
741 tuned 1:54 16 MB Sleeping: 14 day(s) 21:52:32
1339+
38755 yum 0:00 42 MB Running: 00:00
1340+
util-linux-2.23.2-26.el7.x86_64 Upgrade 2.23.2-33.el7_3.2.x86_64
1341+
626 agetty 0:00 848 kB Sleeping: 14 day(s) 21:52:37
1342+
628 agetty 0:00 848 kB Sleeping: 14 day(s) 21:52:37
1343+
1:wpa_supplicant-2.0-17.el7_1.x86_64 Upgrade 1:2.0-21.el7_3.x86_64
1344+
638 wpa_supplicant 0:00 2.6 MB Sleeping: 14 day(s) 21:52:37
1345+
yum-3.4.3-150.el7.centos.noarch
1346+
38755 yum 0:00 42 MB Running: 00:00
1347+
ps
1348+
`,
1349+
models.NewPackages(
1350+
models.Package{
1351+
Name: "python",
1352+
Version: "2.7.5",
1353+
Release: "34.el7",
1354+
Arch: "x86_64",
1355+
// NewVersion: "2.7.5-",
1356+
// NewRelease: "48.el7.x86_64",
1357+
AffectedProcs: []models.AffectedProc{
1358+
{
1359+
PID: "741",
1360+
ProcName: "tuned",
1361+
CPU: "1:54",
1362+
RSS: "16 MB",
1363+
State: "Sleeping",
1364+
Uptime: "14 day(s) 21:52:32",
1365+
},
1366+
{
1367+
PID: "38755",
1368+
ProcName: "yum",
1369+
CPU: "0:00",
1370+
RSS: "42 MB",
1371+
State: "Running",
1372+
Uptime: "00:00",
1373+
},
1374+
},
1375+
},
1376+
models.Package{
1377+
Name: "util-linux",
1378+
Version: "2.23.2",
1379+
Release: "26.el7",
1380+
Arch: "x86_64",
1381+
// NewVersion: "2.7.5",
1382+
// NewRelease: "48.el7.x86_64",
1383+
AffectedProcs: []models.AffectedProc{
1384+
{
1385+
PID: "626",
1386+
ProcName: "agetty",
1387+
CPU: "0:00",
1388+
RSS: "848 kB",
1389+
State: "Sleeping",
1390+
Uptime: "14 day(s) 21:52:37",
1391+
},
1392+
{
1393+
PID: "628",
1394+
ProcName: "agetty",
1395+
CPU: "0:00",
1396+
RSS: "848 kB",
1397+
State: "Sleeping",
1398+
Uptime: "14 day(s) 21:52:37",
1399+
},
1400+
},
1401+
},
1402+
models.Package{
1403+
Name: "wpa_supplicant",
1404+
Version: "1:2.0",
1405+
Release: "17.el7_1",
1406+
Arch: "x86_64",
1407+
// NewVersion: "1:2.0",
1408+
// NewRelease: "21.el7_3.x86_64",
1409+
AffectedProcs: []models.AffectedProc{
1410+
{
1411+
PID: "638",
1412+
ProcName: "wpa_supplicant",
1413+
CPU: "0:00",
1414+
RSS: "2.6 MB",
1415+
State: "Sleeping",
1416+
Uptime: "14 day(s) 21:52:37",
1417+
},
1418+
},
1419+
},
1420+
),
1421+
},
1422+
{
1423+
` pid proc CPU RSS State uptime
1424+
acpid-2.0.19-6.7.amzn1.x86_64
1425+
2388 acpid 0:00 1.4 MB Sleeping: 21:08
1426+
at-3.1.10-48.15.amzn1.x86_64
1427+
2546 atd 0:00 164 kB Sleeping: 21:06
1428+
cronie-anacron-1.4.4-15.8.amzn1.x86_64
1429+
2637 anacron 0:00 1.5 MB Sleeping: 13:14
1430+
12:dhclient-4.1.1-51.P1.26.amzn1.x86_64
1431+
2061 dhclient 0:00 1.4 MB Sleeping: 21:10
1432+
2193 dhclient 0:00 2.1 MB Sleeping: 21:08
1433+
mingetty-1.08-5.9.amzn1.x86_64
1434+
2572 mingetty 0:00 1.4 MB Sleeping: 21:06
1435+
2575 mingetty 0:00 1.4 MB Sleeping: 21:06
1436+
2578 mingetty 0:00 1.5 MB Sleeping: 21:06
1437+
2580 mingetty 0:00 1.4 MB Sleeping: 21:06
1438+
2582 mingetty 0:00 1.4 MB Sleeping: 21:06
1439+
2584 mingetty 0:00 1.4 MB Sleeping: 21:06
1440+
openssh-server-6.6.1p1-33.66.amzn1.x86_64
1441+
2481 sshd 0:00 2.6 MB Sleeping: 21:07
1442+
python27-2.7.12-2.120.amzn1.x86_64
1443+
2649 yum 0:00 35 MB Running: 00:01
1444+
rsyslog-5.8.10-9.26.amzn1.x86_64
1445+
2261 rsyslogd 0:00 2.6 MB Sleeping: 21:08
1446+
udev-173-4.13.amzn1.x86_64
1447+
1528 udevd 0:00 2.5 MB Sleeping: 21:12
1448+
1652 udevd 0:00 2.1 MB Sleeping: 21:12
1449+
1653 udevd 0:00 2.0 MB Sleeping: 21:12
1450+
upstart-0.6.5-13.3.13.amzn1.x86_64
1451+
1 init 0:00 2.5 MB Sleeping: 21:13
1452+
util-linux-2.23.2-33.28.amzn1.x86_64
1453+
2569 agetty 0:00 1.6 MB Sleeping: 21:06
1454+
yum-3.4.3-150.70.amzn1.noarch
1455+
2649 yum 0:00 35 MB Running: 00:01
1456+
`,
1457+
models.Packages{},
1458+
},
1459+
}
1460+
1461+
for _, tt := range tests {
1462+
packages := r.parseYumPS(tt.in)
1463+
for name, ePack := range tt.out {
1464+
if !reflect.DeepEqual(ePack, packages[name]) {
1465+
e := pp.Sprintf("%v", ePack)
1466+
a := pp.Sprintf("%v", packages[name])
1467+
t.Errorf("expected %s, actual %s", e, a)
1468+
}
1469+
}
1470+
}
1471+
}

0 commit comments

Comments
 (0)