Skip to content

Align getdents and getdents64 records to fix unaligned access on MIPS#1636

Open
retrocpugeek wants to merge 3 commits into
qilingframework:devfrom
retrocpugeek:fix/getdents64-alignment
Open

Align getdents and getdents64 records to fix unaligned access on MIPS#1636
retrocpugeek wants to merge 3 commits into
qilingframework:devfrom
retrocpugeek:fix/getdents64-alignment

Conversation

@retrocpugeek

@retrocpugeek retrocpugeek commented Jun 24, 2026

Copy link
Copy Markdown

What

__getdents_common did not round dirent records up to the alignment of their leading d_ino, so the next record's d_ino could land unaligned. A strict-alignment guest (MIPS/MIPS64) then faults with UC_ERR_*_UNALIGNED while walking the buffer (e.g. busybox ls crashes right after the syscall). x86 tolerates the unaligned access, which is why it was never caught.

This affects both getdents64 (u64 d_ino) and the legacy getdents (word-sized d_ino — 8 bytes on a 64-bit guest such as MIPS64 n64; older glibc still issues this syscall).

Closes #1635.

How

Round each record up to the alignment of d_ino, matching the kernel's ALIGN(reclen, sizeof(long)):

unaligned_reclen = n + n + 2 + len(d_name) + 1
d_reclen = (unaligned_reclen + (n - 1)) & ~(n - 1)

applied to both record layouts. The one subtlety is d_type:

  • getdents64 places d_type before d_name, so the trailing pad bytes are inert.
  • legacy getdents stores d_type in the record's last byte (offset d_reclen - 1), so the alignment padding is inserted between d_name and d_type to keep d_type there.

Relationship to #1419 / #1423 / #1425

This bug was fixed once (#1419) and reverted (#1423); #1425 re-submits (d_reclen + n) & ~(n - 1) and is still open. That blanket approach had two issues this PR fixes properly:

  1. (d_reclen + n) over-pads — the canonical round-up is (d_reclen + (n - 1)).
  2. It rounded the legacy record unconditionally without relocating d_type, so the consumer's d_type read (at d_reclen - 1) landed in the pad bytes — almost certainly the revert cause. This PR moves d_type to the new d_reclen - 1, so the legacy layout is correct after padding.

This supersedes #1425.

Tests

  • test_elf.ELFTest.test_linux_getdents64_alignment — drives ql_syscall_getdents64 on a directory and asserts every linux_dirent64 record starts 8-byte aligned.
  • test_elf.ELFTest.test_linux_getdents_alignment — drives the legacy ql_syscall_getdents and asserts every record is aligned, tiles the buffer, and still carries d_type (DT_DIR for ./..) in its last byte.

Both are self-contained (no rootfs binary, run on stock unicorn); each fails on dev and passes with this fix.

Checklist

  • This PR only contains minor fixes.
  • The new code conforms to Qiling Framework naming convention.
  • Essential comments are added.
  • I have added enough tests for this PR.
  • The target branch is dev branch.

retrocpugeek and others added 2 commits June 25, 2026 00:28
getdents64 wrote linux_dirent64 records with d_reclen = header + name,
without rounding up to the alignment of the leading u64 d_ino. The next
record's d_ino then lands unaligned, and a strict-alignment guest (e.g.
MIPS/MIPS64) faults with UC_ERR_READ_UNALIGNED when walking the buffer.
x86 tolerates the unaligned load, which is why this was never caught.

Round d_reclen up to the d_ino alignment, matching the kernel. The rounding
is scoped to the getdents64 (is_64) branch only: getdents64 places d_type
before d_name so the trailing pad bytes are inert, whereas the legacy
getdents layout stores d_type at offset d_reclen-1 and would break if padded
the same way -- that is what got the earlier blanket fix (PR qilingframework#1419) reverted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Calls ql_syscall_getdents64 on a directory and walks the returned
linux_dirent64 records, asserting every record (and thus its leading u64
d_ino) starts on an 8-byte boundary. Before the fix d_reclen was not
rounded up, so records were misaligned and a strict-alignment guest (e.g.
MIPS) faulted with an unaligned load. Self-contained; runs on stock unicorn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the record alignment to the legacy getdents path. The kernel rounds
both getdents and getdents64 records up to the alignment of their leading
d_ino (ALIGN(reclen, sizeof(long))); without it a strict-alignment guest
(MIPS) faults walking the buffer, e.g. older glibc busybox 'ls' (which uses
the legacy getdents syscall) crashes on directory listings.

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 attempt (PR qilingframework#1419) reverted. getdents64 (d_type before d_name) is
unchanged.

Add test_linux_getdents_alignment: walks legacy getdents records on a
big-endian MIPS guest and asserts each is aligned, tiles the buffer, and
keeps d_type (DT_DIR for '.'/'..'). Runs on stock unicorn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@retrocpugeek retrocpugeek changed the title Align getdents64 records to fix unaligned access on MIPS Align getdents and getdents64 records to fix unaligned access on MIPS Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant