about summary refs log tree commit diff stats
path: root/src/focaccia/qemu/x86.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/focaccia/qemu/x86.py')
-rw-r--r--src/focaccia/qemu/x86.py194
1 files changed, 193 insertions, 1 deletions
diff --git a/src/focaccia/qemu/x86.py b/src/focaccia/qemu/x86.py
index 3136fce..25ab874 100644
--- a/src/focaccia/qemu/x86.py
+++ b/src/focaccia/qemu/x86.py
@@ -1,3 +1,7 @@
+import struct
+from typing import Optional
+from dataclasses import dataclass, field
+
 from focaccia.qemu.syscall import SyscallInfo
 
 # Incomplete, only the most common ones
@@ -12,7 +16,7 @@ emulated_system_calls = {
     8: SyscallInfo('lseek'),
     13: SyscallInfo('rt_sigaction', patchup_address_registers=['rdx']),
     14: SyscallInfo('rt_sigprocmask', patchup_address_registers=['rdx']),
-    15: SyscallInfo('rt_sigreturn'),
+    15: SyscallInfo('rt_sigreturn', return_from_signal=True),
     16: SyscallInfo('ioctl', patchup_address_registers=['rdx']),
     17: SyscallInfo('pread64', patchup_address_registers=['rsi']),
     18: SyscallInfo('pwrite64'),
@@ -109,3 +113,191 @@ vdso_system_calls = {
     309: SyscallInfo('getcpu', patchup_address_registers=['rdi', 'rsi', 'rdx'])
 }
 
+@dataclass
+class SigContext:
+    """
+    Represents struct sigcontext on Linux x86-64.
+    You fill these like ctx.r8 = 123, ctx.rip = 0x400abc, etc.
+    """
+
+    # GPRs in kernel-defined order
+    r8: int = 0
+    r9: int = 0
+    r10: int = 0
+    r11: int = 0
+    r12: int = 0
+    r13: int = 0
+    r14: int = 0
+    r15: int = 0
+    rdi: int = 0
+    rsi: int = 0
+    rbp: int = 0
+    rbx: int = 0
+    rdx: int = 0
+    rax: int = 0
+    rcx: int = 0
+    rsp: int = 0  # OLD rsp before signal
+    rip: int = 0  # OLD rip before signal
+
+    eflags: int = 0
+    cs: int = 0x33
+    ss: int = 0x2b
+
+    err: int = 0
+    trapno: int = 0
+    oldmask: int = 0
+    cr2: int = 0
+    fpstate: int = 0   # pointer (kernel uses this)
+
+    # Reserved padding space
+    reserved1: int = 0
+    reserved2: int = 0
+    reserved3: int = 0
+
+    def to_bytes(self) -> bytes:
+        """
+        Pack exactly like struct sigcontext on x86-64.
+        """
+        fields = [
+            self.r8, self.r9, self.r10, self.r11,
+            self.r12, self.r13, self.r14, self.r15,
+            self.rdi, self.rsi, self.rbp, self.rbx,
+            self.rdx, self.rax, self.rcx, self.rsp,
+            self.rip,
+            self.eflags,
+            self.cs, self.ss,
+            self.err, self.trapno, self.oldmask, self.cr2,
+            self.fpstate,
+            self.reserved1, self.reserved2, self.reserved3,
+        ]
+        # All fields are 64-bit except CS/SS (16-bit)
+        # But kernel packs everything on 8-byte boundaries anyway.
+        return struct.pack("<" + "Q"*len(fields), *fields)
+
+
+# ---------------------------------------------------------------------
+# 2. Minimal siginfo_t abstraction (128 bytes on x86-64)
+# ---------------------------------------------------------------------
+
+@dataclass
+class SigInfo:
+    """
+    Minimal representation. You can fill as needed.
+    Layout here is fixed to 128 bytes.
+    Only a few useful fields are exposed.
+    """
+
+    si_signo: int = 0
+    si_errno: int = 0
+    si_code: int = 0
+    si_pid: int = 0
+    si_uid: int = 0
+
+    def to_bytes(self) -> bytes:
+        # Linux siginfo is 128 bytes; real layout is complex.
+        # We place the common initial fields and pad the rest.
+        buf = bytearray(128)
+        struct.pack_into("<iii", buf, 0, self.si_signo, self.si_errno, self.si_code)
+        struct.pack_into("<II", buf, 16, self.si_pid, self.si_uid)
+        return bytes(buf)
+
+
+# ---------------------------------------------------------------------
+# 3. ucontext_t wrapper (only what matters for signal return)
+# ---------------------------------------------------------------------
+
+@dataclass
+class UContext:
+    """
+    Only the parts required for correct signal return.
+    """
+    sigmask: int = 0    # For simplicity; real sigset_t is 8*16 bytes
+    mcontext: SigContext = field(default_factory=SigContext)
+
+    UC_FLAGS: int = 1  # Usually UC_FP_XSTATE
+
+    def to_bytes(self) -> bytes:
+        """
+        Real ucontext_t is large. Here we pack:
+          - uc_flags (8 bytes)
+          - uc_link  (8 bytes, NULL)
+          - stack_t  (3 * 8 bytes)
+          - sigmask  (128 bytes normally; we use 8 bytes for simplicity)
+          - padding up to 0x2c0
+          - mcontext (struct sigcontext)
+        IMPORTANT: total size must be 0x2c0 on x86-64.
+        """
+        uc_buf = bytearray(0x2c0)
+
+        # uc_flags + uc_link(NULL)
+        struct.pack_into("<Q", uc_buf, 0, self.UC_FLAGS)
+        struct.pack_into("<Q", uc_buf, 8, 0)
+
+        # stack_t (ss_sp, ss_flags, ss_size)
+        struct.pack_into("<QQQ", uc_buf, 16, 0, 0, 0)
+
+        # sigmask (we store a minimal 8 bytes)
+        struct.pack_into("<Q", uc_buf, 40, self.sigmask)
+
+        # Now embed sigcontext at the end of ucontext
+        mctx_bytes = self.mcontext.to_bytes()
+        uc_buf[-len(mctx_bytes):] = mctx_bytes
+
+        return bytes(uc_buf)
+
+
+# ---------------------------------------------------------------------
+# 4. Full rt_sigframe abstraction
+# ---------------------------------------------------------------------
+
+@dataclass
+class SigFrame:
+    sp_new: int                      # RSP after signal delivery
+    pretcode: int                    # pointer to restorer trampoline
+    uctx: UContext                   # full ucontext (incl. sigcontext)
+    siginfo: SigInfo                 # siginfo_t
+    tail_size: int = 0               # optional xstate padding
+
+    PRETCODE_SIZE: int = 8
+    UCONTEXT_SIZE: int = 0x2c0
+    SIGINFO_SIZE: int = 128
+
+    @property
+    def uc_addr(self) -> int:
+        return self.sp_new + self.PRETCODE_SIZE
+
+    @property
+    def siginfo_addr(self) -> int:
+        return self.uc_addr + self.UCONTEXT_SIZE
+
+    @property
+    def tail_addr(self) -> int:
+        return self.siginfo_addr + self.SIGINFO_SIZE
+
+    @property
+    def rdx_uc(self) -> int:
+        """What to set in guest RDX."""
+        return self.uc_addr
+
+    @property
+    def rsi_siginfo(self) -> int:
+        """What to set in guest RSI."""
+        return self.siginfo_addr
+
+    def to_bytes(self) -> bytes:
+        buf = bytearray(self.PRETCODE_SIZE + self.UCONTEXT_SIZE +
+                        self.SIGINFO_SIZE + self.tail_size)
+
+        # pretcode
+        struct.pack_into("<Q", buf, 0, self.pretcode)
+
+        # ucontext
+        buf[self.PRETCODE_SIZE : self.PRETCODE_SIZE + self.UCONTEXT_SIZE] = \
+            self.uctx.to_bytes()
+
+        # siginfo
+        si_off = self.PRETCODE_SIZE + self.UCONTEXT_SIZE
+        buf[si_off : si_off + self.SIGINFO_SIZE] = self.siginfo.to_bytes()
+
+        return bytes(buf)
+