diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index 103559f02..a9abc7abd 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -1025,6 +1025,15 @@ class qnx_mmap_flags(Flag): HUGETLB_FLAG_ENCODE_SHIFT = 26 HUGETLB_FLAG_ENCODE_MASK = 0x3f +# see: https://elixir.bootlin.com/linux/v5.19.17/source/include/uapi/linux/msg.h +MSG_NOERROR = 0o10000 # no error if message is too big +MSG_EXCEPT = 0o20000 # recv any msg except of specified type +MSG_COPY = 0o40000 # copy (not remove) all queue messages + +MSGMNI = 32000 # <= IPCMNI, max # of msg queue identifiers +MSGMAX = 8192 # <= INT_MAX, max size of message (bytes) +MSGMNB = 16384 # <= INT_MAX, default max size of a message queue + # ipc syscall SEMOP = 1 SEMGET = 2 diff --git a/qiling/os/posix/posix.py b/qiling/os/posix/posix.py index e7e609b98..c98948e55 100644 --- a/qiling/os/posix/posix.py +++ b/qiling/os/posix/posix.py @@ -3,6 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from collections import deque from inspect import signature, Parameter from typing import Dict, TextIO, Tuple, Union, Callable, IO, List, Optional @@ -26,7 +27,7 @@ from qiling.const import QL_ARCH, QL_OS, 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 MSGMNB, NR_OPEN, errors from qiling.utils import ql_get_module, ql_get_module_function SYSCALL_PREF: str = f'ql_syscall_' @@ -136,6 +137,53 @@ def get_by_attaddr(self, shmaddr: int) -> Optional[QlShmId]: return next((shmobj for shmobj in self.__shm.values() if shmobj.attach.count(shmaddr) > 0), None) +class QlMsgBuf: + def __init__(self, mtype: int, mtext: bytes) -> None: + self.mtype = mtype + self.mtext = mtext + + +# vaguely reflects a msqid64_ds structure +class QlMsqId: + def __init__(self, key: int, uid: int, gid: int, mode: int) -> None: + # ipc64_perm + self.key = key + self.uid = uid + self.gid = gid + self.mode = mode + + self.queue = deque(maxlen=MSGMNB) + + def __len__(self): + return len(self.queue) + + +class QlMsq: + def __init__(self) -> None: + self.__msq: Dict[int, QlMsqId] = {} + self.__id: int = 0x0F000000 + + def __len__(self) -> int: + return len(self.__msq) + + def add(self, msq: QlMsqId) -> int: + msqid = self.__id + self.__msq[msqid] = msq + + self.__id += 0x1000 + + return msqid + + def remove(self, msqid: int) -> None: + del self.__msq[msqid] + + def get_by_key(self, key: int) -> Tuple[int, Optional[QlMsqId]]: + return next(((msqid, msqobj) for msqid, msqobj in self.__msq.items() if msqobj.key == key), (-1, None)) + + def get_by_id(self, msqid: int) -> Optional[QlMsqId]: + return self.__msq.get(msqid, None) + + class QlOsPosix(QlOs): def __init__(self, ql: Qiling): @@ -203,6 +251,7 @@ def __init__(self, ql: Qiling): self.stderr = self._stderr self._shm = QlShm() + self._msq = QlMsq() def __get_syscall_mapper(self, archtype: QL_ARCH): qlos_path = f'.os.{self.type.name.lower()}.map_syscall' @@ -397,4 +446,8 @@ def fd(self): @property def shm(self): - return self._shm \ No newline at end of file + return self._shm + + @property + def msq(self): + return self._msq diff --git a/qiling/os/posix/syscall/__init__.py b/qiling/os/posix/syscall/__init__.py index a8c25d18f..38b10e64e 100644 --- a/qiling/os/posix/syscall/__init__.py +++ b/qiling/os/posix/syscall/__init__.py @@ -6,6 +6,7 @@ from .futex import * from .ioctl import * from .mman import * +from .msg import * from .net import * from .personality import * from .poll import * diff --git a/qiling/os/posix/syscall/msg.py b/qiling/os/posix/syscall/msg.py new file mode 100644 index 000000000..f944ca62a --- /dev/null +++ b/qiling/os/posix/syscall/msg.py @@ -0,0 +1,163 @@ +from typing import Optional +from qiling import Qiling +from qiling.os.posix.const import * +from qiling.os.posix.posix import QlMsqId, QlMsgBuf + + +def __find_msg(msq: QlMsqId, msgtyp: int, msgflg: int) -> Optional[QlMsgBuf]: + # peek at a specific queue item + if msgflg & MSG_COPY: + if msgtyp >= len(msq.queue): + return -1 # ENOMSG + + return msg.queue[msgtyp] + + if msgtyp == 0: + predicate = lambda msg: True + + elif msgtype > 0: + if msgflg & MSG_EXCEPT: + predicate = lambda msg: msg.mtype != msgtyp + else: + predicate = lambda msg: msg.mtype == msgtyp + + elif msgtype < 0: + predicate = lambda msg: msg.mtype <= -msgtyp + + return next((msg for msg in msq.queue if predicate(msg)), None) + + +def __perms(ql: Qiling, msq: QlMsqId, flag: int) -> int: + """ + # see: https://elixir.bootlin.com/linux/v5.19.17/source/ipc/util.c#L553 + # check whether the user has permissions to access this message queue + # FIXME: should probably also use cuid and (c)gid, but we don't support it yet + # TODO: other ipc mechanisms like shm can also reuse this + """ + + request_mode = (flag >> 6) | (flag >> 3) | flag + granted_mode = msq.mode + + if ql.os.uid == msq.uid: + granted_mode >>= 6 + + # is there some bit set in requested_mode but not in granted_mode? + if request_mode & ~granted_mode & 0o007: + return -1 # EACCES + + return 0 + +def ql_syscall_msgget(ql: Qiling, key: int, msgflg: int): + def __create_msq(key: int, flags: int) -> int: + """Create a new message queue for the specified key. + + Returns: msqid of the newly created message queue, -1 if an error has occurred + """ + + if len(ql.os.msq) >= MSGMNI: + return -1 # ENOSPC + + mode = flags & ((1 << 9) - 1) + + msqid = ql.os.msq.add(QlMsqId(key, ql.os.uid, ql.os.gid, mode)) + + ql.log.debug(f'created a new msg queue: key = {key:#x}, mode = 0{mode:o}. assigned id: {msqid:#x}') + + return msqid + + # create new message queue + if key == IPC_PRIVATE: + msqid = __create_msq(key, msgflg) + + else: + msqid, msq = ql.os.msq.get_by_key(key) + + # a message queue with the specified key does not exist + if msq is None: + # the user asked to create a new one? + if msgflg & IPC_CREAT: + msqid = __create_msq(key, msgflg) + + else: + return -1 # ENOENT + + # a message queue with the specified key exists + else: + # the user asked to create a new one? + if msgflg & (IPC_CREAT | IPC_EXCL): + return -1 # EEXIST + + if __perms(ql, msq, msgflg): + return -1 # EACCES + + return msqid + +def ql_syscall_msgsnd(ql: Qiling, msqid: int, msgp: int, msgsz: int, msgflg: int): + msq = ql.os.msq.get_by_id(msqid) + + if msq is None: + return -1 # EINVAL + + # Check if the user has write permissions for the message queue + if __perms(ql, msq, 0o222): # S_IWUGO + return -1 # EACCES + + msg_type = ql.mem.read_ptr(msgp) + msg_text = ql.mem.read(msgp + ql.arch.pointersize, msgsz) + + while True: + if len(msq.queue) < msq.queue.maxlen: + break + + if msgflg & IPC_NOWAIT: + return -1 # EAGAIN + + msq.queue.append(QlMsgBuf(msg_type, bytes(msg_text))) + + return 0 # Success + + +def ql_syscall_msgrcv(ql: Qiling, msqid: int, msgp: int, msgsz: int, msgtyp: int, msgflg: int): + msq = ql.os.msq.get_by_id(msqid) + + if msq is None: + return -1 # EINVAL + + if msgflg & MSG_COPY: + if msgflg & MSG_EXCEPT or not (msgflg & IPC_NOWAIT): + return -1 # EINVAL + + # Check if the user has read permissions for the message queue + if __perms(ql, msq, 0o444): # S_IRUGO + return -1 # EACCES + + while True: + msg = __find_msg(msq, msgtyp, msgflg) + + if msg is not None: + break + + if msgflg & IPC_NOWAIT: + return -1 # ENOMSG + + if not (msgflg & MSG_COPY): + msq.queue.remove(msg) + + if msgsz < len(msg.mtext): + if not (msgflg & MSG_NOERROR): + return -1 # E2BIG + else: + sz = msgsz + else: + sz = len(msg.mtext) + + ql.mem.write_ptr(msgp, msg.mtype) + ql.mem.write(msgp + ql.arch.pointersize, msg.mtext[:sz]) + + return sz # Success + +__all__ = [ + 'ql_syscall_msgget', + 'ql_syscall_msgsnd', + 'ql_syscall_msgrcv' +] diff --git a/qiling/os/posix/syscall/syscall.py b/qiling/os/posix/syscall/syscall.py index 3aba66931..2a3c10922 100644 --- a/qiling/os/posix/syscall/syscall.py +++ b/qiling/os/posix/syscall/syscall.py @@ -7,6 +7,7 @@ from qiling.os.posix.const import * from .shm import * +from .msg import * def ql_syscall_ipc(ql: Qiling, call: int, first: int, second: int, third: int, ptr: int, fifth: int): @@ -27,11 +28,34 @@ def __call_shmdt(*args: int) -> int: def __call_shmget(*args: int) -> int: return ql_syscall_shmget(ql, args[0], args[1], args[2]) + + def __call_msgget(*args: int) -> int: + return ql_syscall_msgget(ql, args[0], args[1]) + + def __call_msgsnd(*args: int) -> int: + return ql_syscall_msgsnd(ql, args[0], args[3], args[1], args[2]) + + def __call_msgrcv(*args: int) -> int: + if version == 0: + if args[3] == 0: + return -1 # EINVAL + + msgp = ql.mem.read_ptr(args[3]) + msgtyp = ql.mem.read_ptr(args[3] + ql.arch.pointersize) + + else: + msgp = args[3] + msgtyp = args[4] + + return ql_syscall_msgrcv(ql, args[0], msgp, args[1], msgtyp, args[2]) ipc_call = { SHMAT: __call_shmat, SHMDT: __call_shmdt, - SHMGET: __call_shmget + SHMGET: __call_shmget, + MSGGET: __call_msgget, + MSGSND: __call_msgsnd, + MSGRCV: __call_msgrcv } if call not in ipc_call: