Reimplement POSIX mmap

This commit is contained in:
elicn
2022-12-18 21:58:16 +02:00
parent 1640831dcb
commit 85e4cccceb
2 changed files with 197 additions and 74 deletions
+83
View File
@@ -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
+114 -74
View File
@@ -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)