mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
Merge pull request #1636 from retrocpugeek/fix/getdents64-alignment
Align getdents and getdents64 records to fix unaligned access on MIPS
This commit is contained in:
@@ -926,7 +926,15 @@ def __getdents_common(ql: Qiling, fd: int, dirp: int, count: int, *, is_64: bool
|
||||
d_off = 0
|
||||
d_name = (result.name if isinstance(result, os.DirEntry) else result._str).encode() + b'\x00'
|
||||
d_type = _type_mapping(result)
|
||||
d_reclen = n + n + 2 + len(d_name) + 1
|
||||
|
||||
# The kernel rounds each record up to the alignment of its leading
|
||||
# (pointer-sized) d_ino, so the next record's d_ino stays aligned.
|
||||
# Without this, strict-alignment guests (e.g. MIPS) fault with an
|
||||
# unaligned load when walking the buffer; x86 tolerates it, which is
|
||||
# why it was never caught. Matches the kernel's ALIGN(reclen,
|
||||
# sizeof(long)) for both getdents and getdents64.
|
||||
unaligned_reclen = n + n + 2 + len(d_name) + 1
|
||||
d_reclen = (unaligned_reclen + (n - 1)) & ~(n - 1)
|
||||
|
||||
# TODO: Dirty fix for X8664 MACOS 11.6 APFS
|
||||
# For some reason MACOS return int value is 64bit
|
||||
@@ -936,6 +944,8 @@ def __getdents_common(ql: Qiling, fd: int, dirp: int, count: int, *, is_64: bool
|
||||
packed_d_ino = (ql.pack64(d_ino), n)
|
||||
|
||||
if is_64:
|
||||
# getdents64 places d_type *before* d_name, so the trailing pad
|
||||
# bytes are inert.
|
||||
fields = (
|
||||
(ql.pack64(d_ino), n),
|
||||
(ql.pack64(d_off), n),
|
||||
@@ -944,11 +954,18 @@ def __getdents_common(ql: Qiling, fd: int, dirp: int, count: int, *, is_64: bool
|
||||
(d_name, len(d_name))
|
||||
)
|
||||
else:
|
||||
# legacy linux_dirent stores d_type in the record's last byte
|
||||
# (offset d_reclen-1), so the alignment padding goes *between*
|
||||
# d_name and d_type to keep d_type there. padding without
|
||||
# relocating d_type is what got the earlier blanket fix
|
||||
# (PR #1419) reverted.
|
||||
pad = d_reclen - unaligned_reclen
|
||||
fields = (
|
||||
packed_d_ino,
|
||||
(ql.pack(d_off), n),
|
||||
(ql.pack16(d_reclen), 2),
|
||||
(d_name, len(d_name)),
|
||||
(b'\x00' * pad, pad),
|
||||
(d_type, 1)
|
||||
)
|
||||
|
||||
|
||||
@@ -725,6 +725,90 @@ class ELFTest(unittest.TestCase):
|
||||
|
||||
del ql
|
||||
|
||||
# Regression for getdents64 record alignment. Each linux_dirent64 record
|
||||
# must be padded so the next record's leading u64 d_ino stays 8-byte
|
||||
# aligned; otherwise a strict-alignment guest (e.g. MIPS) faults with an
|
||||
# unaligned load while walking the buffer. x86 tolerates the unaligned read,
|
||||
# which is why it was never caught.
|
||||
def test_linux_getdents64_alignment(self):
|
||||
from qiling.const import QL_ENDIAN
|
||||
from qiling.os.posix.syscall.fcntl import ql_syscall_open
|
||||
from qiling.os.posix.syscall.unistd import ql_syscall_getdents64
|
||||
|
||||
ql = Qiling(code=b"\x00\x00\x00\x00", archtype=QL_ARCH.MIPS, ostype=QL_OS.LINUX,
|
||||
endian=QL_ENDIAN.EB, rootfs="../examples/rootfs/mips32_linux",
|
||||
verbose=QL_VERBOSE.OFF)
|
||||
|
||||
base = 0x100000
|
||||
ql.mem.map(base, 0x4000)
|
||||
path_ptr, buf_ptr = base, base + 0x100
|
||||
ql.mem.write(path_ptr, b"/\x00")
|
||||
|
||||
fd = ql_syscall_open(ql, path_ptr, 0, 0) # O_RDONLY on a directory
|
||||
self.assertGreaterEqual(fd, 0)
|
||||
|
||||
nbytes = ql_syscall_getdents64(ql, fd, buf_ptr, 0x2000)
|
||||
self.assertGreater(nbytes, 0)
|
||||
|
||||
# walk the linux_dirent64 records; every record (hence its leading u64
|
||||
# d_ino) must start at an 8-byte boundary. d_reclen is a u16 at offset 16.
|
||||
buf = bytes(ql.mem.read(buf_ptr, nbytes))
|
||||
off = 0
|
||||
while off < nbytes:
|
||||
self.assertEqual(off % 8, 0, f"dirent at offset {off} is not 8-byte aligned")
|
||||
d_reclen = int.from_bytes(buf[off + 16:off + 18], "big")
|
||||
self.assertNotEqual(d_reclen, 0)
|
||||
off += d_reclen
|
||||
|
||||
del ql
|
||||
|
||||
def test_linux_getdents_alignment(self):
|
||||
# the legacy getdents record was not rounded up to the alignment of its
|
||||
# word-sized d_ino either, so a strict-alignment guest faulted walking
|
||||
# the buffer. unlike getdents64, legacy linux_dirent keeps d_type in the
|
||||
# record's last byte (offset d_reclen-1), so the padding must sit between
|
||||
# d_name and d_type -- verify the records are aligned AND d_type survives.
|
||||
from qiling.const import QL_ENDIAN
|
||||
from qiling.os.posix.syscall.fcntl import ql_syscall_open
|
||||
from qiling.os.posix.syscall.unistd import ql_syscall_getdents
|
||||
|
||||
ql = Qiling(code=b"\x00\x00\x00\x00", archtype=QL_ARCH.MIPS, ostype=QL_OS.LINUX,
|
||||
endian=QL_ENDIAN.EB, rootfs="../examples/rootfs/mips32_linux",
|
||||
verbose=QL_VERBOSE.OFF)
|
||||
|
||||
base = 0x100000
|
||||
ql.mem.map(base, 0x4000)
|
||||
path_ptr, buf_ptr = base, base + 0x100
|
||||
ql.mem.write(path_ptr, b"/\x00")
|
||||
|
||||
fd = ql_syscall_open(ql, path_ptr, 0, 0) # O_RDONLY on a directory
|
||||
self.assertGreaterEqual(fd, 0)
|
||||
|
||||
nbytes = ql_syscall_getdents(ql, fd, buf_ptr, 0x2000)
|
||||
self.assertGreater(nbytes, 0)
|
||||
|
||||
# walk the linux_dirent records; on 32-bit MIPS d_ino is 4 bytes, so each
|
||||
# record must start at a 4-byte boundary. d_reclen is a u16 at offset 8
|
||||
# (after the 4-byte d_ino + 4-byte d_off); d_name starts at offset 10.
|
||||
buf = bytes(ql.mem.read(buf_ptr, nbytes))
|
||||
off = 0
|
||||
while off < nbytes:
|
||||
self.assertEqual(off % 4, 0, f"dirent at offset {off} is not 4-byte aligned")
|
||||
d_reclen = int.from_bytes(buf[off + 8:off + 10], "big")
|
||||
self.assertNotEqual(d_reclen, 0)
|
||||
|
||||
name = buf[off + 10:buf.index(b"\x00", off + 10)].decode()
|
||||
# legacy getdents stores d_type in the record's last byte; '.' and
|
||||
# '..' must still report DT_DIR (4) after the alignment padding
|
||||
if name in (".", ".."):
|
||||
self.assertEqual(buf[off + d_reclen - 1], 4, f"{name}: d_type not DT_DIR")
|
||||
|
||||
off += d_reclen
|
||||
|
||||
self.assertEqual(off, nbytes) # records tile the buffer exactly
|
||||
|
||||
del ql
|
||||
|
||||
def test_memory_search(self):
|
||||
ql = Qiling(code=b"\xCC", archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user