From: Sven Schnelle <sv...@linux.ibm.com>

[ Upstream commit 873e5a763d604c32988c4a78913a8dab3862d2f9 ]

When strace wants to update the syscall number, it sets GPR2
to the desired number and updates the GPR via PTRACE_SETREGSET.
It doesn't update regs->int_code which would cause the old syscall
executed on syscall restart. As we cannot change the ptrace ABI and
don't have a field for the interruption code, check whether the tracee
is in a syscall and the last instruction was svc. In that case assume
that the tracer wants to update the syscall number and copy the GPR2
value to regs->int_code.

Signed-off-by: Sven Schnelle <sv...@linux.ibm.com>
Signed-off-by: Vasily Gorbik <g...@linux.ibm.com>
Signed-off-by: Sasha Levin <sas...@kernel.org>
---
 arch/s390/kernel/ptrace.c | 31 ++++++++++++++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/arch/s390/kernel/ptrace.c b/arch/s390/kernel/ptrace.c
index cd3df5514552c..65fefbf61e1ca 100644
--- a/arch/s390/kernel/ptrace.c
+++ b/arch/s390/kernel/ptrace.c
@@ -325,6 +325,25 @@ static inline void __poke_user_per(struct task_struct 
*child,
                child->thread.per_user.end = data;
 }
 
+static void fixup_int_code(struct task_struct *child, addr_t data)
+{
+       struct pt_regs *regs = task_pt_regs(child);
+       int ilc = regs->int_code >> 16;
+       u16 insn;
+
+       if (ilc > 6)
+               return;
+
+       if (ptrace_access_vm(child, regs->psw.addr - (regs->int_code >> 16),
+                       &insn, sizeof(insn), FOLL_FORCE) != sizeof(insn))
+               return;
+
+       /* double check that tracee stopped on svc instruction */
+       if ((insn >> 8) != 0xa)
+               return;
+
+       regs->int_code = 0x20000 | (data & 0xffff);
+}
 /*
  * Write a word to the user area of a process at location addr. This
  * operation does have an additional problem compared to peek_user.
@@ -336,7 +355,9 @@ static int __poke_user(struct task_struct *child, addr_t 
addr, addr_t data)
        struct user *dummy = NULL;
        addr_t offset;
 
+
        if (addr < (addr_t) &dummy->regs.acrs) {
+               struct pt_regs *regs = task_pt_regs(child);
                /*
                 * psw and gprs are stored on the stack
                 */
@@ -354,7 +375,11 @@ static int __poke_user(struct task_struct *child, addr_t 
addr, addr_t data)
                                /* Invalid addressing mode bits */
                                return -EINVAL;
                }
-               *(addr_t *)((addr_t) &task_pt_regs(child)->psw + addr) = data;
+
+               if (test_pt_regs_flag(regs, PIF_SYSCALL) &&
+                       addr == offsetof(struct user, regs.gprs[2]))
+                       fixup_int_code(child, data);
+               *(addr_t *)((addr_t) &regs->psw + addr) = data;
 
        } else if (addr < (addr_t) (&dummy->regs.orig_gpr2)) {
                /*
@@ -720,6 +745,10 @@ static int __poke_user_compat(struct task_struct *child,
                        regs->psw.mask = (regs->psw.mask & ~PSW_MASK_BA) |
                                (__u64)(tmp & PSW32_ADDR_AMODE);
                } else {
+
+                       if (test_pt_regs_flag(regs, PIF_SYSCALL) &&
+                               addr == offsetof(struct compat_user, 
regs.gprs[2]))
+                               fixup_int_code(child, data);
                        /* gpr 0-15 */
                        *(__u32*)((addr_t) &regs->psw + addr*2 + 4) = tmp;
                }
-- 
2.25.1

Reply via email to