OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import grp |
| 6 import json |
| 7 import os |
| 8 import pwd |
| 9 import re |
| 10 import string |
| 11 import time |
| 12 |
| 13 from autotest_lib.client.bin import site_login, site_ui_test, utils |
| 14 from autotest_lib.client.common_lib import error |
| 15 |
| 16 class platform_ProcessPrivilegesComprehensive(site_ui_test.UITest): |
| 17 """ |
| 18 Builds a process list (without spawning 'ps'), and validates |
| 19 the list against a baseline of expected processes, their priviliges, |
| 20 how many we expect to find, etc. |
| 21 """ |
| 22 version = 1 |
| 23 baseline = None |
| 24 strict = True |
| 25 |
| 26 def load_baseline(self): |
| 27 # Figure out path to baseline file, by looking up our own path |
| 28 bpath = os.path.abspath(__file__) |
| 29 bpath = os.path.join(os.path.dirname(bpath), 'baseline') |
| 30 bfile = open(bpath) |
| 31 self.baseline = json.loads(bfile.read()) |
| 32 bfile.close() |
| 33 # Initialize the 'seen' counter here, makes code below easier |
| 34 for user in self.baseline.keys(): |
| 35 for prog in self.baseline[user].keys(): |
| 36 self.baseline[user][prog]['seen'] = 0 |
| 37 |
| 38 |
| 39 def get_procentry(self, pid): |
| 40 """Gathers info about one process, given its PID""" |
| 41 pid_status_file = open(os.path.join('/proc', pid, 'status')) |
| 42 procentry = {} |
| 43 # pull Name, Uids, and Guids out of the status output |
| 44 for line in pid_status_file: |
| 45 fields = re.split('\s+',line) |
| 46 if fields[0] == 'Name:': |
| 47 procentry['name'] = fields[1] |
| 48 elif fields[0] == 'Uid:' or fields[0] == 'Gid:': |
| 49 # Add dictionary items like ruid, rgid, euid, egid, etc |
| 50 # Prefer to save uname ('root') but will save uid ('123') |
| 51 # if no uname can be found for that id. |
| 52 ug = fields[0][0].lower() # 'u' or 'g' |
| 53 for i in range(1,4): |
| 54 try: |
| 55 if ug == 'u': |
| 56 fields[i] = pwd.getpwuid(int(fields[i]))[0] |
| 57 else: |
| 58 fields[i] = grp.getgrgid(int(fields[i]))[0] |
| 59 except KeyError: |
| 60 # couldn't find name. We'll save bare id# instead. |
| 61 pass |
| 62 |
| 63 procentry['r%sid' % ug] = fields[1] |
| 64 procentry['e%sid' % ug] = fields[2] |
| 65 procentry['s%sid' % ug] = fields[3] |
| 66 |
| 67 pid_status_file.close() |
| 68 return procentry |
| 69 |
| 70 |
| 71 def procwalk(self): |
| 72 """Gathers info about every process on the system""" |
| 73 for pid in os.listdir('/proc'): |
| 74 if not pid.isdigit(): |
| 75 continue |
| 76 |
| 77 # There can be a race where after we listdir(), a process |
| 78 # exits. In that case get_procentry will throw an IOError |
| 79 # becase /prod/NNNN won't exist. |
| 80 # In those cases, skip to the next go-round of our loop. |
| 81 try: |
| 82 procentry = self.get_procentry(pid) |
| 83 except IOError: |
| 84 continue |
| 85 procname = procentry['name'] |
| 86 procuid = procentry['euid'] |
| 87 |
| 88 # The baseline might not contain a section for this uid |
| 89 if not procuid in self.baseline: |
| 90 self.baseline[procuid] = {} |
| 91 |
| 92 # For processes not explicitly mentioned in the baseline, |
| 93 # our implicit rule depends on how strict we want our checking. |
| 94 # In strict mode, it is an implicit "max: 0" rule (default deny) |
| 95 # In non-strict mode, it is an implicit "min: 0" (default allow) |
| 96 if not procname in self.baseline[procuid]: |
| 97 if self.strict: |
| 98 self.baseline[procuid][procname] = {'max': 0} |
| 99 else: |
| 100 self.baseline[procuid][procname] = {'min': 0} |
| 101 |
| 102 # Initialize/increment a count of how many times we see |
| 103 # this process (e.g. we may expect a min of 4 and a max of 8 |
| 104 # of some certain process, so 'seen' is not a boolean). |
| 105 if not 'seen' in self.baseline[procuid][procname]: |
| 106 self.baseline[procuid][procname]['seen'] = 0 |
| 107 self.baseline[procuid][procname]['seen'] += 1 |
| 108 |
| 109 |
| 110 def report(self): |
| 111 """Return a list of problems identified during procwalk""" |
| 112 problems = [] |
| 113 for user in self.baseline.keys(): |
| 114 for prog in self.baseline[user].keys(): |
| 115 # If there's a min, we may not have met it |
| 116 # If there's a max, we may have exceeded it |
| 117 if 'min' in self.baseline[user][prog]: |
| 118 if (self.baseline[user][prog]['seen'] < |
| 119 self.baseline[user][prog]['min']): |
| 120 p = ('%s (run as %s): expected at least %s processes,' |
| 121 ' saw only %s') |
| 122 p = p % (prog, user, self.baseline[user][prog]['min'], |
| 123 self.baseline[user][prog]['seen']) |
| 124 problems.append(p) |
| 125 |
| 126 if 'max' in self.baseline[user][prog]: |
| 127 if (self.baseline[user][prog]['seen'] > |
| 128 self.baseline[user][prog]['max']): |
| 129 p = ('%s (run as %s): expected at most %s processes,' |
| 130 ' saw %s') |
| 131 p = p % (prog, user, self.baseline[user][prog]['max'], |
| 132 self.baseline[user][prog]['seen']) |
| 133 problems.append(p) |
| 134 problems.sort() |
| 135 return problems |
| 136 |
| 137 |
| 138 def run_once(self): |
| 139 self.load_baseline() |
| 140 self.procwalk() |
| 141 problems = self.report() |
| 142 |
| 143 if (len(problems) != 0): |
| 144 raise error.TestFail( |
| 145 'Process list had %s mis-matches with baseline: %s%s' % |
| 146 (len(problems), string.join(problems, '. '), |
| 147 '(END)')) |
OLD | NEW |