Beispiel #1
0
    def test_ptrace_syscalls(self):
        def process_func():
            ptrace.traceme()
            os.kill(os.getpid(), signal.SIGSTOP)

            with open('/dev/null', 'w') as f:
                f.write('foo')

            rd, wr = os.pipe()
            os.close(rd)
            os.close(wr)

        try:
            process = multiprocessing.Process(target=process_func)
            process.start()

            pid, status = os.waitpid(process.pid, 0)

            self.assertTrue(os.WIFSTOPPED(status))
            stopsig = os.WSTOPSIG(status)
            self.assertEqual(stopsig, signal.SIGSTOP)

            ptrace.setoptions(process.pid, ptrace.PTRACE_O_TRACESYSGOOD)

            syscalls = []
            in_syscall = None

            while True:
                ptrace.syscall(process.pid)
                pid, status = os.waitpid(process.pid, 0)

                if os.WIFEXITED(status):
                    break

                self.assertTrue(os.WIFSTOPPED(status))

                stopsig = os.WSTOPSIG(status)
                self.assertTrue(stopsig & 0x80)
                self.assertEqual(stopsig & 0x7F, signal.SIGTRAP)

                regs = ptrace.getregs(process.pid)
                if not in_syscall:
                    syscall = ptrace.syscall_enter(process.pid, regs)
                    syscalls.append(syscall)
                    in_syscall = syscall
                else:
                    ptrace.syscall_exit(in_syscall, regs)
                    in_syscall = None

        finally:
            try:
                os.kill(process.pid, signal.SIGKILL)
            except OSError as e:
                if e.errno == errno.ESRCH:
                    pass
                else:
                    raise

        syscalls = [
            s for s in syscalls if s.name in {'open', 'write', 'close'}
        ]

        self.assertEqual(len(syscalls), 5)

        open_call, write_call, close_call = syscalls[:3]

        self.assertEqual(open_call.args[0].value, b'/dev/null')
        fno = open_call.result.value
        self.assertGreater(fno, 0)

        self.assertIsNotNone(open_call.result.type)

        self.assertEqual(write_call.args[0].value, fno)
        self.assertEqual(write_call.args[2].value, 3)
        self.assertEqual(write_call.result.value, 3)

        self.assertEqual(close_call.args[0].value, fno)
Beispiel #2
0
def _debugger_thread_inner(main_pid, dbgproc_started, dbgthread_stop,
                           stack_request_pipe, stack_queue, syscall_queue,
                           syscall_filter):
    ptrace_options = ptrace.PTRACE_O_TRACECLONE
    # Attach to the tracee and wait for it to stop.
    ptrace.attach_and_wait(main_pid, ptrace_options)

    if syscall_filter is not None:
        filter_ = lambda sc: any(m.match(sc) for m in syscall_filter)
    else:
        filter_ = None

    syscall_trap = signal.SIGTRAP | 0x80
    enabled = False
    signum = 0
    syscall_state = {}
    sigstop_received = set()

    processes = {main_pid}
    mem_fds = {}
    mem_fds[main_pid] = _open_procmem(main_pid)

    # Notify the parent that we are ready to start tracing.
    dbgproc_started.set()

    try:
        # Restart the tracee and enter the tracing loop.
        ptrace.syscall(main_pid)

        while True:
            if dbgthread_stop.is_set():
                break

            pid, status = ptrace.wait(-1)

            if os.WIFEXITED(status) or os.WIFSIGNALED(status):
                # Traced thread has died.
                processes.discard(pid)
                mem_fd = mem_fds.get(pid)
                if mem_fd is not None:
                    try:
                        os.close(mem_fd)
                    except IOError:
                        pass
                if not processes:
                    break
                else:
                    continue

            elif os.WIFSTOPPED(status):
                ptrace_event = ptrace.WPTRACEEVENT(status)
                if ptrace_event == ptrace.PTRACE_EVENT_CLONE:
                    # A new thread has been created.
                    new_pid = ptrace.geteventmsg(pid)
                    # See the comment below for the explanation of this check.
                    if new_pid not in sigstop_received:
                        ptrace.wait_for_trace_stop(new_pid)
                        try:
                            ptrace.syscall(new_pid)
                        except OSError as e:
                            if e.errno != errno.ESRCH:
                                # The new thread might have already died.
                                raise
                    else:
                        sigstop_received.discard(new_pid)

                    mem_fds[new_pid] = _open_procmem(new_pid)

                    processes.add(new_pid)
                    ptrace.syscall(pid)
                    continue

                stopsig = os.WSTOPSIG(status)
                if stopsig != syscall_trap:
                    # Signal-delivery-stop.

                    # The special condition below is for cases when we
                    # receive a SIGSTOP for a newly created thread _before_
                    # receiving the PTRACE_EVENT_CLONE event for its parent.
                    # In this case we must not forward the signal, but
                    # must record its receipt so that once we _do_ receive
                    # PTRACE_EVENT_CLONE for the parent, we don't wait for
                    # SIGSTOP in the child again.
                    if (stopsig != signal.SIGSTOP or pid in processes
                            or all(syscall.name != 'clone'
                                   for syscall in syscall_state.values()
                                   if syscall is not None)):
                        # forward the signal
                        signum = stopsig
                    else:
                        sigstop_received.add(pid)
                else:
                    # Syscall-stop.
                    syscall = syscall_state.get(pid)
                    regs = ptrace.getregs(pid)
                    mem_fd = mem_fds.get(pid)

                    if syscall is None:
                        # Syscall-enter-stop.
                        syscall_state[pid] = ptrace.syscall_enter(
                            pid, regs, mem_fd)
                    else:
                        # Syscall-exit-stop.
                        ptrace.syscall_exit(syscall, regs, mem_fd)

                        if enabled:
                            # Stop tracing once the tracee executes
                            # the magic open() in ptracer.disable().
                            stop_tracing = (
                                syscall.name == 'open'
                                and syscall.args[0].value == b'\x03\x02\x01'
                            ) or (syscall.name == 'openat'
                                  and syscall.args[1].value == b'\x03\x02\x01')

                            if stop_tracing:
                                break
                            elif filter_ is None or filter_(syscall):
                                # Wait for the traceback to arrive.
                                os.write(stack_request_pipe,
                                         struct.pack('!Q', pid))
                                stack = stack_queue.get()
                                if stack is None:
                                    ptrace.cont(pid)
                                    break

                                syscall.traceback = stack
                                syscall_queue.put_nowait(syscall)

                        elif not enabled:
                            # Start tracing once the tracee executes
                            # the magic open() in ptracer.enable().
                            start_tracing = (
                                syscall.name == 'open'
                                and syscall.args[0].value == b'\x01\x02\x03'
                            ) or (syscall.name == 'openat'
                                  and syscall.args[1].value == b'\x01\x02\x03')

                            if start_tracing:
                                enabled = True

                        syscall_state[pid] = None
            else:
                logger.error('unexpected status of traced process %s: %s', pid,
                             status)

            # Continue until next syscall.
            ptrace.syscall(pid, signum)
            signum = 0
    finally:
        for process in processes:
            try:
                ptrace.detach(process)
            except OSError as e:
                if e.errno == errno.ESRCH:
                    pass
                else:
                    raise

        for fd in mem_fds.values():
            try:
                os.close(fd)
            except (OSError, IOError):
                pass
Beispiel #3
0
    def test_ptrace_procmem(self):
        def process_func():
            ptrace.traceme()
            os.kill(os.getpid(), signal.SIGSTOP)

            with open('/dev/null', 'w') as f:
                f.write('foo')

            rd, wr = os.pipe()
            os.close(rd)
            os.close(wr)

        try:
            process = multiprocessing.Process(target=process_func)
            process.start()

            pid, status = os.waitpid(process.pid, 0)
            ptrace.setoptions(process.pid, ptrace.PTRACE_O_TRACESYSGOOD)

            syscalls = []
            in_syscall = None

            mem_fd = os.open('/proc/{}/mem'.format(pid), os.O_RDONLY)

            while True:
                ptrace.syscall(process.pid)
                pid, status = ptrace.wait(process.pid)

                if os.WIFEXITED(status):
                    break

                regs = ptrace.getregs(process.pid)
                if not in_syscall:
                    syscall = ptrace.syscall_enter(process.pid, regs, mem_fd)
                    syscalls.append(syscall)
                    in_syscall = syscall
                else:
                    ptrace.syscall_exit(in_syscall, regs, mem_fd)
                    in_syscall = None

        finally:
            os.close(mem_fd)

            try:
                os.kill(process.pid, signal.SIGKILL)
            except OSError as e:
                if e.errno == errno.ESRCH:
                    pass
                else:
                    raise

        syscalls = [
            s for s in syscalls
            if s.name in {'open', 'openat', 'write', 'close'}
        ]

        self.assertEqual(len(syscalls), 5)

        open_call, write_call, close_call = syscalls[:3]

        if open_call.name == 'openat':
            self.assertEqual(open_call.args[1].value, b'/dev/null')
        else:
            self.assertEqual(open_call.args[0].value, b'/dev/null')
        fno = open_call.result.value
        self.assertGreater(fno, 0)

        self.assertEqual(write_call.args[0].value, fno)
        self.assertEqual(write_call.args[2].value, 3)
        self.assertEqual(write_call.result.value, 3)

        self.assertEqual(close_call.args[0].value, fno)