diff --git a/examples/src/linux/Makefile b/examples/src/linux/Makefile index 006ea4907..5e6778464 100644 --- a/examples/src/linux/Makefile +++ b/examples/src/linux/Makefile @@ -130,6 +130,9 @@ patch_test.bin: patch_test.bin.c libpatch_test.so x8664_onestraw_server: x8664_linux_onestraw.c $(CC) $(CPPFLAGS) $(CFLAGS) -m64 -o $@ $< +x8664_linux_utime: x8664_linux_utime.c + $(CC) $(CPPFLAGS) $(CFLAGS) -m64 -o $@ $< + $(OBJS):%.o:%.c $(CC) $(CFLAGS) -c $< -o $@ diff --git a/examples/src/linux/x8664_linux_utime.c b/examples/src/linux/x8664_linux_utime.c new file mode 100644 index 000000000..3c3da255b --- /dev/null +++ b/examples/src/linux/x8664_linux_utime.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include /* Definition of AT_* constants */ +#include +#include +#include /* Definition of SYS_* constants */ +#include + +int main(int argc, char **argv){ + /* + struct utimbuf { + time_t atime; + time_t modtime; + */ + srand(0x1337); + struct timespec utimensat_times[2]; + struct timespec atime; + struct timespec mtime; + atime.tv_sec = rand(); + atime.tv_nsec = rand(); + mtime.tv_sec = rand(); + mtime.tv_nsec = rand() & 0xffff; // avoid illegal arg error + utimensat_times[0] = atime; + utimensat_times[1] = mtime; + int res = utimensat(AT_FDCWD, "./utimensat-test", utimensat_times,0); + if (!res){ + perror("utimensat"); + } + + + struct utimbuf utime_time[1]; + struct utimbuf actime; + actime.actime = rand(); + actime.modtime = rand(); + utime_time[0] = actime; + + res = syscall(SYS_utime,"./utime-test", utime_time); + if (!res){ + perror("utime failed"); + } + + struct timeval utimes_times[2]; + struct timeval utimes_actime; + struct timeval utimes_modtime; + utimes_actime.tv_sec = rand() & 0xff; + utimes_actime.tv_usec = rand() & 0xff; + utimes_modtime.tv_sec = rand() & 0xff; + utimes_modtime.tv_usec = rand() & 0xffff; + utimes_times[0] = utimes_actime; + utimes_times[1] = utimes_modtime; + res = syscall(SYS_utimes,"./utimes-test", utimes_times); + if (!res){ + perror("utimes"); + } + + utimes_actime.tv_sec = rand() & 0xff; + utimes_actime.tv_usec = rand() & 0xff; + utimes_modtime.tv_sec = rand() & 0xff; + utimes_modtime.tv_usec = rand() & 0xffff; + utimes_times[0] = utimes_actime; + utimes_times[1] = utimes_modtime; + res = syscall(SYS_futimesat,AT_FDCWD, "./futimesat-test", utimes_times); + if (!res){ + perror("futimesat"); + } + + + + + + + +} + diff --git a/qiling/os/linux/syscall.py b/qiling/os/linux/syscall.py index d97de91bf..0a6d7d848 100644 --- a/qiling/os/linux/syscall.py +++ b/qiling/os/linux/syscall.py @@ -6,25 +6,26 @@ from qiling import Qiling from qiling.arch.x86_const import * from qiling.const import QL_ARCH - +from qiling.os.posix.const import AT_FDCWD, AT_SYMLINK_NOFOLLOW +from qiling.os.posix.structs import * from datetime import datetime from math import floor +import os import ctypes + def __get_timespec_struct(archbits: int): - long = getattr(ctypes, f'c_int{archbits}') - ulong = getattr(ctypes, f'c_uint{archbits}') + long = getattr(ctypes, f"c_int{archbits}") + ulong = getattr(ctypes, f"c_uint{archbits}") class timespec(ctypes.Structure): _pack_ = archbits // 8 - _fields_ = ( - ('tv_sec', ulong), - ('tv_nsec', long) - ) + _fields_ = (("tv_sec", ulong), ("tv_nsec", long)) return timespec + def __get_timespec_obj(archbits: int): now = datetime.now().timestamp() @@ -38,9 +39,9 @@ def __get_timespec_obj(archbits: int): def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): if ql.arch.type == QL_ARCH.X86: u_info = ql.mem.read(u_info_addr, 4 * 4) - index = ql.unpack32s(u_info[0 : 4]) - base = ql.unpack32(u_info[4 : 8]) - limit = ql.unpack32(u_info[8 : 12]) + index = ql.unpack32s(u_info[0:4]) + base = ql.unpack32(u_info[4:8]) + limit = ql.unpack32(u_info[8:12]) ql.log.debug("set_thread_area base : 0x%x limit is : 0x%x" % (base, limit)) @@ -48,7 +49,14 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): index = ql.os.gdtm.get_free_idx(12) if index in (12, 13, 14): - access = QL_X86_A_PRESENT | QL_X86_A_PRIV_3 | QL_X86_A_DESC_DATA | QL_X86_A_DATA | QL_X86_A_DATA_E | QL_X86_A_DATA_W + access = ( + QL_X86_A_PRESENT + | QL_X86_A_PRIV_3 + | QL_X86_A_DESC_DATA + | QL_X86_A_DATA + | QL_X86_A_DATA_E + | QL_X86_A_DATA_W + ) ql.os.gdtm.register_gdt_segment(index, base, limit, access) ql.mem.write_ptr(u_info_addr, index, 4) @@ -57,12 +65,12 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int): return -1 elif ql.arch.type == QL_ARCH.MIPS: - CONFIG3_ULR = (1 << 13) + CONFIG3_ULR = 1 << 13 ql.arch.regs.cp0_config3 = CONFIG3_ULR ql.arch.regs.cp0_userlocal = u_info_addr ql.arch.regs.v0 = 0 ql.arch.regs.a3 = 0 - ql.log.debug ("set_thread_area(0x%x)" % u_info_addr) + ql.log.debug("set_thread_area(0x%x)" % u_info_addr) return 0 @@ -75,18 +83,180 @@ def ql_syscall_set_tls(ql: Qiling, address: int): ql.log.debug("settls(%#x)", address) + def ql_syscall_clock_gettime(ql: Qiling, clock_id: int, tp: int): ts_obj = __get_timespec_obj(ql.arch.bits) ql.mem.write(tp, bytes(ts_obj)) return 0 + def ql_syscall_gettimeofday(ql: Qiling, tv: int, tz: int): if tv: ts_obj = __get_timespec_obj(ql.arch.bits) ql.mem.write(tv, bytes(ts_obj)) if tz: - ql.mem.write(tz, b'\x00' * 8) + ql.mem.write(tz, b"\x00" * 8) + + return 0 + + +# Handle seconds conversions 'in house' +def microseconds_to_nanoseconds(s): + return s * 1000 + +def seconds_to_nanoseconds(s): + return s * 1000000000 + + +""" +Actual implmentation of utime(s) +Rather than repeat work based on different +precision requirements, just convert seconds/microseconds +to ns and pass to os.utime() +""" + + +def do_utime(ql: Qiling, filename: ctypes.POINTER, times: ctypes.POINTER, s): + real_file = "" + try: + # get path inside of qiling rootfs + real_file = ql.os.path.transform_to_real_path(ql.mem.string(filename)) + except Exception as ex: # return errors appropriately, don't try to handle + # everything ourselves + return -ex.errno + actime = modtime = 0 + """ + times[0] specifies the new access time, and times[1] specifies the new modification time. + If times is NULL, then analogously to utime(), the access and modification times of the file are set to the + current time. + """ + if s: # utimes, times[0] == new access time, times[1] == modification + data = make_timeval_buf(ql.arch.bits, ql.arch.endian) + with data.ref(ql.mem, times) as ref_atime: # times[0] + actime = seconds_to_nanoseconds(ref_atime.tv_sec) + actime += microseconds_to_nanoseconds(ref_atime.tv_usec) + with data.ref( + ql.mem, times + ctypes.sizeof(data) + ) as ref_mtime: # increment by ctypes.sizeof() to get times[1] + modtime = seconds_to_nanoseconds(ref_mtime.tv_sec) + modtime += microseconds_to_nanoseconds(ref_mtime.tv_usec) + + else: + # utime uses utimbuf, so different data handling needs to be done + data = make_utimbuf(ql.arch.bits, ql.arch.endian) + with data.ref(ql.mem, times) as ref: + actime = seconds_to_nanoseconds(ref.actime) + modtime = seconds_to_nanoseconds(ref.modtime) + try: + os.utime(real_file, ns=(actime, modtime)) + except Exception as ex: + return -ex.errno return 0 + + +""" +https://www.man7.org/linux/man-pages/man2/utimes.2.html + int utime(const char *filename, + const struct utimbuf *_Nullable times); +""" + + +def ql_syscall_utime(ql: Qiling, filename: ctypes.POINTER, times: ctypes.POINTER): + return do_utime(ql, filename, times, False) # False for 's' means + # do plain utime + + +""" +https://www.man7.org/linux/man-pages/man2/utimes.2.html + int utimes(const char *filename, + const struct timeval times[_Nullable 2]); +""" + + +def ql_syscall_utimes(ql: Qiling, filename: ctypes.POINTER, times: ctypes.POINTER): + return do_utime(ql, filename, times, True) # True for 's' means the + # we want 'utimes', which has a different prototype, and consequently, + # struct unpacking requirements, than utime + + +""" +Not re-using the do_utime implementation so we can handle +the dfd and timespec unpacking here +""" + + +def do_utime_fd_ns( + ql: Qiling, + dfd: int, + filename: ctypes.POINTER, + utimes: ctypes.POINTER, + flags: int, + symlinks, +): + # transform to real path, which ensures that we are + # operating inside of the qiling root + unpacked_filename = ql.os.path.transform_to_real_path(ql.mem.string(filename)) + timespec_struct = make_timespec_buf(ql.arch.bits, ql.arch.endian) + atime_nsec = mtime_nsec = 0 + if dfd is not None: + dfd = ql.os.fd[dfd].fileno + with timespec_struct.ref(ql.mem, utimes) as atime_ref: + atime_nsec = atime_ref.tv_nsec + atime_nsec += seconds_to_nanoseconds(atime_ref.tv_sec) + with timespec_struct.ref( + ql.mem, utimes + ctypes.sizeof(timespec_struct) + ) as mtime_ref: + mtime_nsec = mtime_ref.tv_nsec + mtime_nsec += seconds_to_nanoseconds(mtime_ref.tv_sec) + ql.log.debug(f"Got filename {unpacked_filename} for utimensat syscall ") + try: + os.utime( + unpacked_filename, + ns=(atime_nsec, mtime_nsec), + dir_fd=dfd, + follow_symlinks=symlinks, + ) + except Exception as ex: + return -ex.errno + return 0 + + +""" +https://www.man7.org/linux/man-pages/man2/utimensat.2.html + sys_utimensat int dfd const char *filename struct timespec *utimes int flags +""" + + +def ql_syscall_utimensat( + ql: Qiling, dfd: int, filename: ctypes.POINTER, utimes: ctypes.POINTER, flags: int +): + if filename == 0: + return EACCES + if utimes == 0: + return EACCES + if dfd == AT_FDCWD: + dfd = None + if flags == AT_SYMLINK_NOFOLLOW: + follow_symlink = False + else: + follow_symlink = True + return do_utime_fd_ns(ql, dfd, filename, utimes, flags, follow_symlink) + + +""" +This is considered deprecated, +https://www.man7.org/linux/man-pages/man2/futimesat.2.html +but including here in case some legacy code needs it +int futimesat(int dirfd, const char *pathname, + const struct timeval times[2]); +""" + + +def ql_syscall_futimesat( + ql: Qiling, dfd: int, pathname: ctypes.POINTER, timeval: ctypes.POINTER +): + + return ql_syscall_utimensat(ql, dfd, pathname, timeval, 0) diff --git a/qiling/os/posix/const.py b/qiling/os/posix/const.py index df4c5b587..050746577 100644 --- a/qiling/os/posix/const.py +++ b/qiling/os/posix/const.py @@ -751,8 +751,9 @@ class qnx_mmap_prot_flags(QlPrettyFlag): FD_CLOEXEC = 1 -AT_FDCWD = -100 +AT_FDCWD = 0xffffff9c # /usr/include/linux/fcntl.h AT_EMPTY_PATH = 0x1000 +AT_SYMLINK_NOFOLLOW = 0x100 # error code EPERM = 1 diff --git a/qiling/os/posix/structs.py b/qiling/os/posix/structs.py index a43239ade..c97f7593f 100644 --- a/qiling/os/posix/structs.py +++ b/qiling/os/posix/structs.py @@ -143,3 +143,48 @@ class pollfd(Struct): ) return pollfd + +def make_utimbuf(archbits: int, endian: QL_ENDIAN): + Struct = struct.get_aligned_struct(archbits, endian) + class utimbuf(Struct): + if archbits == 32: + _fields_ = ( + ('actime', ctypes.c_int32), + ('modtime', ctypes.c_int32) + ) + else: + _fields_ = ( + ('actime', ctypes.c_int64), + ('modtime', ctypes.c_int64) + ) + return utimbuf + +def make_timespec_buf(archbits: int, endian: QL_ENDIAN): + Struct = struct.get_aligned_struct(archbits, endian) + class timespec(Struct): + if archbits == 32: + _fields_ = ( + ('tv_sec', ctypes.c_uint32), + ('tv_nsec', ctypes.c_uint32) + ) + else: + _fields_ = ( + ('tv_sec', ctypes.c_long), + ('tv_nsec', ctypes.c_long) + ) + return timespec + +def make_timeval_buf(archbits: int, endian: QL_ENDIAN): + Struct = struct.get_aligned_struct(archbits, endian) + class timeval(Struct): + if archbits == 64: + _fields_ = ( + ('tv_sec', ctypes.c_long), + ('tv_usec', ctypes.c_long) + ) + else: + _fields_ = ( + ('tv_sec', ctypes.c_uint32), + ('tv_usec', ctypes.c_uint32) + ) + return timeval diff --git a/tests/test_elf.py b/tests/test_elf.py index 2798028b7..a6e03d8cc 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -831,6 +831,32 @@ def client(): self.assertIn(b'hello world', ql.os.stdout.read(200)) # 200 is arbitrary--"good enough" for this task del ql + def test_elf_linux_x8664_utime(self): + rootfs = "../examples/rootfs/x8664_linux_glibc2.39" + argv = r"../examples/rootfs/x8664_linux_glibc2.39/bin/x8664_linux_utime".split() + targets = [ + f"{rootfs}/utimensat-test", + f"{rootfs}/utime-test", + f"{rootfs}/utimes-test", + f"{rootfs}/futimesat-test" + ] + for t in targets: + with open(t, "wb" ) as test_file: + test_file.write(b"qiling_test") + # Access time in seconds + atime = 200000000 + + # Modification time in seconds + mtime = 100000000 + os.utime(t, (atime, mtime)) + ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG) + ql.run() + for t in targets: + mtime = os.path.getmtime(t) + atime = os.path.getatime(t) + self.assertNotAlmostEqual(mtime, 100000000) + self.assertNotAlmostEqual(atime, 200000000) + del ql if __name__ == "__main__": unittest.main()