1import logging, re, random
2from autotest_lib.client.common_lib import error
3from autotest_lib.client.virt import aexpect
4
5
6def run_iofuzz(test, params, env):
7    """
8    KVM iofuzz test:
9    1) Log into a guest
10    2) Enumerate all IO port ranges through /proc/ioports
11    3) On each port of the range:
12        * Read it
13        * Write 0 to it
14        * Write a random value to a random port on a random order
15
16    If the guest SSH session hangs, the test detects the hang and the guest
17    is then rebooted. The test fails if we detect the qemu process to terminate
18    while executing the process.
19
20    @param test: kvm test object
21    @param params: Dictionary with the test parameters
22    @param env: Dictionary with test environment.
23    """
24    def outb(session, port, data):
25        """
26        Write data to a given port.
27
28        @param session: SSH session stablished to a VM
29        @param port: Port where we'll write the data
30        @param data: Integer value that will be written on the port. This
31                value will be converted to octal before its written.
32        """
33        logging.debug("outb(0x%x, 0x%x)", port, data)
34        outb_cmd = ("echo -e '\\%s' | dd of=/dev/port seek=%d bs=1 count=1" %
35                    (oct(data), port))
36        try:
37            session.cmd(outb_cmd)
38        except aexpect.ShellError, e:
39            logging.debug(e)
40
41
42    def inb(session, port):
43        """
44        Read from a given port.
45
46        @param session: SSH session stablished to a VM
47        @param port: Port where we'll read data
48        """
49        logging.debug("inb(0x%x)", port)
50        inb_cmd = "dd if=/dev/port seek=%d of=/dev/null bs=1 count=1" % port
51        try:
52            session.cmd(inb_cmd)
53        except aexpect.ShellError, e:
54            logging.debug(e)
55
56
57    def fuzz(session, inst_list):
58        """
59        Executes a series of read/write/randwrite instructions.
60
61        If the guest SSH session hangs, an attempt to relogin will be made.
62        If it fails, the guest will be reset. If during the process the VM
63        process abnormally ends, the test fails.
64
65        @param inst_list: List of instructions that will be executed.
66        @raise error.TestFail: If the VM process dies in the middle of the
67                fuzzing procedure.
68        """
69        for (op, operand) in inst_list:
70            if op == "read":
71                inb(session, operand[0])
72            elif op == "write":
73                outb(session, operand[0], operand[1])
74            else:
75                raise error.TestError("Unknown command %s" % op)
76
77            if not session.is_responsive():
78                logging.debug("Session is not responsive")
79                if vm.process.is_alive():
80                    logging.debug("VM is alive, try to re-login")
81                    try:
82                        session = vm.wait_for_login(timeout=10)
83                    except:
84                        logging.debug("Could not re-login, reboot the guest")
85                        session = vm.reboot(method="system_reset")
86                else:
87                    raise error.TestFail("VM has quit abnormally during "
88                                         "%s: %s" % (op, operand))
89
90
91    login_timeout = float(params.get("login_timeout", 240))
92    vm = env.get_vm(params["main_vm"])
93    vm.verify_alive()
94    session = vm.wait_for_login(timeout=login_timeout)
95
96    try:
97        ports = {}
98        r = random.SystemRandom()
99
100        logging.info("Enumerate guest devices through /proc/ioports")
101        ioports = session.cmd_output("cat /proc/ioports")
102        logging.debug(ioports)
103        devices = re.findall("(\w+)-(\w+)\ : (.*)", ioports)
104
105        skip_devices = params.get("skip_devices","")
106        fuzz_count = int(params.get("fuzz_count", 10))
107
108        for (beg, end, name) in devices:
109            ports[(int(beg, base=16), int(end, base=16))] = name.strip()
110
111        for (beg, end) in ports.keys():
112            name = ports[(beg, end)]
113            if name in skip_devices:
114                logging.info("Skipping device %s", name)
115                continue
116
117            logging.info("Fuzzing %s, port range 0x%x-0x%x", name, beg, end)
118            inst = []
119
120            # Read all ports of the range
121            for port in range(beg, end + 1):
122                inst.append(("read", [port]))
123
124            # Write 0 to all ports of the range
125            for port in range(beg, end + 1):
126                inst.append(("write", [port, 0]))
127
128            # Write random values to random ports of the range
129            for seq in range(fuzz_count * (end - beg + 1)):
130                inst.append(("write",
131                             [r.randint(beg, end), r.randint(0,255)]))
132
133            fuzz(session, inst)
134
135    finally:
136        session.close()
137