arm64: Rework valid_user_regs
[cascardo/linux.git] / arch / arm64 / kernel / ptrace.c
index ff7f132..3f6cd5c 100644 (file)
@@ -500,7 +500,7 @@ static int gpr_set(struct task_struct *target, const struct user_regset *regset,
        if (ret)
                return ret;
 
-       if (!valid_user_regs(&newregs))
+       if (!valid_user_regs(&newregs, target))
                return -EINVAL;
 
        task_pt_regs(target)->user_regs = newregs;
@@ -770,7 +770,7 @@ static int compat_gpr_set(struct task_struct *target,
 
        }
 
-       if (valid_user_regs(&newregs.user_regs))
+       if (valid_user_regs(&newregs.user_regs, target))
                *task_pt_regs(target) = newregs;
        else
                ret = -EINVAL;
@@ -1272,3 +1272,79 @@ asmlinkage void syscall_trace_exit(struct pt_regs *regs)
        if (test_thread_flag(TIF_SYSCALL_TRACE))
                tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT);
 }
+
+/*
+ * Bits which are always architecturally RES0 per ARM DDI 0487A.h
+ * Userspace cannot use these until they have an architectural meaning.
+ * We also reserve IL for the kernel; SS is handled dynamically.
+ */
+#define SPSR_EL1_AARCH64_RES0_BITS \
+       (GENMASK_ULL(63,32) | GENMASK_ULL(27, 22) | GENMASK_ULL(20, 10) | \
+        GENMASK_ULL(5, 5))
+#define SPSR_EL1_AARCH32_RES0_BITS \
+       (GENMASK_ULL(63,32) | GENMASK_ULL(24, 22) | GENMASK_ULL(20,20))
+
+static int valid_compat_regs(struct user_pt_regs *regs)
+{
+       regs->pstate &= ~SPSR_EL1_AARCH32_RES0_BITS;
+
+       if (!system_supports_mixed_endian_el0()) {
+               if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+                       regs->pstate |= COMPAT_PSR_E_BIT;
+               else
+                       regs->pstate &= ~COMPAT_PSR_E_BIT;
+       }
+
+       if (user_mode(regs) && (regs->pstate & PSR_MODE32_BIT) &&
+           (regs->pstate & COMPAT_PSR_A_BIT) == 0 &&
+           (regs->pstate & COMPAT_PSR_I_BIT) == 0 &&
+           (regs->pstate & COMPAT_PSR_F_BIT) == 0) {
+               return 1;
+       }
+
+       /*
+        * Force PSR to a valid 32-bit EL0t, preserving the same bits as
+        * arch/arm.
+        */
+       regs->pstate &= COMPAT_PSR_N_BIT | COMPAT_PSR_Z_BIT |
+                       COMPAT_PSR_C_BIT | COMPAT_PSR_V_BIT |
+                       COMPAT_PSR_Q_BIT | COMPAT_PSR_IT_MASK |
+                       COMPAT_PSR_GE_MASK | COMPAT_PSR_E_BIT |
+                       COMPAT_PSR_T_BIT;
+       regs->pstate |= PSR_MODE32_BIT;
+
+       return 0;
+}
+
+static int valid_native_regs(struct user_pt_regs *regs)
+{
+       regs->pstate &= ~SPSR_EL1_AARCH64_RES0_BITS;
+
+       if (user_mode(regs) && !(regs->pstate & PSR_MODE32_BIT) &&
+           (regs->pstate & PSR_D_BIT) == 0 &&
+           (regs->pstate & PSR_A_BIT) == 0 &&
+           (regs->pstate & PSR_I_BIT) == 0 &&
+           (regs->pstate & PSR_F_BIT) == 0) {
+               return 1;
+       }
+
+       /* Force PSR to a valid 64-bit EL0t */
+       regs->pstate &= PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT;
+
+       return 0;
+}
+
+/*
+ * Are the current registers suitable for user mode? (used to maintain
+ * security in signal handlers)
+ */
+int valid_user_regs(struct user_pt_regs *regs, struct task_struct *task)
+{
+       if (!test_tsk_thread_flag(task, TIF_SINGLESTEP))
+               regs->pstate &= ~DBG_SPSR_SS;
+
+       if (is_compat_thread(task_thread_info(task)))
+               return valid_compat_regs(regs);
+       else
+               return valid_native_regs(regs);
+}