qilingframework / qilingframework/qiling

getdents / getdents64 return unaligned dirent records (unaligned-load fault on MIPS)

Open
#1,635 0 comments 0 reactions 0 assignees View on GitHub

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 — leading u64 d_ino (needs 8-byte alignment on every arch).
  • legacy getdents — word-sized d_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_getdents64ql_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:

  1. (d_reclen + n) over-pads; the canonical round-up is (d_reclen + (n - 1)) & ~(n - 1).
  2. It rounded the legacy getdents record unconditionally without relocating d_type. The legacy linux_dirent stores d_type in the record's last byte (offset d_reclen - 1); padding the record moved the consumer's d_type read into the pad bytes — almost certainly the cause of the revert. (getdents64 is safe because it puts d_type before d_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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.