def run_once(self): kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, self.MIN_KERNEL_VER) < 0: logging.info( 'skipping test: old kernel %s (min %s) missing module %s', kernel_ver, self.MIN_KERNEL_VER, self.OLD_MODULE_NAME) return if utils.compare_versions(kernel_ver, self.NEW_KERNEL_VER) < 0: module_name = self.OLD_MODULE_NAME else: module_name = self.NEW_MODULE_NAME utils.load_module(module_name) self._governor_paths = glob.glob(self.GOVERNOR_GLOB) self._setspeed_paths = glob.glob(self.SETSPEED_GLOB) self._cur_freq_paths = glob.glob(self.CUR_FREQ_GLOB) initial_governors = self._get_governors() initial_quiet_governor = self._get_quiet_governor() with open(self.CPUFREQ_AVAIL_GOVERNORS_PATH, 'r') as f: available_governors = set(f.readline().split()) logging.info('governors: %s', ' '.join(available_governors)) try: if 'userspace' in available_governors: self._test_userspace() else: logging.warning('testing with existing governor') self._test_all_delays() finally: self._reset_freq(initial_governors, initial_quiet_governor) utils.unload_module(module_name)
def run_once(self, args=[]): kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, self.KERNEL_VER) != 0: raise error.TestNAError('Applicable to 3.8 Kernel only') """Run the boot cache test. """ self._run('bootcachetest', "")
def should_run(self): """Indicate if the test should be run on current configuration.""" supported_apis = graphics_utils.GraphicsApiHelper().get_supported_apis( ) num_displays = graphics_utils.get_num_outputs_on() gpu_type = utils.get_gpu_family() soc = utils.get_cpu_soc_family() kernel_version = os.uname()[2] if num_displays == 0 and self._opts['display_required']: # If a test needs a display and we don't have a display, # consider it a pass. logging.warning('No display connected, skipping test.') return False if self._opts['vulkan_required'] and 'vk' not in supported_apis: # If a test needs vulkan to run and we don't have it, # consider it a pass logging.warning('Vulkan is required by test but is not ' 'available on system. Skipping test.') return False if self._opts['min_kernel_version']: min_kernel_version = self._opts['min_kernel_version'] if utils.compare_versions(kernel_version, min_kernel_version) < 0: logging.warning('Test requires kernel version >= %s,' 'have version %s. Skipping test.' % (min_kernel_version, kernel_version)) return False if self.name == 'atomictest' and gpu_type == 'baytrail': logging.warning('Baytrail is on kernel v4.4, but there is no ' 'intention to enable atomic.') return False return True
def run_once(self): kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, self.MIN_KERNEL_VER) < 0: logging.info( 'skipping test: old kernel %s (min %s) missing module %s', kernel_ver, self.MIN_KERNEL_VER, self.MODULE_NAME) return utils.load_module(self.MODULE_NAME) self._governor_paths = glob.glob(self.GOVERNOR_GLOB) self._setspeed_paths = glob.glob(self.SETSPEED_GLOB) self._cur_freq_paths = glob.glob(self.CUR_FREQ_GLOB) initial_governors = self._get_governors() initial_quiet_governor = self._get_quiet_governor() with open(self.CPUFREQ_AVAIL_PATH, 'r') as f: available_freqs = [int(x) for x in f.readline().split()] max_freq = max(available_freqs) min_freq = min(available_freqs) logging.info('cpu frequency max %d min %d', max_freq, min_freq) freqs = [min_freq, max_freq] try: for freq in freqs: self._set_freq(freq) for usecs in self.DELAYS: self._test_udelay(usecs) self._check_freq(freq) finally: self._reset_freq(initial_governors, initial_quiet_governor) utils.unload_module(self.MODULE_NAME)
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")
def run_once(self, host=None, min_version=_DEFAULT_MIN_VERSION): """Runs the test. @param host: A host object representing the DUT. @param min_version: Minimum kernel version required. @raise TestError: Something went wrong while trying to execute the test. @raise TestFail: The test failed. """ try: result = host.run_output('uname -r').strip() except error.GenericHostRunError: raise error.TestFail('Failed to check kernel version') if utils.compare_versions(result, min_version) < 0: raise error.TestFail( 'Device kernel version (%s) older than required (%s)' % (result, min_version))
def run_once(self): # Cache the architecture to avoid redundant execs to "uname". arch = utils.get_arch() userspace_arch = utils.get_arch_userspace() # Report the full uname for anyone reading logs. logging.info('Running %s kernel, %s userspace: %s', arch, userspace_arch, utils.system_output('uname -a')) # Load the list of kernel config variables. config = kernel_config.KernelConfig() config.initialize() # Adjust for kernel-version-specific changes kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, "3.10") >= 0: for entry in self.IS_EXCLUSIVE: if entry['regex'] == 'BINFMT_': entry['builtin'].append('BINFMT_SCRIPT') if utils.compare_versions(kernel_ver, "3.14") >= 0: self.IS_MODULE.append('TEST_ASYNC_DRIVER_PROBE') for entry in self.IS_EXCLUSIVE: if entry['regex'] == 'BINFMT_': entry['builtin'].append('BINFMT_MISC') if utils.compare_versions(kernel_ver, "3.18") >= 0: for entry in self.IS_EXCLUSIVE: if entry['regex'] == '.*_FS$': entry['builtin'].append('SND_PROC_FS') if utils.compare_versions(kernel_ver, "4.4") < 0: for entry in self.IS_EXCLUSIVE: if entry['regex'] == '.*_FS$': entry['builtin'].append('EXT4_USE_FOR_EXT23') # Run the static checks. map(config.has_builtin, self.IS_BUILTIN) map(config.has_module, self.IS_MODULE) map(config.is_enabled, self.IS_ENABLED) map(config.is_missing, self.IS_MISSING) map(config.is_exclusive, self.IS_EXCLUSIVE) # Run the dynamic checks. # Security; NULL-address hole should be as large as possible. # Upstream kernel recommends 64k, which should be large enough to # catch nearly all dereferenced structures. wanted = '65536' if self.is_arm_family(arch): # ... except on ARM where it shouldn't be larger than 32k due # to historical ELF load location. wanted = '32768' config.has_value('DEFAULT_MMAP_MIN_ADDR', [wanted]) # Security; make sure NX page table bits are usable. if self.is_x86_family(arch): if arch == "i386": config.has_builtin('X86_PAE') else: config.has_builtin('X86_64') # Security; marks data segments as RO/NX, text as RO. if (arch == 'armv7l' and utils.compare_versions(kernel_ver, "3.8") < 0): config.is_missing('DEBUG_RODATA') config.is_missing('DEBUG_SET_MODULE_RONX') else: config.has_builtin('DEBUG_RODATA') config.has_builtin('DEBUG_SET_MODULE_RONX') if arch == 'aarch64': config.has_builtin('DEBUG_ALIGN_RODATA') # NaCl; allow mprotect+PROT_EXEC on noexec mapped files. config.has_value('MMAP_NOEXEC_TAINT', ['0']) # Kernel: make sure port 0xED is the one used for I/O delay if self.is_x86_family(arch): config.has_builtin('IO_DELAY_0XED') needed = config.get('CONFIG_IO_DELAY_TYPE_0XED', None) config.has_value('DEFAULT_IO_DELAY_TYPE', [needed]) # Raise a failure if anything unexpected was seen. if len(config.failures()): raise error.TestFail((", ".join(config.failures())))
def is_punch_hole_supported(self): kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, "4.4") < 0: return False return True
def run_once(self): errors = set() # Max procs, max threads, and file max are dependent upon total memory. # The kernel uses a formula similar to: # MemTotal-kb / 128 = max procs # MemTotal-kb / 64 = max threads # MemTotal-kb / 10 = file_max # But note that MemTotal changes at the end of initialization. # The values used below for these settings should give sufficient head # room for usage and kernel allocation. ref_min = { 'file_max': 50000, 'kptr_restrict': 1, 'max_open': 1024, 'max_procs': 3000, 'max_threads': 7000, 'ngroups_max': 65536, 'nr_open': 1048576, 'pid_max': 32768, 'mmap_min_addr': 65536, } ref_equal = { 'leases': 1, 'panic': -1, 'protected_hardlinks': 1, 'protected_symlinks': 1, 'ptrace_scope': 1, 'randomize_va_space': 2, 'sched_rt_period_us': 1000000, 'sched_rt_runtime_us': 800000, 'sysrq': 1, 'suid-dump': 2, 'tcp_syncookies': 1, } refpath = { 'file_max': '/proc/sys/fs/file-max', 'leases': '/proc/sys/fs/leases-enable', 'max_open': '/proc/self/limits', 'max_procs': '/proc/self/limits', 'max_threads': '/proc/sys/kernel/threads-max', 'mmap_min_addr': '/proc/sys/vm/mmap_min_addr', 'kptr_restrict': '/proc/sys/kernel/kptr_restrict', 'ngroups_max': '/proc/sys/kernel/ngroups_max', 'nr_open': '/proc/sys/fs/nr_open', 'panic': '/proc/sys/kernel/panic', 'pid_max': '/proc/sys/kernel/pid_max', 'protected_hardlinks': '/proc/sys/fs/protected_hardlinks', 'protected_symlinks': '/proc/sys/fs/protected_symlinks', 'ptrace_scope': '/proc/sys/kernel/yama/ptrace_scope', 'randomize_va_space': '/proc/sys/kernel/randomize_va_space', 'sched_rt_period_us': '/proc/sys/kernel/sched_rt_period_us', 'sched_rt_runtime_us': '/proc/sys/kernel/sched_rt_runtime_us', 'suid-dump': '/proc/sys/fs/suid_dumpable', 'sysrq': '/proc/sys/kernel/sysrq', 'tcp_syncookies': '/proc/sys/net/ipv4/tcp_syncookies', } # Adjust arch-specific values. if utils.get_arch().startswith('arm'): ref_min['mmap_min_addr'] = 32768 if utils.get_arch().startswith('aarch64'): ref_min['mmap_min_addr'] = 32768 # ARM-compatible limit on x86 if ARC++ is present (b/30146997) if utils.is_arc_available(): ref_min['mmap_min_addr'] = 32768 # Adjust version-specific details. kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, "3.6") < 0: # Prior to kernel version 3.6, Yama handled link restrictions. refpath['protected_hardlinks'] = \ '/proc/sys/kernel/yama/protected_nonaccess_hardlinks' refpath['protected_symlinks'] = \ '/proc/sys/kernel/yama/protected_sticky_symlinks' # Create osvalue dictionary with the same keys as refpath. osvalue = {} for key in refpath: osvalue[key] = None for key in ref_min: osvalue[key] = self.get_limit(key, refpath[key]) if osvalue[key] < ref_min[key]: logging.warning('%s is %d', refpath[key], osvalue[key]) logging.warning('%s should be at least %d', refpath[key], ref_min[key]) errors.add(key) else: logging.info('%s is %d >= %d', refpath[key], osvalue[key], ref_min[key]) for key in ref_equal: osvalue[key] = self.get_limit(key, refpath[key]) if osvalue[key] != ref_equal[key]: logging.warning('%s is set to %d', refpath[key], osvalue[key]) logging.warning('Expected %d', ref_equal[key]) errors.add(key) else: logging.info('%s is %d', refpath[key], osvalue[key]) # Look for anything from refpath that wasn't checked yet: for key in osvalue: if osvalue[key] == None: logging.warning('%s was never checked', key) errors.add(key) # If self.error is not zero, there were errors. if len(errors) > 0: raise error.TestFail('Found incorrect values: %s' % ', '.join(errors))
def run_once(self): """Runs the test.""" arch = utils.get_cpu_arch() if arch == 'x86_64': arch = utils.get_cpu_soc_family() curr_kernel = utils.get_kernel_version() logging.debug('CPU arch is "%s"', arch) logging.debug('Kernel version is "%s"', curr_kernel) if arch not in self.TESTS: raise error.TestNAError('"%s" arch not in test baseline' % arch) # Kernels <= 3.14 don't have this directory and are expected to abort # with TestNA. if not os.path.exists(self.SYSTEM_CPU_VULNERABILITIES): raise error.TestNAError('"%s" directory not present, not testing' % self.SYSTEM_CPU_VULNERABILITIES) failures = [] for filename, expected in self.TESTS[arch].items(): file = os.path.join(self.SYSTEM_CPU_VULNERABILITIES, filename) if not os.path.exists(file): raise error.TestError('"%s" file does not exist, cannot test' % file) min_kernel = expected[0] if utils.compare_versions(curr_kernel, min_kernel) == -1: # The kernel on the DUT is older than the version where # the mitigation was introduced. info_message = 'DUT kernel version "%s"' % curr_kernel info_message += ' is older than "%s"' % min_kernel info_message += ', skipping "%s" test' % filename logging.info(info_message) continue # E.g.: # Not affected # $ cat /sys/devices/system/cpu/vulnerabilities/meltdown # Not affected # # One mitigation # $ cat /sys/devices/system/cpu/vulnerabilities/meltdown # Mitigation: PTI # # Several mitigations # $ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2 # Mitigation: Full generic retpoline, IBPB, IBRS_FW with open(file) as f: lines = f.readlines() if len(lines) > 1: logging.warning('"%s" has more than one line', file) actual = lines[0].strip() logging.debug('"%s" -> "%s"', file, actual) expected_mitigations = expected[1] if not expected_mitigations: if actual != 'Not affected': failures.append((file, actual, expected_mitigations)) else: # CPU is affected. if 'Mitigation' not in actual: failures.append((file, actual, expected_mitigations)) else: mit_list = actual.split(':', 1)[1].split(',') actual_mitigations = set(t.strip() for t in mit_list) # Test set inclusion. if actual_mitigations < expected_mitigations: failures.append((file, actual_mitigations, expected_mitigations)) if failures: for failure in failures: logging.error('"%s" was "%s", expected "%s"', *failure) raise error.TestFail('CPU vulnerabilities not mitigated properly')
def run_once(self): """ The actual test. """ # Cache the architecture to avoid redundant execs to "uname". arch = utils.get_arch() userspace_arch = utils.get_arch_userspace() # Report the full uname for anyone reading logs. logging.info('Running %s kernel, %s userspace: %s', arch, userspace_arch, utils.system_output('uname -a')) # Load the list of kernel config variables. config = kernel_config.KernelConfig() config.initialize(missing_ok=self.MISSING_OK) # Adjust for kernel-version-specific changes kernel_ver = os.uname()[2] # For linux-3.10 or newer. if utils.compare_versions(kernel_ver, "3.10") >= 0: for entry in self.IS_EXCLUSIVE: if entry['regex'] == 'BINFMT_': entry['builtin'].append('BINFMT_SCRIPT') # For linux-3.14 or newer. if utils.compare_versions(kernel_ver, "3.14") >= 0: self.IS_MODULE.append('TEST_ASYNC_DRIVER_PROBE') self.IS_MISSING.remove('INET_DIAG') for entry in self.IS_EXCLUSIVE: if entry['regex'] == 'BINFMT_': entry['builtin'].append('BINFMT_MISC') if entry['regex'] == '.*_FS$': entry['module'].append('NFS_FS') # For linux-3.18 or newer. if utils.compare_versions(kernel_ver, "3.18") >= 0: for entry in self.IS_EXCLUSIVE: if entry['regex'] == '.*_FS$': entry['builtin'].append('SND_PROC_FS') entry['builtin'].append('USB_CONFIGFS_F_FS') entry['builtin'].append('ESD_FS') entry['enabled'].append('CONFIGFS_FS') entry['module'].append('USB_F_FS') # Like FW_LOADER_USER_HELPER, these may be exploited by userspace. # We run udev everywhere which uses netlink sockets for event # propagation rather than executing programs, so don't need this. self.IS_MISSING.append('UEVENT_HELPER') self.IS_MISSING.append('UEVENT_HELPER_PATH') # For kernels older than linux-4.4. if utils.compare_versions(kernel_ver, "4.4") < 0: for entry in self.IS_EXCLUSIVE: if entry['regex'] == '.*_FS$': entry['builtin'].append('EXT4_USE_FOR_EXT23') # For linux-4.4 or newer. if utils.compare_versions(kernel_ver, '4.4') >= 0: self.IS_BUILTIN.append('STATIC_USERMODEHELPER') # For linux-4.19 or newer. if utils.compare_versions(kernel_ver, "4.19") >= 0: self.IS_MISSING.remove('BPF_SYSCALL') self.IS_BUILTIN.append('HAVE_EBPF_JIT') self.IS_BUILTIN.append('BPF_JIT_ALWAYS_ON') self.IS_BUILTIN.remove('CC_STACKPROTECTOR') self.IS_BUILTIN.append('STACKPROTECTOR') # Run the static checks. map(config.has_builtin, self.IS_BUILTIN) map(config.has_module, self.IS_MODULE) map(config.is_enabled, self.IS_ENABLED) map(config.is_missing, self.IS_MISSING) map(config.is_exclusive, self.IS_EXCLUSIVE) # Run the dynamic checks. # Security; NULL-address hole should be as large as possible. # Upstream kernel recommends 64k, which should be large enough # to catch nearly all dereferenced structures. For # compatibility with ARM binaries (even on x86) this needs to # be 32k. wanted = '32768' config.has_value('DEFAULT_MMAP_MIN_ADDR', [wanted]) # Security; make sure usermode helper is our tool for linux-4.4+. if utils.compare_versions(kernel_ver, '4.4') >= 0: wanted = '"/sbin/usermode-helper"' config.has_value('STATIC_USERMODEHELPER_PATH', [wanted]) # Security; make sure NX page table bits are usable. if self.is_x86_family(arch): if arch == "i386": config.has_builtin('X86_PAE') else: config.has_builtin('X86_64') # Security; marks data segments as RO/NX, text as RO. if utils.compare_versions(kernel_ver, "4.11") < 0: config.has_builtin('DEBUG_RODATA') config.has_builtin('DEBUG_SET_MODULE_RONX') else: config.has_builtin('STRICT_KERNEL_RWX') config.has_builtin('STRICT_MODULE_RWX') if arch == 'aarch64': config.has_builtin('DEBUG_ALIGN_RODATA') # NaCl; allow mprotect+PROT_EXEC on noexec mapped files. config.has_value('MMAP_NOEXEC_TAINT', ['0']) # Kernel: make sure port 0xED is the one used for I/O delay. if self.is_x86_family(arch): config.has_builtin('IO_DELAY_0XED') needed = config.get('CONFIG_IO_DELAY_TYPE_0XED', None) config.has_value('DEFAULT_IO_DELAY_TYPE', [needed]) # Raise a failure if anything unexpected was seen. if len(config.failures()): raise error.TestFail((", ".join(config.failures())))
def run_once(self): kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, self.MIN_KERNEL_VER) < 0: logging.info( 'skipping test: old kernel %s (min %s) missing module %s', kernel_ver, self.MIN_KERNEL_VER, self.MODULE_NAME) return utils.load_module(self.MODULE_NAME) start_rtc, start_ktime, start_error = self._get_times() logging.info( 'start rtc %d ktime %f error %f', start_rtc, start_ktime, start_error) recent_diffs = [] max_diff = 0 sum_rtc = 0 sum_diff = 0 sum_rtc_rtc = 0 sum_rtc_diff = 0 sum_diff_diff = 0 for i in xrange(self.TEST_DURATION): # Sleep some amount of time to avoid busy waiting the entire time sleep((i % 10) * 0.1) current_rtc, current_ktime, current_error = self._get_times() elapsed_rtc = current_rtc - start_rtc elapsed_ktime = current_ktime - start_ktime elapsed_diff = float(elapsed_rtc) - elapsed_ktime # Allow for inaccurate ktime off ALLOWABLE_DRIFT from elapsed RTC, # and take into account start and current error in times gathering max_error = start_error + current_error drift_threshold = elapsed_rtc * self.ALLOWABLE_DRIFT + max_error # Track rolling average and maximum diff recent_diffs.append(elapsed_diff) if len(recent_diffs) > self.DIFFS_TO_AVERAGE: recent_diffs.pop(0) rolling_diff = sum(recent_diffs) / len(recent_diffs) if abs(elapsed_diff) > abs(max_diff): max_diff = elapsed_diff # Track linear regression sum_rtc += elapsed_rtc sum_diff += elapsed_diff sum_rtc_rtc += elapsed_rtc * elapsed_rtc sum_rtc_diff += elapsed_rtc * elapsed_diff sum_diff_diff += elapsed_diff * elapsed_diff logging.info(( 'current rtc %d ktime %f error %f; elapsed rtc %d ' 'ktime %f: threshold %f diff %+f rolling %+f'), current_rtc, current_ktime, current_error, elapsed_rtc, elapsed_ktime, drift_threshold, elapsed_diff, rolling_diff) if abs(elapsed_diff) > drift_threshold: raise error.TestFail(( 'elapsed rtc %d and ktime %f diff %f ' 'is greater than threshold %f') % (elapsed_rtc, elapsed_ktime, elapsed_diff, drift_threshold)) # Dump final statistics logging.info('max_diff %f', max_diff) mean_rtc = sum_rtc / self.TEST_DURATION mean_diff = sum_diff / self.TEST_DURATION slope = ((sum_rtc_diff - sum_rtc * mean_diff) / (sum_rtc_rtc - sum_rtc * mean_rtc)) logging.info('drift %.9f', slope) utils.unload_module(self.MODULE_NAME)
class kernel_CrosECSysfs(test.test): '''Make sure the EC sysfs interface provides meaningful output''' version = 1 cros_ec = '/dev/cros_ec' sysfs_path = '/sys/devices/virtual/chromeos/cros_ec' kernel_ver = os.uname()[2] if utils.compare_versions(kernel_ver, "3.14") >= 0: sysfs_path = '/sys/class/chromeos/cros_ec' def _read_file(self, filename): """ Return the contents of the given file or fail. @param filename Full path to the file to be read """ try: content = utils.read_file(filename) except Exception as err: raise error.TestFail('sysfs file problem: %s' % err) return content def _read_sysfs(self, filename): """ Read the contents of the given sysfs file or fail @param filename Name of the file within the sysfs interface directory """ fullpath = os.path.join(self.sysfs_path, filename) return self._read_file(fullpath) def _read_field(self, filename, field): """ Return the given field from the sysfs file or fail @param filename Name of the file within the sysfs interface directory @param field Name of field to match in the file content """ fullpath = os.path.join(self.sysfs_path, filename) content = self._read_file(fullpath) match = utils.get_field(content, 0, field) if match is None: raise error.TestFail("no '%s' field in %s" % (field, fullpath)) return match def run_once(self): """ Quick check for the existence of the basic sysfs files """ # If /dev/cros_ec isn't present, then the MFD_CROS_EC_DEV driver isn't # present, so there's no point to looking for the sysfs interface to it. if not os.path.exists(self.cros_ec): raise error.TestFail("%s not found. No driver?" % self.cros_ec) flashsize = self._read_field('flashinfo', 'FlashSize') logging.info("flashsize is %s", flashsize) build = self._read_field('version', 'Build info:') logging.info("build is %s", build) reboot = self._read_sysfs('reboot') if reboot.find("ro") < 0: raise error.TestFail('reboot help is weird: %s' % reboot) logging.info("reboot is %s", reboot)