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
5import logging
6import os
7
8from autotest_lib.client.bin import test, utils
9from autotest_lib.client.common_lib import error
10import os.path
11
12class security_ReservedPrivileges(test.test):
13    version = 1
14
15    def reserved_commands(self, command_list):
16        process_list = []
17        for line in command_list:
18            items = line.split()
19            # We don't care about defunct processes for purposes of this test,
20            # so skip to the next process if we encounter one.
21            if '<defunct>' in items:
22                continue
23
24            # There are n items in the list.  The first is the command, all of
25            # the remaining are either the different users or groups.  They
26            # must all match, if they don't we collect it.
27            matches = [i for i,owners in enumerate(items) if owners == items[1]]
28            # We do < because some processes have the same name as their owner.
29            # in that case the number of items will equal the number of matches
30            if (len(matches) < (len(items) - 1)):
31                process_list.append(items[0])
32        return set(process_list)
33
34
35    def load_baseline(self, bltype):
36        # Figure out path to baseline file, by looking up our own path
37        path = os.path.abspath(__file__)
38        path = os.path.join(os.path.dirname(path), 'baseline.%s' % bltype)
39        if (os.path.isfile(path) == False):
40            return set([])
41        baseline_file = open(path)
42        baseline_data = baseline_file.read()
43        baseline_set = set(baseline_data.splitlines())
44        baseline_file.close()
45        return baseline_set
46
47
48    def run_once(self, owner_type='user'):
49        """
50        Do a find on the system for commands with reserved privileges and
51        compare against baseline.  Fail if these do not match.
52        """
53
54        # Find the max column width needed to represent user and group names
55        # in ps outoupt.
56        usermax = utils.system_output("cut -d: -f1 /etc/passwd | wc -L",
57                                      ignore_status=True)
58        usermax = max(int(usermax), 8)
59
60        groupmax = utils.system_output("cut -d: -f1 /etc/group | wc -L",
61                                       ignore_status=True)
62        groupmax = max(int(groupmax), 8)
63
64        if (owner_type == 'user'):
65            command = ('ps --no-headers -eo '\
66                       'comm:16,euser:%d,ruser:%d,suser:%d,fuser:%d' %
67                       (usermax, usermax, usermax, usermax))
68        else:
69            command = ('ps --no-headers -eo comm:16,rgroup:%d,group:%d' %
70                       (groupmax, groupmax))
71
72        command_output = utils.system_output(command, ignore_status=True)
73        output_lines = command_output.splitlines()
74
75        dump_file = open(os.path.join(self.resultsdir, "ps_output"), 'w')
76        for line in output_lines:
77            dump_file.write(line.strip() + "\n")
78
79        dump_file.close()
80
81        observed_set = self.reserved_commands(output_lines)
82        baseline_set = self.load_baseline(owner_type)
83
84        # If something in the observed set is not
85        # covered by the baseline...
86        diff = observed_set.difference(baseline_set)
87        if len(diff) > 0:
88            for command in diff:
89                logging.error('Unexpected command: %s' % command)
90
91        # Or, things in baseline are missing from the system:
92        diff2 = baseline_set.difference(observed_set)
93        if len(diff2) > 0:
94            for command in diff2:
95                logging.error('Missing command: %s' % command)
96
97        if (len(diff) + len(diff2)) > 0:
98            raise error.TestFail('Baseline mismatch')
99