1# 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 5from autotest_lib.client.bin import test 6from autotest_lib.client.common_lib import error 7 8import logging 9import os 10import errno 11 12class security_RuntimeExecStack(test.test): 13 """Tests that processes have non-executable stacks 14 15 Examines the /proc/$pid/maps file of all running processes for the 16 stack segments' markings. If "x" is found, it fails. 17 """ 18 version = 1 19 20 def check_no_exec_stack(self, maps): 21 """Reads process memory map and checks there are no executable stacks. 22 23 Args: 24 @param maps: opened /proc/<pid>/maps file 25 26 Returns: 27 A tuple containing the error code and a string (usually a single line) 28 with debug information. Error code could be: 29 0: ok: stack not executable (second element will be None) 30 1: error: stack is executable 31 2: error: stack is not writable 32 3: error: stack not found 33 """ 34 contents = '' 35 stack_count = 0 36 for line in maps: 37 line = line.strip() 38 contents += line + '\n' 39 40 if '[stack' not in line: 41 continue 42 stack_count += 1 43 44 perms = line.split(' ', 2)[1] 45 46 # Stack segment is executable. 47 if 'x' in perms: 48 return 1, line 49 50 # Sanity check we have stack segment perms. 51 if not 'w' in perms: 52 return 2, line 53 54 if stack_count > 0: 55 # Stack segments are non-executable. 56 return 0, None 57 else: 58 # Should be impossible: no stack segment seen. 59 return 3, contents 60 61 def run_once(self): 62 failed = set([]) 63 64 for pid in os.listdir('/proc'): 65 maps_path = '/proc/%s/maps' % (pid) 66 # Is this a pid directory? 67 if not os.path.exists(maps_path): 68 continue 69 # Is this a kernel thread? 70 try: 71 os.readlink('/proc/%s/exe' % (pid)) 72 except OSError, e: 73 if e.errno == errno.ENOENT: 74 continue 75 try: 76 maps = open(maps_path) 77 cmd = open('/proc/%s/cmdline' % (pid)).read() 78 except IOError: 79 # Allow the path to vanish out from under us. If 80 # we've failed for any other reason, raise the failure. 81 if os.path.exists(maps_path): 82 raise 83 logging.debug('ignored: pid %s vanished', pid) 84 continue 85 86 # Clean up cmdline for reporting. 87 cmd = cmd.replace('\x00', ' ') 88 exe = cmd 89 if ' ' in exe: 90 exe = exe[:exe.index(' ')] 91 92 # Check the stack segment. 93 stack, report = self.check_no_exec_stack(maps) 94 95 # Report outcome. 96 if stack == 0: 97 logging.debug('ok: %s %s', pid, exe) 98 else: 99 logging.info('FAIL: %s %s %s', pid, cmd, report) 100 failed.add(exe) 101 102 if len(failed) != 0: 103 msg = 'Bad stacks segments: %s' % (', '.join(failed)) 104 raise error.TestFail(msg) 105