Merge pull request #1363 from chinggg/ipc-msg

feat(os): add posix message queue syscalls
This commit is contained in:
xwings
2023-07-04 09:58:26 +08:00
committed by GitHub
5 changed files with 253 additions and 3 deletions
+9
View File
@@ -1025,6 +1025,15 @@ SHMMNI = 4096 # max num of segs system wide
HUGETLB_FLAG_ENCODE_SHIFT = 26 HUGETLB_FLAG_ENCODE_SHIFT = 26
HUGETLB_FLAG_ENCODE_MASK = 0x3f 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 # ipc syscall
SEMOP = 1 SEMOP = 1
SEMGET = 2 SEMGET = 2
+55 -2
View File
@@ -3,6 +3,7 @@
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework # Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# #
from collections import deque
from inspect import signature, Parameter from inspect import signature, Parameter
from typing import Dict, TextIO, Tuple, Union, Callable, IO, List, Optional from typing import Dict, TextIO, Tuple, Union, Callable, IO, List, Optional
@@ -26,7 +27,7 @@ from qiling.cc import QlCC, intel, arm, mips, riscv, ppc
from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT from qiling.const import QL_ARCH, QL_OS, QL_INTERCEPT
from qiling.exception import QlErrorSyscallNotFound from qiling.exception import QlErrorSyscallNotFound
from qiling.os.os import QlOs 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 from qiling.utils import ql_get_module, ql_get_module_function
SYSCALL_PREF: str = f'ql_syscall_' SYSCALL_PREF: str = f'ql_syscall_'
@@ -136,6 +137,53 @@ class QlShm:
return next((shmobj for shmobj in self.__shm.values() if shmobj.attach.count(shmaddr) > 0), None) 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): class QlOsPosix(QlOs):
def __init__(self, ql: Qiling): def __init__(self, ql: Qiling):
@@ -203,6 +251,7 @@ class QlOsPosix(QlOs):
self.stderr = self._stderr self.stderr = self._stderr
self._shm = QlShm() self._shm = QlShm()
self._msq = QlMsq()
def __get_syscall_mapper(self, archtype: QL_ARCH): def __get_syscall_mapper(self, archtype: QL_ARCH):
qlos_path = f'.os.{self.type.name.lower()}.map_syscall' qlos_path = f'.os.{self.type.name.lower()}.map_syscall'
@@ -397,4 +446,8 @@ class QlOsPosix(QlOs):
@property @property
def shm(self): def shm(self):
return self._shm return self._shm
@property
def msq(self):
return self._msq
+1
View File
@@ -6,6 +6,7 @@ from .fcntl import *
from .futex import * from .futex import *
from .ioctl import * from .ioctl import *
from .mman import * from .mman import *
from .msg import *
from .net import * from .net import *
from .personality import * from .personality import *
from .poll import * from .poll import *
+163
View File
@@ -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'
]
+25 -1
View File
@@ -7,6 +7,7 @@ from qiling import Qiling
from qiling.os.posix.const import * from qiling.os.posix.const import *
from .shm 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): def ql_syscall_ipc(ql: Qiling, call: int, first: int, second: int, third: int, ptr: int, fifth: int):
@@ -27,11 +28,34 @@ def ql_syscall_ipc(ql: Qiling, call: int, first: int, second: int, third: int, p
def __call_shmget(*args: int) -> int: def __call_shmget(*args: int) -> int:
return ql_syscall_shmget(ql, args[0], args[1], args[2]) 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 = { ipc_call = {
SHMAT: __call_shmat, SHMAT: __call_shmat,
SHMDT: __call_shmdt, SHMDT: __call_shmdt,
SHMGET: __call_shmget SHMGET: __call_shmget,
MSGGET: __call_msgget,
MSGSND: __call_msgsnd,
MSGRCV: __call_msgrcv
} }
if call not in ipc_call: if call not in ipc_call: