mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
Reimplement POSIX mmap
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user