Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux: Add stat method for FdLike #1780

Merged
merged 10 commits into from
Aug 11, 2020
8 changes: 8 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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
72 changes: 47 additions & 25 deletions manticore/platforms/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -142,6 +166,10 @@ def ioctl(self, request, argp) -> int:
def tell(self) -> int:
...

@abstractmethod
def stat(self) -> StatResult:
...


@dataclass
class FdTableEntry:
Expand Down Expand Up @@ -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)
bradlarsen marked this conversation as resolved.
Show resolved Hide resolved

def ioctl(self, request, argp):
try:
Expand Down Expand Up @@ -375,6 +403,12 @@ def close(self):
except OSError as e:
return -e.errno

def stat(self):
ekilmer marked this conversation as resolved.
Show resolved Hide resolved
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

Expand Down Expand Up @@ -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
)

Expand Down Expand Up @@ -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
Expand Down
174 changes: 174 additions & 0 deletions tests/native/test_syscalls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down