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