From 85e4cccceb36027fe054e2bb856e9a6acaa50e35 Mon Sep 17 00:00:00 2001 From: elicn Date: Thu, 15 Dec 2022 20:17:01 +0200 Subject: [PATCH] Reimplement POSIX mmap --- qiling/os/posix/const.py | 83 ++++++++++++++ qiling/os/posix/syscall/mman.py | 188 +++++++++++++++++++------------- 2 files changed, 197 insertions(+), 74 deletions(-) diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index 4aea9b76..9e23881e 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -3,6 +3,8 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +from enum import Flag + # OS Threading Constants THREAD_EVENT_INIT_VAL = 0 THREAD_EVENT_EXIT_EVENT = 1 @@ -622,6 +624,87 @@ qnx_arm64_open_flags = { 'O_BINARY' : None } + +# see: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h +class linux_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_ANONYMOUS = 0x00000020 + MAP_GROWSDOWN = 0x00000100 + MAP_DENYWRITE = 0x00000800 + MAP_EXECUTABLE = 0x00001000 + MAP_LOCKED = 0x00002000 + MAP_NORESERVE = 0x00004000 + MAP_POPULATE = 0x00008000 + MAP_NONBLOCK = 0x00010000 + MAP_STACK = 0x00020000 + MAP_HUGETLB = 0x00040000 + MAP_SYNC = 0x00080000 + MAP_FIXED_NOREPLACE = 0x00100000 + MAP_UNINITIALIZED = 0x04000000 + + +# see: https://github.com/torvalds/linux/blob/master/arch/mips/include/uapi/asm/mman.h +class mips_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_NORESERVE = 0x00000400 + MAP_ANONYMOUS = 0x00000800 + MAP_GROWSDOWN = 0x00001000 + MAP_DENYWRITE = 0x00002000 + MAP_EXECUTABLE = 0x00004000 + MAP_LOCKED = 0x00008000 + MAP_POPULATE = 0x00010000 + MAP_NONBLOCK = 0x00020000 + MAP_STACK = 0x00040000 + MAP_HUGETLB = 0x00080000 + MAP_FIXED_NOREPLACE = 0x00100000 + + +# see: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/mman.h +class macos_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_RENAME = 0x00000020 + MAP_NORESERVE = 0x00000040 + MAP_NOEXTEND = 0x00000100 + MAP_HASSEMAPHORE = 0x00000200 + MAP_NOCACHE = 0x00000400 + MAP_JIT = 0x00000800 + MAP_ANONYMOUS = 0x00001000 + + +# see: https://github.com/vocho/openqnx/blob/master/trunk/lib/c/public/sys/mman.h +class qnx_mmap_flags(Flag): + MAP_FILE = 0x00000000 + MAP_SHARED = 0x00000001 + MAP_PRIVATE = 0x00000002 + + MAP_FIXED = 0x00000010 + MAP_ELF = 0x00000020 + MAP_NOSYNCFILE = 0x00000040 + MAP_LAZY = 0x00000080 + MAP_STACK = 0x00001000 + MAP_BELOW = 0x00002000 + MAP_NOINIT = 0x00004000 + MAP_PHYS = 0x00010000 + MAP_NOX64K = 0x00020000 + MAP_BELOW16M = 0x00040000 + MAP_ANONYMOUS = 0x00080000 + MAP_SYSRAM = 0x01000000 + + # define this alias for compatibility with other os flags + MAP_UNINITIALIZED = MAP_NOINIT + # fcntl flags F_DUPFD = 0 F_GETFD = 1 diff --git a/qiling/os/posix/syscall/mman.py b/qiling/os/posix/syscall/mman.py index 4c9a4edc..6870cce7 100755 --- a/qiling/os/posix/syscall/mman.py +++ b/qiling/os/posix/syscall/mman.py @@ -4,8 +4,11 @@ # import os +from enum import IntFlag +from typing import Optional from qiling import Qiling +from qiling.const import QL_ARCH, QL_OS from qiling.exception import QlMemoryMappedError from qiling.os.filestruct import ql_file from qiling.os.posix.const_mapping import * @@ -53,97 +56,131 @@ def ql_syscall_mprotect(ql: Qiling, start: int, mlen: int, prot: int): return 0 -def syscall_mmap_impl(ql: Qiling, addr: int, mlen: int, prot: int, flags: int, fd: int, pgoffset: int, ver: int): - MAP_FAILED = -1 - MAP_SHARED = 0x01 - MAP_FIXED = 0x10 - MAP_ANONYMOUS = 0x20 +def syscall_mmap_impl(ql: Qiling, addr: int, length: int, prot: int, flags: int, fd: int, pgoffset: int, ver: int): + def __select_mmap_flags(archtype: QL_ARCH, ostype: QL_OS): + """The mmap flags definitions differ between operating systems and architectures. + This method provides the apropriate flags set based on those properties. + """ + + osflags = { + QL_OS.LINUX: mips_mmap_flags if archtype == QL_ARCH.MIPS else linux_mmap_flags, + QL_OS.FREEBSD: linux_mmap_flags, # FIXME: need one for freebsd + QL_OS.MACOS: macos_mmap_flags, + QL_OS.QNX: qnx_mmap_flags # FIXME: only for arm? + }[ostype] + + class mmap_flags(IntFlag): + MAP_FILE = osflags.MAP_FILE.value + MAP_SHARED = osflags.MAP_SHARED.value + MAP_FIXED = osflags.MAP_FIXED.value + MAP_ANONYMOUS = osflags.MAP_ANONYMOUS.value + + # the following flags do not exist on all flags sets. + # if not exists, set it to 0 so it would fail all bitwise-and checks + MAP_FIXED_NOREPLACE = osflags.MAP_FIXED_NOREPLACE.value if hasattr(osflags, 'MAP_FIXED_NOREPLACE') else 0 + MAP_UNINITIALIZED = osflags.MAP_UNINITIALIZED.value if hasattr(osflags, 'MAP_UNINITIALIZED') else 0 + + return mmap_flags + + api_name = ('old_mmap', 'mmap', 'mmap2')[ver] + mmap_flags = __select_mmap_flags(ql.arch.type, ql.os.type) pagesize = ql.mem.pagesize - api_name = { - 0 : 'old_mmap', - 1 : 'mmap', - 2 : 'mmap2' - }[ver] + mapping_size = ql.mem.align_up(length + (addr & (pagesize - 1))) - if ql.arch.bits == 64: - fd = ql.unpack64(ql.pack64(fd)) + ################################ + # determine mapping boundaries # + ################################ - elif ql.arch.type == QL_ARCH.MIPS: - MAP_ANONYMOUS = 2048 - if ver == 2: - pgoffset = pgoffset * pagesize + # as opposed to other systems, QNX allows specifying an unaligned base address + # for fixed mappings. to keep it consistent across all systems we allocate the + # enclosing pages of the requested area, while returning the requested fixed + # base address. + # + # to help track this, we use the following variables: + # addr : the address that becomes available, from program point of view + # lbound : lower bound of actual mapping range (aligned to page) + # ubound : upper bound of actual mapping range (aligned to page) + # mapping_size : actual mapping range size + # + # note that on non-QNX systems addr and lbound are equal. + # + # for example, assume requested base address and length are 0x774ec8d8 and 0x1800 + # respectively, then we allocate 3 pages as follows: + # [align(0x7700c8d8), align_up(0x7700c8d8 + 0x1800)] -> [0x7700c000, 0x7700f000] - elif ql.arch.type == QL_ARCH.ARM and ql.os.type == QL_OS.QNX: - MAP_ANONYMOUS = 0x00080000 - fd = ql.unpack32s(ql.pack32s(fd)) + # if mapping is fixed, use the base address as-is + if flags & (mmap_flags.MAP_FIXED | mmap_flags.MAP_FIXED_NOREPLACE): + # on non-QNX systems, base must be aligned to page boundary + if addr & (pagesize - 1) and ql.os.type != QL_OS.QNX: + return -1 # errno: EINVAL + + # if not, use the base address as a hint but always above or equal to + # the value specified in /proc/sys/vm/mmap_min_addr (here: mmap_address) + else: + addr = ql.mem.find_free_space(mapping_size, max(addr, ql.loader.mmap_address)) + + # on non-QNX systems addr is already aligned to page boundary + lbound = ql.mem.align(addr) + ubound = lbound + mapping_size + + ################################## + # make sure memory can be mapped # + ################################## + + if flags & mmap_flags.MAP_FIXED_NOREPLACE: + if not ql.mem.is_available(lbound, mapping_size): + return -1 # errno: EEXIST + + elif flags & mmap_flags.MAP_FIXED: + for page in range(lbound, ubound, pagesize): + if ql.mem.is_mapped(page, pagesize): + ql.log.debug(f'{api_name}: unmapping page at {page:#x} to make room for fixed mapping') + + ql.mem.unmap(page, pagesize) + + ############################# + # determine mapping content # + ############################# + + if flags & mmap_flags.MAP_ANONYMOUS: + data = b'' if flags & mmap_flags.MAP_UNINITIALIZED else b'\x00' * length + label = '[anonymous mapping]' else: - fd = ql.unpack32s(ql.pack32(fd)) - if ver == 2: - pgoffset = pgoffset * pagesize + fd = ql.unpacks(ql.pack(fd)) - need_mmap = True - mmap_base = addr - mmap_size = ql.mem.align_up(mlen - (addr & (pagesize - 1))) + if fd not in range(NR_OPEN): + return -1 # errno: EBADF - if ql.os.type != QL_OS.QNX: - mmap_base = ql.mem.align(mmap_base) + f: Optional[ql_file] = ql.os.fd[fd] - if (flags & MAP_FIXED) and mmap_base != addr: - return MAP_FAILED + if f is None: + return -1 # errno: EBADF - # initial ql.loader.mmap_address - if mmap_base != 0 and ql.mem.is_mapped(mmap_base, mmap_size): - if (flags & MAP_FIXED) > 0: - ql.log.debug("%s - MAP_FIXED, mapping not needed" % api_name) - try: - ql.mem.protect(mmap_base, mmap_size, prot) - except Exception as e: - ql.log.debug(e) - raise QlMemoryMappedError("Error: change protection at: 0x%x - 0x%x" % (mmap_base, mmap_base + mmap_size - 1)) - need_mmap = False - else: - mmap_base = 0 + # set mapping properties for future unmap + f._is_map_shared = bool(flags & mmap_flags.MAP_SHARED) + f._mapped_offset = pgoffset - # initialized mapping - if need_mmap: - if mmap_base == 0: - mmap_base = ql.loader.mmap_address - ql.loader.mmap_address = mmap_base + mmap_size - ql.log.debug("%s - mapping needed for 0x%x" % (api_name, mmap_base)) - try: - ql.mem.map(mmap_base, mmap_size, prot, "[syscall_%s]" % api_name) - except Exception as e: - raise QlMemoryMappedError("Error: mapping needed but failed") - ql.log.debug("%s - addr range 0x%x - 0x%x: " % (api_name, mmap_base, mmap_base + mmap_size - 1)) + fname = f.name - # FIXME: MIPS32 Big Endian - try: - ql.mem.write(mmap_base, b'\x00' * mmap_size) - except Exception as e: - raise QlMemoryMappedError("Error: trying to zero memory") + if isinstance(fname, bytes): + fname = fname.decode() - if ((flags & MAP_ANONYMOUS) == 0) and fd in range(NR_OPEN): - f = ql.os.fd[fd] + f.seek(pgoffset) - if f is not None: - f.seek(pgoffset) - data = f.read(mlen) - mem_info = str(f.name) - f._is_map_shared = flags & MAP_SHARED - f._mapped_offset = pgoffset - ql.log.debug("mem write : " + hex(len(data))) - ql.log.debug("mem mmap : " + mem_info) + data = f.read(length) + label = os.path.basename(fname) - ql.mem.change_mapinfo(mmap_base, mmap_base + mmap_size, mem_info=f'[{api_name}] {os.path.basename(mem_info)}') - try: - ql.mem.write(mmap_base, data) - except Exception as e: - ql.log.debug(e) - raise QlMemoryMappedError("Error: trying to write memory: ") + # finally, we have everything we need to map the memory + ql.mem.map(lbound, mapping_size, info=label) - return mmap_base + if data: + ql.mem.write(addr, data) + + ql.mem.protect(lbound, mapping_size, prot) + + return addr def ql_syscall_old_mmap(ql: Qiling, struct_mmap_args: int): @@ -164,6 +201,9 @@ def ql_syscall_mmap(ql: Qiling, addr: int, length: int, prot: int, flags: int, f def ql_syscall_mmap2(ql: Qiling, addr: int, length: int, prot: int, flags: int, fd: int, pgoffset: int): + if ql.os.type != QL_OS.QNX: + pgoffset *= ql.mem.pagesize + return syscall_mmap_impl(ql, addr, length, prot, flags, fd, pgoffset, 2)