statx returns a byte-swapped struct on big-endian guests (directories misdetected)
Describe the bug
ql_syscall_statx (qiling/os/posix/syscall/stat.py) always serializes the result with the little-endian Statx32 / Statx64 structs (ctypes.Structure), regardless of the guest's endianness:
if ql.arch.bits == 32:
Statx = Statx32
else:
Statx = Statx64
Every other stat-family struct in this file already has an explicit big-endian variant (LinuxMips64EBStat, LinuxARMEBStat, …) selected via ql.arch.endian. statx is the only one missing it, so on a big-endian guest (MIPS/MIPS64 EB, ARM EB, PPC, …) every field of struct statx is byte-swapped.
The most visible consequence is stx_mode: for a directory the type bits land in the wrong byte, so S_ISDIR is false on a big-endian guest. A directory is reported as a non-directory, and e.g. busybox ls / prints just the path name instead of listing the directory. Because modern glibc routes stat/fstatat through statx, this affects ordinary directory operations, not only direct statx callers.
Repro (pure Python, no guest binary)
This serializes the exact struct Qiling emits and shows it reads back wrong on a big-endian guest:
import os
from qiling.os.posix.syscall.stat import Statx64
st_mode = os.stat('/').st_mode # a directory: 0o40755
raw = bytes(Statx64(stx_mode=st_mode))
off = Statx64.stx_mode.offset
field = raw[off:off+2]
print('host / st_mode = 0o%o (S_IFDIR=%s)' % (st_mode, (st_mode & 0o170000) == 0o040000))
print('stx_mode bytes = %s (offset %d)' % (field.hex(), off))
le = int.from_bytes(field, 'little')
be = int.from_bytes(field, 'big')
print('LE guest reads mode = 0x%04x -> type 0o%o' % (le, le & 0o170000))
print('BE guest reads mode = 0x%04x -> type 0o%o' % (be, be & 0o170000))
Output:
host / st_mode = 0o40755 (S_IFDIR=True)
stx_mode bytes = ed41 (offset 28)
LE guest reads mode = 0x41ed -> type 0o40000 # S_IFDIR -> directory
BE guest reads mode = 0xed41 -> type 0o160000 # not S_IFDIR -> "not a directory"
S_IFDIR is 0o40000. The struct is written little-endian (ed 41), so a little-endian guest reads 0x41ed (correct) while a big-endian guest reads 0xed41 → 0o160000, which is not a directory type.
Expected behavior
statx should serialize the struct in the guest's byte order, so stx_mode (and every other field) reads back correctly. S_ISDIR should be true for a directory on big-endian guests, exactly as it already is on little-endian ones.
End-to-end confirmation
A static MIPS64 big-endian binary that calls statx(AT_FDCWD, "/", 0, STATX_BASIC_STATS, &stx) and prints DIR iff S_ISDIR(stx.stx_mode) (source: examples/src/linux/statx.c):
- Before the fix:
NOTDIR mode=176501 (the byte-swapped mode)
- After the fix:
DIR
Why it was never caught
statx is only emitted by recent glibc; the big-endian binaries in the test rootfs are older/static and use stat/fstat/lstat, which already have big-endian struct variants. And no existing test inspects a directory's mode via statx on a big-endian guest. (Same bug class as the already-fixed #521, where bind unpacked sin_family with the wrong endianness on MIPS MSB.)
Environment
- Qiling:
dev (reproduced through current HEAD); the Python repro above reproduces on any host.
- Affected guests: any big-endian Linux target (MIPS/MIPS64 EB, ARM EB, PPC, …).
- Not arch-specific in Qiling code — it's the shared
statx syscall handler.
Proposed fix
Add big-endian variants Statx32EB / Statx64EB (and StatxTimestamp32EB / StatxTimestamp64EB) as ctypes.BigEndianStructure, reusing the little-endian field layouts, and select them in ql_syscall_statx when ql.arch.endian == QL_ENDIAN.EB. The little-endian path is byte-for-byte unchanged.
Implemented on retrocpugeek:feature/mips64be:
- Fix:
Fix statx byte order on big-endian guests (commit 7550983b)
- Regression test:
test_elf.ELFTest.test_elf_linux_mips64eb_statx (asserts statx reports / as DIR) + examples/src/linux/statx.c
Happy to open a PR against dev.
statxreturns a byte-swapped struct on big-endian guests (directories misdetected)Describe the bug
ql_syscall_statx(qiling/os/posix/syscall/stat.py) always serializes the result with the little-endianStatx32/Statx64structs (ctypes.Structure), regardless of the guest's endianness:Every other stat-family struct in this file already has an explicit big-endian variant (
LinuxMips64EBStat,LinuxARMEBStat, …) selected viaql.arch.endian.statxis the only one missing it, so on a big-endian guest (MIPS/MIPS64 EB, ARM EB, PPC, …) every field ofstruct statxis byte-swapped.The most visible consequence is
stx_mode: for a directory the type bits land in the wrong byte, soS_ISDIRis false on a big-endian guest. A directory is reported as a non-directory, and e.g.busybox ls /prints just the path name instead of listing the directory. Because modern glibc routesstat/fstatatthroughstatx, this affects ordinary directory operations, not only directstatxcallers.Repro (pure Python, no guest binary)
This serializes the exact struct Qiling emits and shows it reads back wrong on a big-endian guest:
Output:
S_IFDIRis0o40000. The struct is written little-endian (ed 41), so a little-endian guest reads0x41ed(correct) while a big-endian guest reads0xed41→0o160000, which is not a directory type.Expected behavior
statxshould serialize the struct in the guest's byte order, sostx_mode(and every other field) reads back correctly.S_ISDIRshould be true for a directory on big-endian guests, exactly as it already is on little-endian ones.End-to-end confirmation
A static MIPS64 big-endian binary that calls
statx(AT_FDCWD, "/", 0, STATX_BASIC_STATS, &stx)and printsDIRiffS_ISDIR(stx.stx_mode)(source:examples/src/linux/statx.c):NOTDIR mode=176501(the byte-swapped mode)DIRWhy it was never caught
statxis only emitted by recent glibc; the big-endian binaries in the test rootfs are older/static and usestat/fstat/lstat, which already have big-endian struct variants. And no existing test inspects a directory's mode viastatxon a big-endian guest. (Same bug class as the already-fixed #521, wherebindunpackedsin_familywith the wrong endianness on MIPS MSB.)Environment
dev(reproduced through current HEAD); the Python repro above reproduces on any host.statxsyscall handler.Proposed fix
Add big-endian variants
Statx32EB/Statx64EB(andStatxTimestamp32EB/StatxTimestamp64EB) asctypes.BigEndianStructure, reusing the little-endian field layouts, and select them inql_syscall_statxwhenql.arch.endian == QL_ENDIAN.EB. The little-endian path is byte-for-byte unchanged.Implemented on
retrocpugeek:feature/mips64be:Fix statx byte order on big-endian guests(commit7550983b)test_elf.ELFTest.test_elf_linux_mips64eb_statx(assertsstatxreports/asDIR) +examples/src/linux/statx.cHappy to open a PR against
dev.