class LinuxDevice(DesktopDevice): def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self) @staticmethod def local_ips(): raise XVEx("TODO: Local IPs for Linux") @staticmethod def open_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not opening') # TODO: open the application here @staticmethod def close_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not closing') # TODO: close the application here def os_name(self): return 'linux' def os_version(self): return " ".join(platform.linux_distribution()) def report_info(self): info = super().report_info() commands = [ ['uname', '-a'], ['lsb_release', '-a'], ['lscpu'], ] for command in commands: try: info += self._connector_helper.check_command(command)[0] except XVProcessException as ex: L.warning( "Couldn't get system info using command {}:\n{}".format( command, ex)) return info def kill_process(self, pid): L.debug("Killing process {}".format(pid)) return self._connector_helper.execute_scriptlet( 'remote_os_kill.py', [pid, int(signal.SIGKILL)], root=True) def pgrep(self, process_name): L.debug("pgrep-ing for {}".format(process_name)) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) def command_line_for_pid(self, pid): return self._connector_helper.execute_scriptlet( 'command_line_for_pid.py', [pid], root=True)
class LinuxDevice(DesktopDevice): def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self) @staticmethod def local_ips(): raise XVEx("TODO: Local IPs for Linux") @staticmethod def open_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not opening') # TODO: open the application here @staticmethod def close_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not closing') # TODO: close the application here def os_name(self): return 'linux' def os_version(self): L.warning("TODO: Linux version") return 'TODO: Linux version' def report_info(self): # TODO: Add Linux-specific reporting here. info = super().report_info() return info def kill_process(self, pid): L.debug("Killing process {}".format(pid)) return self._connector_helper.execute_scriptlet( 'remote_os_kill.py', [pid, int(signal.SIGKILL)], root=True) def pgrep(self, process_name): L.debug("pgrep-ing for {}".format(process_name)) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) def command_line_for_pid(self, pid): return self._connector_helper.execute_scriptlet( 'command_line_for_pid.py', [pid], root=True)
class MacOSDevice(DesktopDevice): def __init__(self, config, connector): super().__init__(config, connector) # TODO: I think this should be part of DesktopDevice. Need to clarify what all these thigns # mean. I think we should move to DesktopDevice meaning anything with the tools. Maybe even # this becomes ToolsDevice. self._connector_helper = ConnectorHelper(self) # TODO: This needs to execute remotely in general. Let's make a scriptlet. Let's ensure that # nothing on the device classes themselves restricts the devices to being the localhost @staticmethod def local_ips(): ips = [] for iface in netifaces.interfaces(): if netifaces.AF_INET in netifaces.ifaddresses(iface): ips.append(netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']) return [ipaddress.ip_address(ip) for ip in ips] def open_app(self, bundle_path, root=False): # Quote the bundle path as some have spaces in self._connector_helper.execute_scriptlet( 'macos_open_app.py', ["'{}'".format(bundle_path)], root=root) def close_app(self, bundle_path, root=False): # Quit by sending quit signal to the window so the app shuts down how a user would shut it # down. In theory it's equivalent to a pkill but slightly more realistic this way self._connector_helper.execute_command( ['osascript', '-e', "'quit app \"{}\"'".format(bundle_path)], root=root) def os_name(self): return 'macos' def os_version(self): return self._connector_helper.execute_scriptlet('remote_mac_ver.py', [])[0] def report_info(self): info = super().report_info() try: info += self._connector_helper.check_command( ['system_profiler', 'SPSoftwareDataType'])[0] except XVProcessException as ex: L.warning("Couldn't get OS info from system_profiler:\n{}".format(ex)) return info def kill_process(self, pid): L.debug("Killing process {}".format(pid)) return self._connector_helper.execute_scriptlet( 'remote_os_kill.py', [pid, int(signal.SIGKILL)], root=True) def pgrep(self, process_name): '''Similar to the posix pgrep program, however it will return any process ids where process_name is a a substring of the whole process command line.''' L.debug("pgrep-ing for {}".format(process_name)) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) def command_line_for_pid(self, pid): return self._connector_helper.execute_scriptlet('command_line_for_pid.py', [pid], root=True)
def main(argv=None): if argv is None: argv = sys.argv[1:] scriptlet_args = argv[1:] argv = argv[0:1] parser = argparse.ArgumentParser(description='Execute a scriptlet locally for testing') parser.add_argument( 'scriptlet', help='Name of the scriptlet in the xv_leak_tools/scriplets folder') args = parser.parse_args(argv) L.configure({ 'trace': { 'level': L.INFO, }, 'describe': { 'file_format': None, }, 'report': { 'file_format': None, }, }) try: L.info("Running scriptlet {} with args {}".format(args.scriptlet, scriptlet_args)) context = TestRunContext({'output_directory': 'output'}) localhost = LocalhostDiscoverer(context, []).discover_device({}) helper = ConnectorHelper(localhost) L.info("scriptlet returned {}".format(helper.execute_scriptlet( 'remote_mac_ver.py', scriptlet_args))) except BaseException as ex: L.info("scriptlet raised {}".format(ex)) return 0
class WindowsDevice(DesktopDevice): # pylint: disable=no-self-use PROG_OS_VERSION = re.compile(r'OS Version:\s*([^\s]+).*') def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self) def _pgrep_cygwin(self, process_name): # -W shows windows processes under cygwin lines = self._connector_helper.check_command(['ps', '-efW'])[0].splitlines() lines = [line for line in lines if process_name in line] return [int(line.split()[1]) for line in lines] def is_cygwin(self): ret = self._connector_helper.execute_command(['uname'])[0] # Don't even need to check the output. This will fail if DOS. return ret == 0 def os_name(self): return 'windows' def os_version(self): try: # Use systeminfo on Windows as platform.win32_ver doesn't work for cygwin. That way the # code is agnostic to the shell. output = self._connector_helper.check_command(['systeminfo'])[0] for line in output.splitlines(): match = WindowsDevice.PROG_OS_VERSION.match(line) if match: return match.group(1) raise XVEx( "Couldn't determine Windows Version from systeminfo output:\n{}" .format(output)) except XVProcessException as ex: raise XVEx( "Couldn't determine Windows Version as systeminfo failed:\n{}". format(ex)) # TODO: I think all desktop devices share this, so derive instead. @staticmethod def local_ips(): ips = [] for iface in netifaces.interfaces(): if netifaces.AF_INET in netifaces.ifaddresses(iface): ips.append( netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']) return [ipaddress.ip_address(ip) for ip in ips] # TODO: Not sure this will either work at all or work on cygwin def open_app(self, app_path, root=False): # pylint: disable=unused-argument cmd = ['run', "\"{}\"".format(windows_safe_path(app_path))] L.debug("Executing cmd '{}'".format(cmd)) self._connector_helper.check_command(cmd) def close_app(self, app_path, root=False): # pylint: disable=unused-argument pname = windows_path_split(app_path)[1] pids = [str(pid) for pid in self.pgrep(pname)] if len(pids) > 1: L.warning( "Closing all pids {} associated to application {}".format( ', '.join(pids), pname)) for pid in pids: self.kill_process(pid) def kill_process(self, pid): L.debug("Killing process {}".format(pid)) # taskkill is the most generic way to handle windows self._connector_helper.check_command( ['taskkill', '/PID', str(pid), '/F'], root=True) def pgrep(self, process_name): '''Similar to the posix pgrep program, however it will return any process ids where process_name is a a substring of the whole process command line.''' L.debug("pgrep-ing for {}".format(process_name)) if self.is_cygwin(): return self._pgrep_cygwin(process_name) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) # This is pretty sketchy. Do better! @staticmethod def _fix_quotes(cmd_line): args = [] next_arg = "" start_quote = False escaping = False for ichar, char in enumerate(cmd_line): if char not in ["\\", "\"", " "]: next_arg += char continue if char == "\"": if escaping: next_arg += char escaping = False continue elif start_quote: args.append(next_arg) start_quote = False next_arg = "" continue else: start_quote = True continue elif char == "\\": if cmd_line[ichar + 1] == "\"": escaping = True next_arg += char else: next_arg += char continue elif char == " ": if start_quote: next_arg += char continue else: if next_arg: args.append(next_arg) next_arg = "" continue if next_arg != "": args.append(next_arg) return args def command_line_for_pid(self, pid): if self.is_cygwin(): # The psutil module isn't supported on cygwin cmd = [ "wmic", "process", "where", "ProcessID='{}'".format(pid), "get", "CommandLine" ] args = self._connector_helper.check_command(cmd)[0] L.verbose("Raw wmic command line was: {}".format(args)) return WindowsDevice._fix_quotes(args) return self._connector_helper.execute_scriptlet( 'command_line_for_pid.py', [pid]) def report_info(self): info = super().report_info() try: info += self._connector_helper.check_command(['systeminfo'])[0] except XVProcessException as ex: L.warning("Couldn't get OS info from systeminfo:\n{}".format(ex)) return info