blob: 65982488f5621e36729d672a2082752aafb449e7 [file] [log] [blame]
Jim Hebert2e04d942012-05-18 11:46:02 -07001# Copyright (c) 2012 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
7import re
8import stat
9import subprocess
10
Jorge Lucangeli Obes3bd4e232013-11-25 11:36:08 -080011from autotest_lib.client.bin import test
Jim Hebert2e04d942012-05-18 11:46:02 -070012from autotest_lib.client.common_lib import error
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080013from autotest_lib.client.cros import asan
Jim Hebert2e04d942012-05-18 11:46:02 -070014
15class security_OpenFDs(test.test):
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080016 """Checks a number of sensitive processes on Chrome OS for unexpected open
17 file descriptors.
18 """
19
Jim Hebert2e04d942012-05-18 11:46:02 -070020 version = 1
21
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080022 @staticmethod
23 def _S_ISANONFD(mode):
Jim Hebert64ed5cc2012-11-06 14:08:43 -080024 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080025 Returns whether |mode| represents an "anonymous inode" file descriptor.
26 Python does not expose a type-checking macro for anonymous inode fds.
27 Implements the same interface as stat.S_ISREG(x).
Jim Hebert64ed5cc2012-11-06 14:08:43 -080028
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080029 @param mode: mode bits, usually from 'stat(path).st_mode'
Jim Hebert64ed5cc2012-11-06 14:08:43 -080030 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080031 return 0 == (mode & 0770000)
Jorge Lucangeli Obes3ceb6592014-12-09 09:52:00 -080032
33
Jim Hebert64ed5cc2012-11-06 14:08:43 -080034 def get_fds(self, pid, typechecker):
Jim Hebert2e04d942012-05-18 11:46:02 -070035 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080036 Returns the set of open file descriptors for |pid|.
37 Each open fd is represented as 'mode path', e.g.: '0500 /dev/random'.
38
39 @param pid: pid of process
40 @param typechecker: callback restricting allowed fd types
Jim Hebert2e04d942012-05-18 11:46:02 -070041 """
42 proc_fd_dir = os.path.join('/proc/', pid, 'fd')
43 fd_perms = set([])
44 for link in os.listdir(proc_fd_dir):
45 link_path = os.path.join(proc_fd_dir, link)
46 target = os.readlink(link_path)
Jim Hebert64ed5cc2012-11-06 14:08:43 -080047 # The "mode" on the link tells us if the file is
48 # opened for read/write. We are more interested
49 # in that than the permissions of the file on the fs.
50 link_st_mode = os.lstat(link_path).st_mode
51 # On the other hand, we need the type information
52 # off the real file, otherwise we're going to get
53 # S_ISLNK for everything.
54 real_st_mode = os.stat(link_path).st_mode
55 if not typechecker(real_st_mode):
56 raise error.TestFail('Pid %s has fd for %s, disallowed type' %
57 (pid, target))
58 mode = stat.S_IMODE(link_st_mode)
Jim Hebert2e04d942012-05-18 11:46:02 -070059 fd_perms.add('%s %s' % (oct(mode), target))
60 return fd_perms
61
62
63 def snapshot_system(self):
64 """
65 Dumps a systemwide snapshot of open-fd and process table
66 information into the results directory, to assist with any
67 triage/debug later.
68 """
69 subprocess.call('ps -ef > "%s/ps-ef.txt"' % self.resultsdir,
70 shell=True)
71 subprocess.call('ls -l /proc/*[0-9]*/fd > "%s/proc-fd.txt"' %
72 self.resultsdir, shell=True)
73
74
75 def apply_filters(self, fds, filters):
76 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080077 Removes every item in |fds| matching any of the regexes in |filters|.
Jim Hebert2e04d942012-05-18 11:46:02 -070078 This modifies the set in-place, and returns a list containing
79 any regexes which did not match anything.
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080080
81 @param fds: set of 'mode path' strings representing open fds
82 @param filters: list of regexes to filter open fds with
Jim Hebert2e04d942012-05-18 11:46:02 -070083 """
84 failed_filters = set()
85 for filter_re in filters:
86 expected_fds = set([fd_perm for fd_perm in fds
87 if re.match(filter_re, fd_perm)])
88 if not expected_fds:
89 failed_filters.add(filter_re)
90 fds -= expected_fds
91 return failed_filters
92
93
94 def find_pids(self, process, arg_regex):
95 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -080096 Finds all pids for |process| whose command line arguments
97 match |arg_regex|. Returns a list of pids.
98
99 @param process: process name
100 @param arg_regex: regex to match command line arguments
Jim Hebert2e04d942012-05-18 11:46:02 -0700101 """
102 p1 = subprocess.Popen(['ps', '-C', process, '-o', 'pid,command'],
103 stdout=subprocess.PIPE)
Jim Hebertb34a0602012-07-03 13:55:21 -0700104 # We're adding '--ignored= --type=renderer' to the GPU process cmdline
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800105 # to fix crbug.com/129884.
Jim Hebertb34a0602012-07-03 13:55:21 -0700106 # This process has different characteristics, so we need to avoid
107 # finding it when we find --type=renderer tests processes.
108 p2 = subprocess.Popen(['grep', '-v', '--',
109 '--ignored=.*%s' % arg_regex],
110 stdin=p1.stdout, stdout=subprocess.PIPE)
111 p3 = subprocess.Popen(['grep', arg_regex], stdin=p2.stdout,
Jim Hebert2e04d942012-05-18 11:46:02 -0700112 stdout=subprocess.PIPE)
Jim Hebertb34a0602012-07-03 13:55:21 -0700113 p4 = subprocess.Popen(['awk', '{print $1}'], stdin=p3.stdout,
Jim Hebert2e04d942012-05-18 11:46:02 -0700114 stdout=subprocess.PIPE)
Jim Hebertb34a0602012-07-03 13:55:21 -0700115 output = p4.communicate()[0]
Jim Hebert2e04d942012-05-18 11:46:02 -0700116 return output.splitlines()
117
118
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800119 def check_process(self, process, args, filters, typechecker):
Jim Hebert2e04d942012-05-18 11:46:02 -0700120 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800121 Checks a process for unexpected open file descriptors:
122 * Identifies all instances (pids) of |process|.
123 * Identifies all file descriptors open by those pids.
124 * Reports any fds not accounted for by regexes in |filters|.
125 * Reports any filters which fail to match any open fds.
126
127 If there were any fds unaccounted for, or failed filters,
128 mark the test failed.
129
130 @param process: process name
131 @param args: regex to match command line arguments
132 @param filters: list of regexes to filter open fds with
133 @param typechecker: callback restricting allowed fd types
Jim Hebert2e04d942012-05-18 11:46:02 -0700134 """
Jorge Lucangeli Obes3bd4e232013-11-25 11:36:08 -0800135 logging.debug('Checking %s %s', process, args)
Jim Hebert2e04d942012-05-18 11:46:02 -0700136 test_pass = True
137 for pid in self.find_pids(process, args):
Jorge Lucangeli Obes3bd4e232013-11-25 11:36:08 -0800138 logging.debug('Found pid %s for %s', pid, process)
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800139 fds = self.get_fds(pid, typechecker)
Jim Hebert2e04d942012-05-18 11:46:02 -0700140 failed_filters = self.apply_filters(fds, filters)
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800141 # Log failed filters to allow pruning the list.
Jim Hebert2e04d942012-05-18 11:46:02 -0700142 if failed_filters:
Jorge Lucangeli Obes3bd4e232013-11-25 11:36:08 -0800143 logging.error('Some filter(s) failed to match any fds: %s',
Jim Hebert2e04d942012-05-18 11:46:02 -0700144 repr(failed_filters))
Jim Hebert2e04d942012-05-18 11:46:02 -0700145 if fds:
Jorge Lucangeli Obes3bd4e232013-11-25 11:36:08 -0800146 logging.error('Found unexpected fds in %s %s: %s',
147 process, args, repr(fds))
Jim Hebert2e04d942012-05-18 11:46:02 -0700148 test_pass = False
149 return test_pass
150
151
152 def run_once(self):
153 """
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800154 Checks a number of sensitive processes on Chrome OS for
155 unexpected open file descriptors.
Jim Hebert2e04d942012-05-18 11:46:02 -0700156 """
157 self.snapshot_system()
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800158
Jim Hebert2e04d942012-05-18 11:46:02 -0700159 passes = []
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800160 filters = [r'0700 anon_inode:\[event.*\]',
Jim Hebert2e04d942012-05-18 11:46:02 -0700161 r'0[35]00 pipe:.*',
162 r'0[57]00 socket:.*',
Jim Hebert54876d42012-12-06 16:53:36 -0800163 r'0500 /dev/null',
Jim Hebert2e04d942012-05-18 11:46:02 -0700164 r'0[57]00 /dev/urandom',
165 r'0300 /var/log/chrome/chrome_.*',
Jim Hebert54876d42012-12-06 16:53:36 -0800166 r'0[37]00 /var/log/ui/ui.*',
Jim Hebert2e04d942012-05-18 11:46:02 -0700167 ]
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800168
169 # Whitelist fd-type check, suitable for Chrome processes.
170 # Notably, this omits S_ISDIR.
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800171 allowed_fd_type_check = lambda x: (stat.S_ISREG(x) or
172 stat.S_ISCHR(x) or
173 stat.S_ISSOCK(x) or
174 stat.S_ISFIFO(x) or
175 security_OpenFDs._S_ISANONFD(x))
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800176
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800177 # TODO(jorgelo): revisit this and potentially remove.
178 if asan.running_on_asan():
Jorge Lucangeli Obes3ceb6592014-12-09 09:52:00 -0800179 # On ASan, allow all fd types and opening /proc
180 logging.info("Running on ASan, allowing /proc")
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800181 allowed_fd_type_check = lambda x: True
Jorge Lucangeli Obes3ceb6592014-12-09 09:52:00 -0800182 filters.append(r'0500 /proc')
183
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800184 passes.append(self.check_process('chrome', 'type=plugin', filters,
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800185 allowed_fd_type_check))
Jim Hebert2e04d942012-05-18 11:46:02 -0700186
Jorge Lucangeli Obes3bd4e232013-11-25 11:36:08 -0800187 filters.extend([r'0[57]00 /dev/shm/..*',
Jim Hebert23a63b02012-08-09 18:06:28 -0700188 r'0500 /opt/google/chrome/.*.pak',
Jorge Lucangeli Obes8c7b99e2014-02-14 14:29:09 -0800189 r'0500 /opt/google/chrome/icudtl.dat',
Jorge Lucangeli Obes24178b12015-04-08 20:22:12 -0700190 # These used to be bundled with the Chrome binary.
191 # See crbug.com/475170.
192 r'0500 /opt/google/chrome/natives_blob.bin',
193 r'0500 /opt/google/chrome/snapshot_blob.bin',
Jorge Lucangeli Obes09dd0452015-09-30 16:42:06 -0700194 # Font files can be kept open in renderers
195 # for performance reasons.
196 # See crbug.com/452227.
197 r'0500 /usr/share/fonts/.*',
Ilja H. Friedelfe14f582016-06-20 15:35:39 -0700198 # Zero-copy texture uploads. crbug.com/607632.
Ilja H. Friedel37aee5e2016-06-17 17:40:33 -0700199 r'0700 anon_inode:dmabuf',
Jim Hebert2e04d942012-05-18 11:46:02 -0700200 ])
Jorge Lucangeli Obes741fb662015-10-20 22:08:11 -0700201 try:
Michael Spang0c187b52015-10-21 14:16:16 -0400202 # Renderers have access to DRM vgem device for graphics tile upload.
203 # See crbug.com/537474.
Jorge Lucangeli Obes741fb662015-10-20 22:08:11 -0700204 filters.append(r'0700 /dev/dri/%s' % os.readlink('/dev/dri/vgem'))
205 except OSError:
206 # /dev/dri/vgem doesn't exist.
207 pass
208
Jim Hebert64ed5cc2012-11-06 14:08:43 -0800209 passes.append(self.check_process('chrome', 'type=renderer', filters,
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800210 allowed_fd_type_check))
Jim Hebert2e04d942012-05-18 11:46:02 -0700211
212 if False in passes:
Jorge Lucangeli Obese84e1542014-12-08 17:53:09 -0800213 raise error.TestFail("Unexpected open file descriptors.")