def test_find_task(self): pid = os.getpid() with open("/proc/self/comm", "rb") as f: comm = f.read()[:-1] task = find_task(self.prog, os.getpid()) self.assertEqual(task.pid, pid) self.assertEqual(task.comm.string_(), comm)
def test_get_net_ns_by_fd(self): pid = os.getpid() task = find_task(self.prog, pid) with open(f"/proc/{pid}/ns/net") as file: net = get_net_ns_by_fd(task, file.fileno()) for index, name in socket.if_nameindex(): netdev = netdev_get_by_index(net, index) self.assertEqual(netdev.name.string_().decode(), name) with tempfile.TemporaryFile("rb") as file: self.assertRaisesRegex( ValueError, "not a namespace inode", get_net_ns_by_fd, task, file.fileno(), ) with open(f"/proc/{pid}/ns/mnt") as file: self.assertRaisesRegex( ValueError, "not a network namespace inode", get_net_ns_by_fd, task, file.fileno(), )
def test_by_task_struct(self): pid = fork_and_pause() wait_until(lambda: proc_state(pid) == "S") self.assertIn("pause", str(self.prog.stack_trace(find_task(self.prog, pid)))) os.kill(pid, signal.SIGKILL) os.waitpid(pid, 0)
def test_non_canonical_x86_64(self): task = find_task(self.prog, os.getpid()) data = b"hello, world" buf = ctypes.create_string_buffer(data) address = ctypes.addressof(buf) self.assertRaises(FaultError, access_process_vm, task, address ^ (1 << 63), len(data))
def setUpClass(cls): # It'd be nice to just use addClassCleanup(), but that was added in # Python 3.8. cls.__cleanups = [] try: super().setUpClass() # Don't enable cgroup2 on systems that aren't already using it (or # don't support it). cgroup2_enabled = False try: with open("/proc/self/cgroup", "rb") as f: for line in f: if line.startswith(b"0::"): cgroup2_enabled = True break except FileNotFoundError: pass if not cgroup2_enabled: raise unittest.SkipTest("cgroup2 not enabled") # It's easier to mount the cgroup2 filesystem than to find it. cgroup2_mount = Path(tempfile.mkdtemp(prefix="drgn-tests-")) cls.__cleanups.append((cgroup2_mount.rmdir, )) mount("cgroup2", cgroup2_mount, "cgroup2", MS_NOSUID | MS_NODEV | MS_NOEXEC) cls.__cleanups.append((umount, cgroup2_mount)) cls.root_cgroup = cls.prog["cgrp_dfl_root"].cgrp.address_of_() pid = fork_and_pause() try: task = find_task(cls.prog, pid) parent_cgroup_dir = Path( tempfile.mkdtemp(prefix="drgn-tests-", dir=cgroup2_mount)) cls.__cleanups.append((parent_cgroup_dir.rmdir, )) cls.parent_cgroup_name = os.fsencode(parent_cgroup_dir.name) cls.parent_cgroup_path = b"/" + cls.parent_cgroup_name (parent_cgroup_dir / "cgroup.procs").write_text(str(pid)) cls.parent_cgroup = task.cgroups.dfl_cgrp.read_() child_cgroup_dir = parent_cgroup_dir / "child" child_cgroup_dir.mkdir() cls.__cleanups.append((child_cgroup_dir.rmdir, )) cls.child_cgroup_name = os.fsencode(child_cgroup_dir.name) cls.child_cgroup_path = (cls.parent_cgroup_path + b"/" + cls.child_cgroup_name) (child_cgroup_dir / "cgroup.procs").write_text(str(pid)) cls.child_cgroup = task.cgroups.dfl_cgrp.read_() finally: os.kill(pid, signal.SIGKILL) os.waitpid(pid, 0) except BaseException: for cleanup in reversed(cls.__cleanups): cleanup[0](*cleanup[1:]) raise
def test_access_process_vm(self): task = find_task(self.prog, os.getpid()) data = b"hello, world" buf = ctypes.create_string_buffer(data) address = ctypes.addressof(buf) self.assertEqual(access_process_vm(task, address, len(data)), data) self.assertEqual(access_remote_vm(task.mm, address, len(data)), data) self.assertRaises(FaultError, access_process_vm, task, 0, 8)
def test_thread(self): thread = self.prog.thread(1) self.assertEqual(thread.tid, 1) self.assertEqual(thread.object, find_task(self.prog, 1)) crashed_thread_tid = self.prog.crashed_thread().tid self.assertEqual( self.prog.thread(crashed_thread_tid).tid, crashed_thread_tid)
def test_for_each_file(self): task = find_task(self.prog, os.getpid()) with os.scandir("/proc/self/fd") as dir: # NB: The call to for_each_file() comes first so that it will # include the scandir file descriptor. self.assertEqual( {fd for fd, file in for_each_file(task)}, {int(entry.name) for entry in dir}, )
def test_pt_regs(self): # This won't unwind anything useful, but at least make sure it accepts # a struct pt_regs. self.prog.stack_trace(Object(self.prog, "struct pt_regs", value={})) # Likewise, this is nonsense, but we should also accept a struct # pt_regs *. task = find_task(self.prog, os.getpid()) self.prog.stack_trace(cast("struct pt_regs *", task.stack))
def test_task_state_to_char(self): task = find_task(self.prog, os.getpid()) self.assertEqual(task_state_to_char(task), "R") pid = fork_and_pause() task = find_task(self.prog, pid) wait_until(lambda: proc_state(pid) == "S") self.assertEqual(task_state_to_char(task), "S") os.kill(pid, signal.SIGSTOP) wait_until(lambda: proc_state(pid) == "T") self.assertEqual(task_state_to_char(task), "T") os.kill(pid, signal.SIGKILL) wait_until(lambda: proc_state(pid) == "Z") self.assertEqual(task_state_to_char(task), "Z") os.waitpid(pid, 0)
def test_access_process_vm_big(self): task = find_task(self.prog, os.getpid()) with self._pages() as (map, address, _): self.assertEqual(access_process_vm(task, address, len(map)), map[:]) self.assertEqual( access_process_vm(task, address + 1, len(map) - 1), map[1:] ) self.assertEqual( access_process_vm(task, address + 1, len(map) - 2), map[1:-1] )
def test_thread_size(self): # As far as I can tell, there's no way to query this value from # userspace, so at least sanity check that it's a power-of-two multiple # of the page size and that we can read the entire stack. thread_size = self.prog["THREAD_SIZE"].value_() page_size = self.prog["PAGE_SIZE"].value_() self.assertEqual(thread_size % page_size, 0) self.assertTrue(is_power_of_two(thread_size // page_size)) task = find_task(self.prog, os.getpid()) self.prog.read(task.stack, thread_size)
def test_sk_tcpstate(self): with create_socket() as sock: task = find_task(self.prog, os.getpid()) file = fget(task, sock.fileno()) sk = cast("struct socket *", file.private_data).sk self.assertEqual(sk_tcpstate(sk), self.prog["TCP_CLOSE"]) sock.bind(("localhost", 0)) sock.listen() self.assertEqual(sk_tcpstate(sk), self.prog["TCP_LISTEN"]) with socket.create_connection( sock.getsockname()), sock.accept()[0] as sock2: file = fget(task, sock2.fileno()) sk = cast("struct socket *", file.private_data).sk self.assertEqual(sk_tcpstate(sk), self.prog["TCP_ESTABLISHED"])
def test_environ(self): with open("/proc/self/environ", "rb") as f: proc_environ = f.read().split(b"\0")[:-1] task = find_task(self.prog, os.getpid()) self.assertEqual(environ(task), proc_environ)
def test_cmdline(self): with open("/proc/self/cmdline", "rb") as f: proc_cmdline = f.read().split(b"\0")[:-1] task = find_task(self.prog, os.getpid()) self.assertEqual(cmdline(task), proc_cmdline)
def test_sk_fullsock(self): with create_socket() as sock: file = fget(find_task(self.prog, os.getpid()), sock.fileno()) sk = cast("struct socket *", file.private_data).sk.read_() self.assertTrue(sk_fullsock(sk))
def test_thread(self): pid = os.getpid() thread = self.prog.thread(pid) self.assertEqual(thread.tid, pid) self.assertEqual(thread.object, find_task(self.prog, pid))
def test_cgroup_name(self): task = find_task(self.prog, os.getpid()) self.assertEqual( cgroup_name(task.cgroups.dfl_cgrp), os.path.basename(self.cgroup) )
def test_fget(self): with tempfile.NamedTemporaryFile(prefix="drgn-tests-") as f: file = fget(find_task(self.prog, os.getpid()), f.fileno()) self.assertEqual(d_path(file.f_path), os.fsencode(os.path.abspath(f.name)))
def test_dentry_path(self): pwd = os.fsencode(os.getcwd()) task = find_task(self.prog, os.getpid()) self.assertTrue(pwd.endswith(dentry_path(task.fs.pwd.dentry)))
def test_d_path(self): task = find_task(self.prog, os.getpid()) self.assertEqual(d_path(task.fs.pwd.address_of_()), os.fsencode(os.getcwd()))
def test_cgroup_path(self): task = find_task(self.prog, os.getpid()) self.assertEqual(cgroup_path(task.cgroups.dfl_cgrp), self.cgroup)
def test_kernfs_path(self): with open("/sys/kernel/vmcoreinfo", "r") as f: file = fget(find_task(self.prog, os.getpid()), f.fileno()) kn = cast("struct kernfs_node *", file.f_inode.i_private) self.assertEqual(kernfs_path(kn), b"/kernel/vmcoreinfo")