From 522f8ba31e0c6d7c5d5713ed23a3c48baca7cbf8 Mon Sep 17 00:00:00 2001 From: Eric Kilmer Date: Wed, 15 Sep 2021 09:13:48 -0400 Subject: [PATCH] Add methods for getting list of (un)implemented system calls (#2491) This is primarily a helper function that is useful in evaluating the capabilities of Manticore on Linux * Add Linux Kernel version constant to syscalls list * Add method for getting unimplemented syscalls This is specific per architecture --- manticore/platforms/linux.py | 38 ++++++++++++++++++++++ manticore/platforms/linux_syscalls.py | 6 ++-- scripts/extract_syscalls.py | 4 +-- tests/native/test_linux.py | 47 ++++++++++++++++++++++++++- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/manticore/platforms/linux.py b/manticore/platforms/linux.py index efc526f7e..2e1c44752 100644 --- a/manticore/platforms/linux.py +++ b/manticore/platforms/linux.py @@ -3316,6 +3316,44 @@ def _interp_total_size(interp): last = load_segs[-1] return last.header.p_vaddr + last.header.p_memsz + @classmethod + def implemented_syscalls(cls) -> Iterable[str]: + """ + Get a listing of all concretely implemented system calls for Linux. This + does not include whether a symbolic version exists. To get that listing, + use the SLinux.implemented_syscalls() method. + """ + import inspect + + return ( + name + for (name, obj) in inspect.getmembers(cls, predicate=inspect.isfunction) + if name.startswith("sys_") and + # Check that the class defining the method is exactly this one + getattr(inspect.getmodule(obj), obj.__qualname__.rsplit(".", 1)[0], None) == cls + ) + + @classmethod + def unimplemented_syscalls(cls, syscalls: Union[Set[str], Dict[int, str]]) -> Set[str]: + """ + Get a listing of all unimplemented concrete system calls for a given + collection of Linux system calls. To get a listing of unimplemented + symbolic system calls, use the ``SLinux.unimplemented_syscalls()`` + method. + + Available system calls can be found at ``linux_syscalls.py`` or you may + pass your own as either a set of system calls or as a mapping of system + call number to system call name. + + Note that passed system calls should follow the naming convention + located in ``linux_syscalls.py``. + """ + implemented_syscalls = set(cls.implemented_syscalls()) + if isinstance(syscalls, set): + return syscalls.difference(implemented_syscalls) + else: + return set(syscalls.values()).difference(implemented_syscalls) + ############################################################################ # Symbolic versions follows diff --git a/manticore/platforms/linux_syscalls.py b/manticore/platforms/linux_syscalls.py index 32579385b..af603bfda 100644 --- a/manticore/platforms/linux_syscalls.py +++ b/manticore/platforms/linux_syscalls.py @@ -2,8 +2,8 @@ # # AUTOGENERATED, DO NOT EDIT # -# From version: 4.11 -# + +LINUX_KERNEL_VERSION = "4.11" i386 = { 0: "sys_restart_syscall", @@ -426,7 +426,7 @@ 53: "sys_socketpair", 54: "sys_setsockopt", 55: "sys_getsockopt", - 56: "sys_clone_ptregs", + 56: "sys_clone/ptregs", 57: "sys_fork/ptregs", 58: "sys_vfork/ptregs", 59: "sys_execve/ptregs", diff --git a/scripts/extract_syscalls.py b/scripts/extract_syscalls.py index 2aa1048bf..1e8d67781 100755 --- a/scripts/extract_syscalls.py +++ b/scripts/extract_syscalls.py @@ -78,8 +78,8 @@ def write_without_includes(f, res): args = parser.parse_args() output = open(args.output, "w+") - output.write("#\n#\n# AUTOGENERATED, DO NOT EDIT\n#\n") - output.write(f"# From version: {args.linux_version}\n#\n\n") + output.write("#\n#\n# AUTOGENERATED, DO NOT EDIT\n#\n\n") + output.write(f'LINUX_KERNEL_VERSION = "{args.linux_version}"\n\n') for arch, path in ARCH_TABLES: url = BASE_URL.format(path, args.linux_version) diff --git a/tests/native/test_linux.py b/tests/native/test_linux.py index 028cac39c..8d23df0fd 100644 --- a/tests/native/test_linux.py +++ b/tests/native/test_linux.py @@ -15,7 +15,13 @@ from manticore.native import Manticore from manticore.platforms import linux, linux_syscalls from manticore.utils.helpers import pickle_dumps -from manticore.platforms.linux import EnvironmentError, logger as linux_logger, SymbolicFile +from manticore.platforms.linux import ( + EnvironmentError, + logger as linux_logger, + SymbolicFile, + Linux, + SLinux, +) class LinuxTest(unittest.TestCase): @@ -417,3 +423,42 @@ def post(state): m.run() self.assertTrue(m.context["success"]) + + def test_implemented_syscall_report(self) -> None: + concrete_syscalls = set(Linux.implemented_syscalls()) + symbolic_syscalls = set(SLinux.implemented_syscalls()) + + # Make sure at least one known syscall implementation appears in both + assert "sys_read" in concrete_syscalls + assert "sys_read" in symbolic_syscalls + + # Make sure an unimplemented syscall (taken from linux_syscall_stubs) + # does not appear in our list of concrete syscalls. This could change in + # the future + assert "sys_bpf" not in concrete_syscalls + + # Make sure that a concretely implemented syscall does not have a (at + # this time) symbolic equivalent. This could change in the future + assert "sys_tgkill" in concrete_syscalls + assert "sys_tgkill" not in symbolic_syscalls + + # This doesn't _need_ to be true, but our design says it should be true + assert symbolic_syscalls.issubset(concrete_syscalls) + + def test_unimplemented_syscall_report(self) -> None: + """This test is the inverse of test_implemented_syscall_report""" + from manticore.platforms.linux_syscalls import amd64 + + unimplemented_concrete_syscalls = set(Linux.unimplemented_syscalls(amd64)) + unimplemented_symbolic_syscalls = set(SLinux.unimplemented_syscalls(set(amd64.values()))) + + assert "sys_read" not in unimplemented_concrete_syscalls + assert "sys_read" not in unimplemented_symbolic_syscalls + + assert "sys_bpf" in unimplemented_concrete_syscalls + + assert "sys_tgkill" not in unimplemented_concrete_syscalls + assert "sys_tgkill" in unimplemented_symbolic_syscalls + + # This doesn't _need_ to be true, but our design says it should be true + assert unimplemented_concrete_syscalls.issubset(unimplemented_symbolic_syscalls)