5499802b22
The conversion from __copy_from_user() to __get_user() by commitd3ccc97815
("powerpc/signal: Use __get_user() to copy sigset_t") introduced a regression in __get_user_sigset() for powerpc/32. The bug was subsequently moved into unsafe_get_user_sigset(). The bug is due to the copied 64 bit value being truncated to 32 bits while being assigned to dst->sig[0] The regression was reported by users of the Xorg packages distributed in Debian/powerpc -- "The symptoms are that the fb screen goes blank, with the backlight remaining on and no errors logged in /var/log; wdm (or startx) run with no effect (I tried logging in in the blind, with no effect). And they are hard to kill, requiring 'kill -KILL ...'" Fix the regression by copying each word of the sigset, not only the first one. __get_user_sigset() was tentatively optimised to copy 64 bits at once in order to minimise KUAP unlock/lock impact, but the unsafe variant doesn't suffer that, so it can just copy words. Fixes:887f3ceb51
("powerpc/signal32: Convert do_setcontext[_tm]() to user access block") Cc: stable@vger.kernel.org # v5.13+ Reported-by: Finn Thain <fthain@linux-m68k.org> Reported-and-tested-by: Stan Johnson <userm57@yahoo.com> Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/99ef38d61c0eb3f79c68942deb0c35995a93a777.1636966353.git.christophe.leroy@csgroup.eu
214 lines
6.7 KiB
C
214 lines
6.7 KiB
C
/*
|
|
* Copyright (c) 2007 Benjamin Herrenschmidt, IBM Corporation
|
|
* Extracted from signal_32.c and signal_64.c
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General
|
|
* Public License. See the file README.legal in the main directory of
|
|
* this archive for more details.
|
|
*/
|
|
|
|
#ifndef _POWERPC_ARCH_SIGNAL_H
|
|
#define _POWERPC_ARCH_SIGNAL_H
|
|
|
|
void __user *get_sigframe(struct ksignal *ksig, struct task_struct *tsk,
|
|
size_t frame_size, int is_32);
|
|
|
|
extern int handle_signal32(struct ksignal *ksig, sigset_t *oldset,
|
|
struct task_struct *tsk);
|
|
|
|
extern int handle_rt_signal32(struct ksignal *ksig, sigset_t *oldset,
|
|
struct task_struct *tsk);
|
|
|
|
static inline int __get_user_sigset(sigset_t *dst, const sigset_t __user *src)
|
|
{
|
|
BUILD_BUG_ON(sizeof(sigset_t) != sizeof(u64));
|
|
|
|
return __get_user(dst->sig[0], (u64 __user *)&src->sig[0]);
|
|
}
|
|
#define unsafe_get_user_sigset(dst, src, label) do { \
|
|
sigset_t *__dst = dst; \
|
|
const sigset_t __user *__src = src; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < _NSIG_WORDS; i++) \
|
|
unsafe_get_user(__dst->sig[i], &__src->sig[i], label); \
|
|
} while (0)
|
|
|
|
#ifdef CONFIG_VSX
|
|
extern unsigned long copy_vsx_to_user(void __user *to,
|
|
struct task_struct *task);
|
|
extern unsigned long copy_ckvsx_to_user(void __user *to,
|
|
struct task_struct *task);
|
|
extern unsigned long copy_vsx_from_user(struct task_struct *task,
|
|
void __user *from);
|
|
extern unsigned long copy_ckvsx_from_user(struct task_struct *task,
|
|
void __user *from);
|
|
unsigned long copy_fpr_to_user(void __user *to, struct task_struct *task);
|
|
unsigned long copy_ckfpr_to_user(void __user *to, struct task_struct *task);
|
|
unsigned long copy_fpr_from_user(struct task_struct *task, void __user *from);
|
|
unsigned long copy_ckfpr_from_user(struct task_struct *task, void __user *from);
|
|
|
|
#define unsafe_copy_fpr_to_user(to, task, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)to; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NFPREG - 1 ; i++) \
|
|
unsafe_put_user(__t->thread.TS_FPR(i), &buf[i], label); \
|
|
unsafe_put_user(__t->thread.fp_state.fpscr, &buf[i], label); \
|
|
} while (0)
|
|
|
|
#define unsafe_copy_vsx_to_user(to, task, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)to; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NVSRHALFREG ; i++) \
|
|
unsafe_put_user(__t->thread.fp_state.fpr[i][TS_VSRLOWOFFSET], \
|
|
&buf[i], label);\
|
|
} while (0)
|
|
|
|
#define unsafe_copy_fpr_from_user(task, from, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)from; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NFPREG - 1; i++) \
|
|
unsafe_get_user(__t->thread.TS_FPR(i), &buf[i], label); \
|
|
unsafe_get_user(__t->thread.fp_state.fpscr, &buf[i], label); \
|
|
} while (0)
|
|
|
|
#define unsafe_copy_vsx_from_user(task, from, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)from; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NVSRHALFREG ; i++) \
|
|
unsafe_get_user(__t->thread.fp_state.fpr[i][TS_VSRLOWOFFSET], \
|
|
&buf[i], label); \
|
|
} while (0)
|
|
|
|
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
|
|
#define unsafe_copy_ckfpr_to_user(to, task, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)to; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NFPREG - 1 ; i++) \
|
|
unsafe_put_user(__t->thread.TS_CKFPR(i), &buf[i], label);\
|
|
unsafe_put_user(__t->thread.ckfp_state.fpscr, &buf[i], label); \
|
|
} while (0)
|
|
|
|
#define unsafe_copy_ckvsx_to_user(to, task, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)to; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NVSRHALFREG ; i++) \
|
|
unsafe_put_user(__t->thread.ckfp_state.fpr[i][TS_VSRLOWOFFSET], \
|
|
&buf[i], label);\
|
|
} while (0)
|
|
|
|
#define unsafe_copy_ckfpr_from_user(task, from, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)from; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NFPREG - 1 ; i++) \
|
|
unsafe_get_user(__t->thread.TS_CKFPR(i), &buf[i], label);\
|
|
unsafe_get_user(__t->thread.ckfp_state.fpscr, &buf[i], failed); \
|
|
} while (0)
|
|
|
|
#define unsafe_copy_ckvsx_from_user(task, from, label) do { \
|
|
struct task_struct *__t = task; \
|
|
u64 __user *buf = (u64 __user *)from; \
|
|
int i; \
|
|
\
|
|
for (i = 0; i < ELF_NVSRHALFREG ; i++) \
|
|
unsafe_get_user(__t->thread.ckfp_state.fpr[i][TS_VSRLOWOFFSET], \
|
|
&buf[i], label); \
|
|
} while (0)
|
|
#endif
|
|
#elif defined(CONFIG_PPC_FPU_REGS)
|
|
|
|
#define unsafe_copy_fpr_to_user(to, task, label) \
|
|
unsafe_copy_to_user(to, (task)->thread.fp_state.fpr, \
|
|
ELF_NFPREG * sizeof(double), label)
|
|
|
|
#define unsafe_copy_fpr_from_user(task, from, label) \
|
|
unsafe_copy_from_user((task)->thread.fp_state.fpr, from, \
|
|
ELF_NFPREG * sizeof(double), label)
|
|
|
|
static inline unsigned long
|
|
copy_fpr_to_user(void __user *to, struct task_struct *task)
|
|
{
|
|
return __copy_to_user(to, task->thread.fp_state.fpr,
|
|
ELF_NFPREG * sizeof(double));
|
|
}
|
|
|
|
static inline unsigned long
|
|
copy_fpr_from_user(struct task_struct *task, void __user *from)
|
|
{
|
|
return __copy_from_user(task->thread.fp_state.fpr, from,
|
|
ELF_NFPREG * sizeof(double));
|
|
}
|
|
|
|
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
|
|
#define unsafe_copy_ckfpr_to_user(to, task, label) \
|
|
unsafe_copy_to_user(to, (task)->thread.ckfp_state.fpr, \
|
|
ELF_NFPREG * sizeof(double), label)
|
|
|
|
inline unsigned long copy_ckfpr_to_user(void __user *to, struct task_struct *task)
|
|
{
|
|
return __copy_to_user(to, task->thread.ckfp_state.fpr,
|
|
ELF_NFPREG * sizeof(double));
|
|
}
|
|
|
|
static inline unsigned long
|
|
copy_ckfpr_from_user(struct task_struct *task, void __user *from)
|
|
{
|
|
return __copy_from_user(task->thread.ckfp_state.fpr, from,
|
|
ELF_NFPREG * sizeof(double));
|
|
}
|
|
#endif /* CONFIG_PPC_TRANSACTIONAL_MEM */
|
|
#else
|
|
#define unsafe_copy_fpr_to_user(to, task, label) do { if (0) goto label;} while (0)
|
|
|
|
#define unsafe_copy_fpr_from_user(task, from, label) do { if (0) goto label;} while (0)
|
|
|
|
static inline unsigned long
|
|
copy_fpr_to_user(void __user *to, struct task_struct *task)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned long
|
|
copy_fpr_from_user(struct task_struct *task, void __user *from)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PPC64
|
|
|
|
extern int handle_rt_signal64(struct ksignal *ksig, sigset_t *set,
|
|
struct task_struct *tsk);
|
|
|
|
#else /* CONFIG_PPC64 */
|
|
|
|
extern long sys_rt_sigreturn(void);
|
|
extern long sys_sigreturn(void);
|
|
|
|
static inline int handle_rt_signal64(struct ksignal *ksig, sigset_t *set,
|
|
struct task_struct *tsk)
|
|
{
|
|
return -EFAULT;
|
|
}
|
|
|
|
#endif /* !defined(CONFIG_PPC64) */
|
|
|
|
void signal_fault(struct task_struct *tsk, struct pt_regs *regs,
|
|
const char *where, void __user *ptr);
|
|
|
|
#endif /* _POWERPC_ARCH_SIGNAL_H */
|