Home | History | Annotate | Download | only in security_RootfsStatefulSymlinks
      1 # Copyright (c) 2011 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 logging
      8 import pwd
      9 import os
     10 import stat
     11 
     12 from autotest_lib.client.bin import test, utils
     13 from autotest_lib.client.common_lib import error
     14 
     15 
     16 class security_RootfsStatefulSymlinks(test.test):
     17     version = 1
     18     _BAD_DESTINATIONS = [
     19                          '*/var/*', '*/home/*', '*/stateful_partition/*',
     20                          '*/usr/local/*'
     21                         ]
     22 
     23     def load_baseline(self):
     24         bfile = open(os.path.join(self.bindir, 'baseline'))
     25         baseline = json.loads(bfile.read())
     26         bfile.close()
     27         return baseline
     28 
     29 
     30     def validate_attributes(self, link, expectations):
     31         """
     32         Given a symlink, validate that the file it points to
     33         matches all of the expected properties (owner, group, mode).
     34         Returns True if all expections are met, False otherwise.
     35         """
     36         destination = os.readlink(link)
     37         if destination != expectations['destination']:
     38             logging.error(
     39                 "Expected '%s' to point to '%s', but it points to '%s'",
     40                 link, expectations['destination'], destination)
     41             logging.error(utils.system_output("ls -ald '%s'" % destination))
     42             return False
     43 
     44         # By this point, we know it points to the right place, but we
     45         # need to determine if the destination exists (and, if not, if
     46         # that's permitted by "can_dangle": true in the baseline.
     47         if not os.path.exists(destination):
     48             logging.warning("'%s' points to '%s', but it's dangling",
     49                             link, destination)
     50             return expectations['can_dangle']
     51 
     52         # It exists, it's the right path, so check the permissions.
     53         s = os.stat(destination)
     54         owner = pwd.getpwuid(s.st_uid).pw_name
     55         group = grp.getgrgid(s.st_gid).gr_name
     56         mode = oct(stat.S_IMODE(s.st_mode))
     57         if (owner == expectations['owner'] and
     58             group == expectations['group'] and
     59             mode == expectations['mode']):
     60             return True
     61         else:
     62             logging.error("'%s': expected %s:%s %s, saw %s:%s %s",
     63                           destination, expectations['owner'],
     64                           expectations['group'], expectations['mode'],
     65                           owner, group, mode)
     66             return False
     67 
     68 
     69     def run_once(self):
     70         """
     71         Find any symlinks that point from the rootfs into
     72         "bad destinations" (e.g., stateful partition). Validate
     73         that any approved cases meet with all expectations, and
     74         that there are no unexpected additional such links found.
     75         """
     76         baseline = self.load_baseline()
     77         test_pass = True
     78 
     79         clauses = ["-lname '%s'" % i for i in self._BAD_DESTINATIONS]
     80         cmd = 'find / -xdev %s' % ' -o '.join(clauses)
     81         cmd_output = utils.system_output(cmd, ignore_status=True)
     82 
     83         links_seen = set(cmd_output.splitlines())
     84         for link in links_seen:
     85             # Check if this link is in the baseline. If not, test fails.
     86             if not link in baseline:
     87                 logging.error("No baseline entry for '%s'", link)
     88                 logging.error(utils.system_output("ls -ald '%s'" % link))
     89                 test_pass = False
     90                 continue
     91             # If it is, proceed to validate other attributes (where it points,
     92             # permissions of what it points to, etc).
     93             file_pass = self.validate_attributes(link, baseline[link])
     94             test_pass = test_pass and file_pass
     95 
     96         # The above will have flagged any links for which we had no baseline.
     97         # Warn (but do not trigger failure) when we have baseline entries
     98         # which we did not find on the system.
     99         expected_set = set(baseline.keys())
    100         diff = expected_set.difference(links_seen)
    101         if diff:
    102             logging.warning("Warning, possible stale baseline entries:")
    103             for d in diff:
    104                 logging.warning(d)
    105 
    106         if not test_pass:
    107             raise error.TestFail("Baseline mismatch")
    108 
    109