| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import grp |
| import json |
| import logging |
| import pwd |
| import os |
| import stat |
| |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| |
| |
| class security_RootfsStatefulSymlinks(test.test): |
| version = 1 |
| _BAD_DESTINATIONS = [ |
| '*/var/*', '*/home/*', '*/stateful_partition/*', |
| '*/usr/local/*' |
| ] |
| |
| def load_baseline(self): |
| bfile = open(os.path.join(self.bindir, 'baseline')) |
| baseline = json.loads(bfile.read()) |
| bfile.close() |
| return baseline |
| |
| |
| def validate_attributes(self, link, expectations): |
| """ |
| Given a symlink, validate that the file it points to |
| matches all of the expected properties (owner, group, mode). |
| Returns True if all expections are met, False otherwise. |
| """ |
| destination = os.readlink(link) |
| if destination != expectations['destination']: |
| logging.error("Expected %s to point to %s, but it points to %s" % |
| (link, expectations['destination'], destination)) |
| logging.error(utils.system_output("ls -ald '%s'" % destination)) |
| return False |
| |
| # By this point, we know it points to the right place, but we |
| # need to determine if the destination exists (and, if not, if |
| # that's permitted by "can_dangle": true in the baseline. |
| if not os.path.exists(destination): |
| return expectations['can_dangle'] |
| |
| # It exists, it's the right path, so check the permissions. |
| s = os.stat(destination) |
| owner = pwd.getpwuid(s.st_uid).pw_name |
| group = grp.getgrgid(s.st_gid).gr_name |
| mode = oct(stat.S_IMODE(s.st_mode)) |
| if (owner == expectations['owner'] and |
| group == expectations['group'] and |
| mode == expectations['mode']): |
| return True |
| else: |
| logging.error("%s: Expected %s:%s %s; Saw %s:%s %s" % |
| (destination, expectations['owner'], |
| expectations['group'], expectations['mode'], |
| owner, group, mode)) |
| return False |
| |
| |
| def run_once(self): |
| """ |
| Find any symlinks that point from the rootfs into |
| "bad destinations" (e.g., stateful partition). Validate |
| that any approved cases meet with all expectations, and |
| that there are no unexpected additional such links found. |
| """ |
| baseline = self.load_baseline() |
| test_pass = True |
| |
| clauses = ["-lname '%s'" % i for i in self._BAD_DESTINATIONS] |
| cmd = 'find / -xdev %s' % ' -o '.join(clauses) |
| cmd_output = utils.system_output(cmd, ignore_status=True) |
| |
| links_seen = set(cmd_output.splitlines()) |
| for link in links_seen: |
| # Check if this link is in the baseline. If not, test fails. |
| if not link in baseline: |
| logging.error("No baseline entry for %s" % link) |
| logging.error(utils.system_output("ls -ald '%s'" % link)) |
| test_pass = False |
| continue |
| # If it is, proceed to validate other attributes (where it points, |
| # permissions of what it points to, etc). |
| file_pass = self.validate_attributes(link, baseline[link]) |
| test_pass = test_pass and file_pass |
| |
| # The above will have flagged any links for which we had no baseline. |
| # Warn (but do not trigger failure) when we have baseline entries |
| # which we did not find on the system. |
| expected_set = set(baseline.keys()) |
| diff = expected_set.difference(links_seen) |
| if diff: |
| logging.warning("Warning, possible stale baseline entries:") |
| for d in diff: |
| logging.warning(d) |
| |
| if not test_pass: |
| raise error.TestFail('Baseline mismatch') |
| |