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
10
11class security_SuidBinaries(test.test):
12    """
13    Make sure no surprise binaries become setuid, setgid, or gain filesystem
14    capabilities without autotest noticing.
15    """
16    version = 1
17
18    def _load_baseline_file(self, basename):
19        """Load the list of expected files from a given file name.
20
21        @param basename the basename of the file to load.
22        @returns a set containing the names of the files listed in the baseline
23        file.
24        """
25        path = os.path.join(self.bindir, basename)
26        if os.path.exists(path):
27            with open(path) as basefile:
28                return set(l.strip() for l in basefile if l.strip()[0] != '#')
29        return set()
30
31
32    def _load_baseline(self, bltype):
33        """Load the list of expected files for a given baseline type.
34
35        @param bltype the baseline to load.
36        @returns a set containing the names of the files in the board's
37        baseline.
38        """
39        # Baseline common to all boards.
40        blname = 'baseline.' + bltype
41        blset = self._load_baseline_file(blname)
42        # Board-specific baseline.
43        board_blname = 'baseline.%s.%s' % (utils.get_current_board(), bltype)
44        blset |= self._load_baseline_file(board_blname)
45        return blset
46
47
48    def run_once(self, baseline='suid'):
49        """
50        Do a find on the system for setuid binaries, compare against baseline.
51        Fail if setuid binaries are found on the system but not on the baseline.
52        """
53        exclude = [ '/proc',
54                    '/dev',
55                    '/sys',
56                    '/run',
57                    '/usr/local',
58                    '/mnt/stateful_partition',
59                  ]
60        cmd = 'find / '
61        for item in exclude:
62            cmd += '-wholename %s -prune -o ' % (item)
63        cmd += '-type f '
64
65        permmask = {'suid': '4000', 'sgid': '2000'}
66
67        if baseline in permmask:
68            cmd += '-a -perm /%s -print' % (permmask[baseline])
69        elif baseline == 'fscap':
70            cmd += '-exec getcap {} +'
71        else:
72            raise error.TestFail("Unknown baseline '%s'!" % (baseline))
73
74        cmd_output = utils.system_output(cmd, ignore_status=True)
75        observed_set = set(cmd_output.splitlines())
76        baseline_set = self._load_baseline(baseline)
77
78        # Report observed set for debugging.
79        for line in observed_set:
80            logging.debug('%s: %s', baseline, line)
81
82        # Fail if we find new binaries.
83        new = observed_set.difference(baseline_set)
84        if len(new) > 0:
85            message = 'New %s binaries: %s' % (baseline, ', '.join(new))
86            raise error.TestFail(message)
87
88        # Log but not fail if we find missing binaries.
89        missing = baseline_set.difference(observed_set)
90        if len(missing) > 0:
91            for filepath in missing:
92                logging.error('Missing %s binary: %s', baseline, filepath)
93        else:
94            logging.debug('OK: %s baseline matches system', baseline)
95