diff --git a/.coveragerc b/.coveragerc index 37058bd541..a57809fa16 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,3 +2,11 @@ source = manticore omit = *__init__.py + +[report] +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't try to cover special syntax "..." in abstract class + @abstractmethod diff --git a/manticore/platforms/linux.py b/manticore/platforms/linux.py index bc2e88f016..0d7dd7ac4d 100644 --- a/manticore/platforms/linux.py +++ b/manticore/platforms/linux.py @@ -105,6 +105,30 @@ class creation time, you will get an error only if you try to instantiate return cls +@dataclass +class StatResult: + """ + Data structure corresponding to result received from stat, fstat, lstat for + information about a file. + + See https://man7.org/linux/man-pages/man2/stat.2.html for more info + """ + + st_mode: int + st_ino: int + st_dev: int + st_nlink: int + st_uid: int + st_gid: int + st_size: int + st_atime: int + st_mtime: int + st_ctime: int + st_blksize: int + st_blocks: int + st_rdev: int + + class FdLike(ABC): """ An abstract class for different kinds of file descriptors. @@ -142,6 +166,10 @@ def ioctl(self, request, argp) -> int: def tell(self) -> int: ... + @abstractmethod + def stat(self) -> StatResult: + ... + @dataclass class FdTableEntry: @@ -277,9 +305,9 @@ def closed(self) -> bool: def stat(self): try: - return os.fstat(self.fileno()) + return os.stat(self.fileno()) except OSError as e: - return -e.errno + raise FdError(f"Cannot stat: {e.strerror}", e.errno) def ioctl(self, request, argp): try: @@ -375,6 +403,12 @@ def close(self): except OSError as e: return -e.errno + def stat(self): + try: + return os.stat(self.fileno()) + except OSError as e: + raise FdError(f"Cannot stat: {e.strerror}", e.errno) + def fileno(self): return self.fd @@ -541,31 +575,17 @@ def ioctl(self, request, argp): def tell(self) -> int: raise FdError("Invalid tell() operation on SocketDesc", errno.EBADF) + def stat(self) -> StatResult: + # Copied from Socket.stat + return StatResult( + 8592, 11, 9, 1, 1000, 5, 0, 1378673920, 1378673920, 1378653796, 0x400, 0x8808, 0 + ) + @concreteclass class Socket(FdLike): def stat(self): - from collections import namedtuple - - stat_result = namedtuple( - "stat_result", - [ - "st_mode", - "st_ino", - "st_dev", - "st_nlink", - "st_uid", - "st_gid", - "st_size", - "st_atime", - "st_mtime", - "st_ctime", - "st_blksize", - "st_blocks", - "st_rdev", - ], - ) - return stat_result( + return StatResult( 8592, 11, 9, 1, 1000, 5, 0, 1378673920, 1378673920, 1378653796, 0x400, 0x8808, 0 ) @@ -3051,8 +3071,10 @@ def sys_getdents(self, fd, dirent, count) -> int: # Don't overflow buffer break - stat = item.stat() - print(f"FILE MODE: {item.name} :: {stat.st_mode:o}") + try: + stat = item.stat() + except FdError as e: + return -e.err # https://elixir.bootlin.com/linux/v5.1.15/source/include/linux/fs_types.h#L27 d_type = (stat.st_mode >> 12) & 15 diff --git a/tests/native/test_syscalls.py b/tests/native/test_syscalls.py index d5e96091a0..a73a1744ae 100644 --- a/tests/native/test_syscalls.py +++ b/tests/native/test_syscalls.py @@ -91,6 +91,180 @@ def test_directories(self): self.linux.sys_rmdir(0x1100) self.assertFalse(os.path.exists(dname)) + def test_dir_stat(self): + dname = self.get_path("test_dir_stat") + self.assertFalse(os.path.exists(dname)) + + self.linux.current.memory.mmap(0x1000, 0x1000, "rw") + self.linux.current.write_string(0x1100, dname) + + # Create it as a dir + self.linux.sys_mkdir(0x1100, mode=0o777) + fd = self.linux.sys_open(0x1100, flags=os.O_RDONLY | os.O_DIRECTORY, mode=0o777) + self.assertTrue(os.path.exists(dname)) + self.assertGreater(fd, 0) + + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Remove from file system on host but not in Manticore + os.rmdir(dname) + self.assertFalse(os.path.exists(dname)) + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertLess(res, 0) + # The file descriptor is still valid even though the directory is gone + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Remove the directory using Manticore + self.linux.sys_rmdir(0x1100) + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertLess(res, 0) + # The file descriptor is still valid even though the directory is gone + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Close the file descriptor to totally remove it + self.linux.sys_close(fd) + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertLess(res, 0) + + def test_file_stat(self): + fname = self.get_path("test_file_stat") + self.assertFalse(os.path.exists(fname)) + + self.linux.current.memory.mmap(0x1000, 0x1000, "rw") + self.linux.current.write_string(0x1100, fname) + + # Create a file + fd = self.linux.sys_open(0x1100, os.O_RDWR, 0o777) + self.assertTrue(os.path.exists(fname)) + + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Remove from file system on host but not in Manticore + os.remove(fname) + self.assertFalse(os.path.exists(fname)) + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Remove the file using Manticore + self.linux.sys_unlink(0x1100) + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Close the file descriptor to totally remove it + self.linux.sys_close(fd) + res = self.linux.sys_stat32(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_stat64(0x1100, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertLess(res, 0) + + def test_socketdesc_stat(self): + self.linux.current.memory.mmap(0x1000, 0x1000, "rw") + + # Create a socket + fd = self.linux.sys_socket(socket.AF_INET, socket.SOCK_STREAM, 0) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertEqual(res, 0) + + # Close the socket + self.linux.sys_close(fd) + res = self.linux.sys_newfstat(fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat(fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat64(fd, 0x1200) + self.assertLess(res, 0) + + def test_socket_stat(self): + self.linux.current.memory.mmap(0x1000, 0x1000, "rw") + + # Create a socket + sock_fd = self.linux.sys_socket(socket.AF_INET, socket.SOCK_STREAM, 0) + self.linux.sys_bind(sock_fd, None, None) + self.linux.sys_listen(sock_fd, None) + conn_fd = self.linux.sys_accept(sock_fd, None, 0) + + res = self.linux.sys_newfstat(conn_fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat(conn_fd, 0x1200) + self.assertEqual(res, 0) + res = self.linux.sys_fstat64(conn_fd, 0x1200) + self.assertEqual(res, 0) + + # Close the socket + self.linux.sys_close(conn_fd) + res = self.linux.sys_newfstat(conn_fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat(conn_fd, 0x1200) + self.assertLess(res, 0) + res = self.linux.sys_fstat64(conn_fd, 0x1200) + self.assertLess(res, 0) + def test_pipe(self): self.linux.current.memory.mmap(0x1000, 0x1000, "rw") self.linux.sys_pipe(0x1100)