qilingframework / qilingframework/qiling
getdents / getdents64 return unaligned dirent records (unaligned-load fault on MIPS)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 6.1k
- Forks
- 798
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 9
Description
getdents / getdents64 return unaligned dirent records (unaligned-load fault on MIPS)
Describe the bug
__getdents_common (qiling/os/posix/syscall/unistd.py) computes each record length as
d_reclen = n + n + 2 + len(d_name) + 1
without rounding it up. The kernel rounds each record up to the alignment of its leading d_ino, so the next record's d_ino stays aligned. Without that, records pack tightly and the next d_ino lands on a misaligned address. A strict-alignment guest (e.g. MIPS / MIPS64) then faults with an unaligned load (UC_ERR_*_UNALIGNED) while walking the buffer — e.g. busybox ls crashes right after the syscall. x86 tolerates the unaligned read, which is why it's invisible there.
This affects both dirent syscalls:
getdents64— leadingu64 d_ino(needs 8-byte alignment on every arch).- legacy
getdents— word-sizedd_ino: 4 bytes on a 32-bit guest, 8 bytes on a 64-bit guest (e.g. MIPS64 n64). Older glibc still issues this syscall, so MIPS64 directory listings fault here too.
Repro (Python) — getdents64
from qiling import Qiling
from qiling.const import QL_ARCH, QL_OS, QL_ENDIAN, QL_VERBOSE
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)
ql.mem.write(base, b"/\x00")
fd = ql_syscall_open(ql, base, 0, 0)
n = ql_syscall_getdents64(ql, fd, base + 0x100, 0x2000)
buf = bytes(ql.mem.read(base + 0x100, n))
off = 0
while off < n:
print("record at offset", off, "aligned" if off % 8 == 0 else "*** MISALIGNED ***")
off += int.from_bytes(buf[off + 16:off + 18], "big") # d_reclen (u16 @ 16)
Output (without the fix):
record at offset 0 aligned
record at offset 21 *** MISALIGNED *** # ".." starts at 21 -> its u64 d_ino is unaligned
record at offset 43 *** MISALIGNED ***
...
The legacy getdents path has the identical defect (swap ql_syscall_getdents64 → ql_syscall_getdents; d_reclen is a u16 at offset 2 * pointersize). On MIPS the guest's load of the misaligned d_ino raises UC_ERR_*_UNALIGNED.
Expected behavior
Each record should be padded so the next record's leading d_ino is aligned, matching the kernel's ALIGN(reclen, sizeof(long)). All record offsets land on aligned boundaries and a strict-alignment guest can walk the buffer.
Why it was never caught
x86 tolerates unaligned loads. The existing getdents test binaries (test_elf_linux_x8664_getdents, test_elf_linux_x86_getdents64) drive the legacy getdents path but only assert directory names, never the record alignment / d_type, so the defect is invisible in CI.
Prior art (please read before reviewing)
This bug was fixed once and reverted:
- #1419 merged
d_reclen = (d_reclen + n) & ~(n - 1), then #1423 reverted it ("seems like there is an issue"). #1425 re-submits the same formula and is still open.
That blanket approach has two problems the fix here avoids:
(d_reclen + n)over-pads; the canonical round-up is(d_reclen + (n - 1)) & ~(n - 1).- It rounded the legacy
getdentsrecord unconditionally without relocatingd_type. The legacylinux_direntstoresd_typein the record's last byte (offsetd_reclen - 1); padding the record moved the consumer'sd_typeread into the pad bytes — almost certainly the cause of the revert. (getdents64is safe because it putsd_typebefored_name, so trailing pad is inert.)
A fix for both layouts is proposed in PR #1636 (supersedes #1425).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in qiling/os/posix/syscall/unistd.py at __getdents_common, then inspect the existing test_elf_linux_x8664_getdents and test_elf_linux_x86_getdents64 binaries. Exercise both getdents paths with the MIPS reproduction and verify that every record begins at the required alignment while legacy d_type and directory names remain correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100