Skip to content

statx returns a byte-swapped struct on big-endian guests (directories misdetected) #1633

Description

@retrocpugeek

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 0xed410o160000, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions