Skip to content

Initial signal support for POSIX #1545

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

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions qiling/os/posix/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 10 additions & 2 deletions qiling/os/posix/posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -264,3 +268,7 @@ def shm(self):
@property
def msq(self):
return self._msq

@property
def sig(self):
return self._sig
178 changes: 170 additions & 8 deletions qiling/os/posix/syscall/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# )

# <WORKAROUND> see FIXME above
class sighandler_union(Struct):
_fields_ = (
('sa_handler', native_type),
)
# </WORKAROUND>

# 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
16 changes: 16 additions & 0 deletions qiling/os/posix/syscall/unistd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down