Skip to content

Commit 19d33b6

Browse files
authored
Merge pull request #1634 from retrocpugeek/fix/statx-bigendian
Fix statx byte order on big-endian guests
2 parents d9b4d9d + 0e52738 commit 19d33b6

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

qiling/os/posix/syscall/stat.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,17 +1413,45 @@ class Statx64(ctypes.Structure):
14131413

14141414
_pack_ = 4
14151415

1416+
# Big-endian counterparts of the statx structs. The kernel statx layout is the
1417+
# same on every architecture, so the big-endian variants reuse the little-endian
1418+
# field lists verbatim and only change the ctypes base class (and the nested
1419+
# timestamp type, which must itself be big-endian). Without these, statx() byte-
1420+
# swaps every field on a big-endian guest (e.g. MIPS/MIPS64 EB), so stx_mode
1421+
# loses its S_IFDIR bit and tools like `ls` treat directories as plain files.
1422+
class StatxTimestamp32EB(ctypes.BigEndianStructure):
1423+
_fields_ = StatxTimestamp32._fields_
1424+
1425+
class StatxTimestamp64EB(ctypes.BigEndianStructure):
1426+
_fields_ = StatxTimestamp64._fields_
1427+
1428+
def _statx_fields_eb(fields):
1429+
swap = {StatxTimestamp32: StatxTimestamp32EB, StatxTimestamp64: StatxTimestamp64EB}
1430+
return [(name, swap.get(ftype, ftype)) for (name, ftype) in fields]
1431+
1432+
class Statx32EB(ctypes.BigEndianStructure):
1433+
_fields_ = _statx_fields_eb(Statx32._fields_)
1434+
_pack_ = 8
1435+
1436+
class Statx64EB(ctypes.BigEndianStructure):
1437+
_fields_ = _statx_fields_eb(Statx64._fields_)
1438+
_pack_ = 4
1439+
14161440
# int statx(int dirfd, const char *restrict pathname, int flags,
14171441
# unsigned int mask, struct statx *restrict statxbuf);
14181442
def ql_syscall_statx(ql: Qiling, dirfd: int, path: int, flags: int, mask: int, buf_ptr: int):
1443+
is_eb = ql.arch.endian == QL_ENDIAN.EB
1444+
14191445
def statx_convert_timestamp(tv_sec, tv_nsec):
14201446
tv_sec = struct.unpack('i', struct.pack('f', tv_sec))[0]
14211447
tv_nsec = struct.unpack('i', struct.pack('f', tv_nsec))[0]
14221448

14231449
if ql.arch.bits == 32:
1424-
return StatxTimestamp32(tv_sec=tv_sec, tv_nsec=tv_nsec)
1450+
Timestamp = StatxTimestamp32EB if is_eb else StatxTimestamp32
14251451
else:
1426-
return StatxTimestamp64(tv_sec=tv_sec, tv_nsec=tv_nsec)
1452+
Timestamp = StatxTimestamp64EB if is_eb else StatxTimestamp64
1453+
1454+
return Timestamp(tv_sec=tv_sec, tv_nsec=tv_nsec)
14271455

14281456

14291457
def major(dev):
@@ -1438,9 +1466,9 @@ def minor(dev):
14381466
st = Stat(real_path, fd)
14391467

14401468
if ql.arch.bits == 32:
1441-
Statx = Statx32
1469+
Statx = Statx32EB if is_eb else Statx32
14421470
else:
1443-
Statx = Statx64
1471+
Statx = Statx64EB if is_eb else Statx64
14441472

14451473
stx = Statx(
14461474
stx_mask = 0x07ff, # STATX_BASIC_STATS

tests/test_elf.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,37 @@ def test_linux_getdents_alignment(self):
836836

837837
del ql
838838

839+
# Regression for statx() byte order on big-endian guests: the statx struct
840+
# must be serialized in the guest's endianness. Otherwise stx_mode is byte-
841+
# swapped and a directory loses its S_IFDIR bit (so e.g. `ls` treats a
842+
# directory as a regular file). Exercised on a big-endian MIPS context.
843+
def test_linux_statx_bigendian(self):
844+
import stat as _stat
845+
from qiling.const import QL_ENDIAN
846+
from qiling.os.posix.syscall.stat import ql_syscall_statx, Statx32
847+
848+
ql = Qiling(code=b"\x00\x00\x00\x00", archtype=QL_ARCH.MIPS, ostype=QL_OS.LINUX,
849+
endian=QL_ENDIAN.EB, rootfs="../examples/rootfs/mips32_linux",
850+
verbose=QL_VERBOSE.OFF)
851+
852+
base = 0x100000
853+
ql.mem.map(base, 0x1000)
854+
path_ptr, buf_ptr = base, base + 0x200
855+
ql.mem.write(path_ptr, b"/\x00")
856+
857+
AT_FDCWD = -100 & 0xffffffff
858+
STATX_BASIC_STATS = 0x07ff
859+
ret = ql_syscall_statx(ql, AT_FDCWD, path_ptr, 0, STATX_BASIC_STATS, buf_ptr)
860+
self.assertEqual(ret, 0)
861+
862+
# the guest is big-endian, so it reads stx_mode in big-endian byte order;
863+
# it must come back as a directory (S_IFDIR). Before the fix the struct
864+
# was emitted little-endian and the type bits were lost.
865+
mode = int.from_bytes(ql.mem.read(buf_ptr + Statx32.stx_mode.offset, 2), 'big')
866+
self.assertTrue(_stat.S_ISDIR(mode))
867+
868+
del ql
869+
839870
def test_memory_search(self):
840871
ql = Qiling(code=b"\xCC", archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX, verbose=QL_VERBOSE.DEBUG)
841872

0 commit comments

Comments
 (0)