| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging, os |
| |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server import test |
| |
| class security_kASLR(test.test): |
| """Tests the kASLR entropy seen across many reboots of a device |
| """ |
| version = 1 |
| target_symbol = 'sys_exit' |
| reboot_count = 100 |
| reboot_timeout = 60 |
| |
| def _reboot_machine(self): |
| """Reboot the client machine. |
| |
| We'll wait until the client is down, then up again. |
| """ |
| boot_id = self._client.get_boot_id() |
| self._client.run('reboot &') |
| self._client.wait_for_restart(old_boot_id=boot_id, |
| timeout=self.reboot_timeout, |
| down_timeout=self.reboot_timeout, |
| down_warning=self.reboot_timeout) |
| |
| def _read_kallsyms(self, filename): |
| """Fetch /proc/kallsyms from client and return lines in the file |
| |
| @param filename: The file to write 'cat /proc/kallsyms' into. |
| """ |
| |
| f = open(filename, 'w') |
| self._client.run('cat /proc/kallsyms', stdout_tee=f) |
| f.close() |
| |
| return utils.read_file(filename) |
| |
| def _parse_kallsyms(self, kallsyms): |
| """ Parse the contents of each line of kallsyms, extracting |
| symbol addresses into a returned hash. |
| |
| @param kallsyms: string of kallsyms contents |
| """ |
| symbols = {} |
| for line in kallsyms.splitlines(): |
| addr, symtype, symbol = line.strip().split(' ', 2) |
| # Just keep regular text symbols for now. |
| if symtype != "T": |
| continue |
| symbols.setdefault(symbol, addr) |
| return symbols |
| |
| def run_once(self, host=None): |
| """Run the test. |
| |
| @param host: The client machine to connect to; should be a Host object. |
| """ |
| assert host is not None, "The host must be specified." |
| |
| self._client = host |
| |
| # Report client configuration, to help debug any problems. |
| kernel_ver = self._client.run('uname -r').stdout.rstrip() |
| arch = utils.get_arch(self._client.run) |
| logging.info("Starting kASLR tests for '%s' on '%s'", |
| kernel_ver, arch) |
| |
| # Make sure we're expecting kernel ASLR at all. |
| if utils.compare_versions(kernel_ver, "3.8") < 0: |
| logging.info("kASLR not available on this kernel") |
| return |
| if arch.startswith('arm'): |
| logging.info("kASLR not available on this architecture") |
| return |
| |
| kallsyms_filename = os.path.join(self.resultsdir, 'kallsyms') |
| address_count = {} |
| |
| count = 0 |
| while True: |
| kallsyms = self._read_kallsyms(kallsyms_filename) |
| symbols = self._parse_kallsyms(kallsyms) |
| |
| assert symbols.has_key(self.target_symbol), \ |
| "The '%s' symbol is missing!?" % (self.target_symbol) |
| |
| addr = symbols[self.target_symbol] |
| logging.debug("Reboot %d: Symbol %s @ %s", \ |
| count, self.target_symbol, addr) |
| |
| address_count.setdefault(addr, 0) |
| address_count[addr] += 1 |
| |
| count += 1 |
| if count == self.reboot_count: |
| break |
| self._reboot_machine() |
| |
| unique = len(address_count) |
| logging.info("Unique kernel offsets: %d", unique) |
| highest = 0 |
| for addr in address_count: |
| logging.debug("Address %s: %d", addr, address_count[addr]) |
| if address_count[addr] > highest: |
| highest = address_count[addr] |
| if unique < 2: |
| raise error.TestFail("kASLR not functioning") |
| if unique < (self.reboot_count / 3): |
| raise error.TestFail("kASLR entropy seems very low") |
| if highest > (unique / 10): |
| raise error.TestFail("kASLR entropy seems to clump") |