Skip to content
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
9 changes: 9 additions & 0 deletions qiling/os/posix/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 55 additions & 2 deletions qiling/os/posix/posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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_'
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -397,4 +446,8 @@ def fd(self):

@property
def shm(self):
return self._shm
return self._shm

@property
def msq(self):
return self._msq
1 change: 1 addition & 0 deletions qiling/os/posix/syscall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
163 changes: 163 additions & 0 deletions qiling/os/posix/syscall/msg.py
Original file line number Diff line number Diff line change
@@ -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'
]
26 changes: 25 additions & 1 deletion qiling/os/posix/syscall/syscall.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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:
Expand Down