Home | History | Annotate | Download | only in security_RuntimeExecStack
      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 
      5 from autotest_lib.client.bin import test
      6 from autotest_lib.client.common_lib import error
      7 
      8 import logging
      9 import os
     10 import errno
     11 
     12 class 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