]> www.infradead.org Git - users/willy/linux.git/commitdiff
arm64: compat: Ensure upper 32 bits of x0 are zero on syscall return
authorWill Deacon <will@kernel.org>
Fri, 3 Jul 2020 11:08:42 +0000 (12:08 +0100)
committerWill Deacon <will@kernel.org>
Thu, 16 Jul 2020 10:41:31 +0000 (11:41 +0100)
Although we zero the upper bits of x0 on entry to the kernel from an
AArch32 task, we do not clear them on the exception return path and can
therefore expose 64-bit sign extended syscall return values to userspace
via interfaces such as the 'perf_regs' ABI, which deal exclusively with
64-bit registers.

Explicitly clear the upper 32 bits of x0 on return from a compat system
call.

Cc: <stable@vger.kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Keno Fischer <keno@juliacomputing.com>
Cc: Luis Machado <luis.machado@linaro.org>
Signed-off-by: Will Deacon <will@kernel.org>
arch/arm64/include/asm/syscall.h
arch/arm64/kernel/syscall.c

index 65299a2dcf9cd3d0c233a74e28ef8f7f6d146bc8..cfc0672013f672aaf77a18b1c2f3a7a5e692266e 100644 (file)
@@ -34,6 +34,10 @@ static inline long syscall_get_error(struct task_struct *task,
                                     struct pt_regs *regs)
 {
        unsigned long error = regs->regs[0];
+
+       if (is_compat_thread(task_thread_info(task)))
+               error = sign_extend64(error, 31);
+
        return IS_ERR_VALUE(error) ? error : 0;
 }
 
@@ -47,7 +51,13 @@ static inline void syscall_set_return_value(struct task_struct *task,
                                            struct pt_regs *regs,
                                            int error, long val)
 {
-       regs->regs[0] = (long) error ? error : val;
+       if (error)
+               val = error;
+
+       if (is_compat_thread(task_thread_info(task)))
+               val = lower_32_bits(val);
+
+       regs->regs[0] = val;
 }
 
 #define SYSCALL_MAX_ARGS 6
index 7c14466a12af34ae362e09cc78f733290a1d6175..98a26d4e7b0c875af9e0c257ccefe38654f11755 100644 (file)
@@ -50,6 +50,9 @@ static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
                ret = do_ni_syscall(regs, scno);
        }
 
+       if (is_compat_task())
+               ret = lower_32_bits(ret);
+
        regs->regs[0] = ret;
 }