From f912611530bdab73384d692c43fa9c06186ed7a4 Mon Sep 17 00:00:00 2001 From: elicn Date: Sat, 8 Mar 2025 22:01:17 +0200 Subject: [PATCH] Initial signal support --- qiling/os/posix/const.py | 3 + qiling/os/posix/posix.py | 12 +- qiling/os/posix/syscall/signal.py | 178 ++++++++++++++++++++++++++++-- qiling/os/posix/syscall/unistd.py | 16 +++ 4 files changed, 199 insertions(+), 10 deletions(-) diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index a03f68eaa..3f81e1d53 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -17,6 +17,9 @@ # File Open Limits NR_OPEN = 1024 +# number of signals +NSIG = 32 + SOCK_TYPE_MASK = 0x0f class linux_x86_socket_types(Enum): diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index 1550ef31f..a0a7b6290 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -10,7 +10,7 @@ from qiling.const import QL_ARCH, QL_INTERCEPT from qiling.exception import QlErrorSyscallNotFound from qiling.os.os import QlOs -from qiling.os.posix.const import NR_OPEN, errors +from qiling.os.posix.const import NR_OPEN, NSIG, errors from qiling.os.posix.msq import QlMsq from qiling.os.posix.shm import QlShm from qiling.os.posix.syscall.abi import QlSyscallABI, arm, intel, mips, ppc, riscv @@ -49,7 +49,6 @@ class QlOsPosix(QlOs): def __init__(self, ql: Qiling): super().__init__(ql) - self.sigaction_act = [0] * 256 conf = self.profile['KERNEL'] self.uid = self.euid = conf.getint('uid') @@ -92,6 +91,11 @@ def __init__(self, ql: Qiling): self._shm = QlShm() self._msq = QlMsq() + self._sig = [None] * NSIG + + # a bitmap representing the blocked signals. a set bit at index i means signal i is blocked. + # note that SIGKILL and SIGSTOP cannot be blocked. + self.blocked_signals = 0 def __get_syscall_mapper(self, archtype: QL_ARCH): qlos_path = f'.os.{self.type.name.lower()}.map_syscall' @@ -264,3 +268,7 @@ def shm(self): @property def msq(self): return self._msq + + @property + def sig(self): + return self._sig diff --git a/qiling/os/posix/syscall/signal.py b/qiling/os/posix/syscall/signal.py index c0e4583a7..1591cb558 100644 --- a/qiling/os/posix/syscall/signal.py +++ b/qiling/os/posix/syscall/signal.py @@ -3,27 +3,189 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from qiling import Qiling +from __future__ import annotations + +import ctypes +from typing import TYPE_CHECKING, Type + +from qiling.const import QL_ARCH +from qiling.os import struct +from qiling.os.posix.const import NSIG + +# TODO: MIPS differs in too many details around signals; MIPS implementation is better extracted out + +if TYPE_CHECKING: + from qiling import Qiling + from qiling.arch.arch import QlArch + + +@struct.cache +def __make_sigset(arch: QlArch): + native_type = struct.get_native_type(arch.bits) + + sigset_type = { + QL_ARCH.X86: native_type, + QL_ARCH.X8664: native_type, + QL_ARCH.ARM: native_type, + QL_ARCH.ARM64: native_type, + QL_ARCH.MIPS: ctypes.c_uint32 * (128 // (4 * 8)), + QL_ARCH.CORTEX_M: native_type + } + + if arch.type not in sigset_type: + raise NotImplementedError(f'sigset definition is missing for {arch.type.name}') + + return sigset_type[arch.type] + + +@struct.cache +def __make_sigaction(arch: QlArch) -> Type[struct.BaseStruct]: + native_type = struct.get_native_type(arch.bits) + Struct = struct.get_aligned_struct(arch.bits, arch.endian) + + sigset_type = __make_sigset(arch) + + # # FIXME: untill python 3.11 ctypes Union does not support an endianess that is different from + # the hosting paltform. if a LE system is emulating a BE one or vice versa, this will fail. to + # work around that we avoid using a union and refer to the inner field as 'sa_handler' regardless. + # + # Union = struct.get_aligned_union(arch.bits) + # + # class sighandler_union(Union): + # _fields_ = ( + # ('sa_handler', native_type), + # ('sa_sigaction', native_type) + # ) + + # see FIXME above + class sighandler_union(Struct): + _fields_ = ( + ('sa_handler', native_type), + ) + # + + # see: https://elixir.bootlin.com/linux/v5.19.17/source/arch/arm/include/uapi/asm/signal.h + class arm_sigaction(Struct): + _anonymous_ = ('_u',) + + _fields_ = ( + ('_u', sighandler_union), + ('sa_mask', sigset_type), + ('sa_flags', native_type), + ('sa_restorer', native_type) + ) + + # see: https://elixir.bootlin.com/linux/v5.19.17/source/arch/x86/include/uapi/asm/signal.h + class x86_sigaction(Struct): + _anonymous_ = ('_u',) + + _fields_ = ( + ('_u', sighandler_union), + ('sa_mask', sigset_type), + ('sa_flags', native_type), + ('sa_restorer', native_type) + ) + + class x8664_sigaction(Struct): + _fields_ = ( + ('sa_handler', native_type), + ('sa_flags', native_type), + ('sa_restorer', native_type), + ('sa_mask', sigset_type) + ) + + # see: https://elixir.bootlin.com/linux/v5.19.17/source/arch/mips/include/uapi/asm/signal.h + class mips_sigaction(Struct): + _fields_ = ( + ('sa_flags', ctypes.c_uint32), + ('sa_handler', native_type), + ('sa_mask', sigset_type) + ) + + sigaction_struct = { + QL_ARCH.X86: x86_sigaction, + QL_ARCH.X8664: x8664_sigaction, + QL_ARCH.ARM: arm_sigaction, + QL_ARCH.ARM64: arm_sigaction, + QL_ARCH.MIPS: mips_sigaction, + QL_ARCH.CORTEX_M: arm_sigaction + } + + if arch.type not in sigaction_struct: + raise NotImplementedError(f'sigaction definition is missing for {arch.type.name}') + + return sigaction_struct[arch.type] + def ql_syscall_rt_sigaction(ql: Qiling, signum: int, act: int, oldact: int): + SIGKILL = 9 + SIGSTOP = 23 if ql.arch.type is QL_ARCH.MIPS else 19 + + if signum not in range(NSIG) or signum in (SIGKILL, SIGSTOP): + return -1 # EINVAL + + sigaction = __make_sigaction(ql.arch) + if oldact: - arr = ql.os.sigaction_act[signum] or [0] * 5 - data = b''.join(ql.pack32(key) for key in arr) + old = ql.os.sig[signum] or sigaction() - ql.mem.write(oldact, data) + old.save_to(ql.mem, oldact) if act: - ql.os.sigaction_act[signum] = [ql.mem.read_ptr(act + 4 * i, 4) for i in range(5)] + ql.os.sig[signum] = sigaction.load_from(ql.mem, act) return 0 -def ql_syscall_rt_sigprocmask(ql: Qiling, how: int, nset: int, oset: int, sigsetsize: int): - # SIG_BLOCK = 0x0 - # SIG_UNBLOCK = 0x1 +def __sigprocmask(ql: Qiling, how: int, newset: int, oldset: int): + SIG_BLOCK = 0 + SIG_UNBLOCK = 1 + SIG_SETMASK = 2 + + SIGKILL = 9 + SIGSTOP = 19 + + if oldset: + ql.mem.write_ptr(newset, ql.os.blocked_signals) + + if newset: + set_mask = ql.mem.read_ptr(newset) + + if how == SIG_BLOCK: + ql.os.blocked_signals |= set_mask + + elif how == SIG_UNBLOCK: + ql.os.blocked_signals &= ~set_mask + + elif how == SIG_SETMASK: + ql.os.blocked_signals = set_mask + else: + return -1 # EINVAL + + # silently drop attempts to block SIGKILL and SIGSTOP + ql.os.blocked_signals &= ~((1 << SIGKILL) | (1 << SIGSTOP)) + + return 0 + + +def __sigprocmask_mips(ql: Qiling, how: int, newset: int, oldset: int): + SIG_BLOCK = 1 + SIG_UNBLOCK = 2 + SIG_SETMASK = 3 + + SIGKILL = 9 + SIGSTOP = 23 + + # TODO: to implement return 0 +def ql_syscall_rt_sigprocmask(ql: Qiling, how: int, newset: int, oldset: int): + impl = __sigprocmask_mips if ql.arch.type is QL_ARCH.MIPS else __sigprocmask + + return impl(ql, how, newset, oldset) + + def ql_syscall_signal(ql: Qiling, sig: int, sighandler: int): return 0 diff --git a/qiling/os/posix/syscall/unistd.py b/qiling/os/posix/syscall/unistd.py index 62eab143c..8931f95e9 100644 --- a/qiling/os/posix/syscall/unistd.py +++ b/qiling/os/posix/syscall/unistd.py @@ -152,6 +152,22 @@ def ql_syscall_capset(ql: Qiling, hdrp: int, datap: int): def ql_syscall_kill(ql: Qiling, pid: int, sig: int): + if sig not in range(NSIG): + return -1 # EINVAL + + if pid > 0 and pid != ql.os.pid: + return -1 # ESRCH + + sigaction = ql.os.sig[sig] + + # sa_handler is: + # SIG_DFL for the default action. + # SIG_IGN to ignore this signal. + # handler pointer + + # if sa_flags & SA_SIGINFO: + # call sa_sigaction instead of sa_handler + return 0