mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-22 01:10:28 +00:00
sparc64: Fix race in signal instruction flushing.
If another cpu does a very wide munmap() on the signal frame area, it can tear down the page table hierarchy from underneath us. Borrow an idea from the 64-bit fault path's get_user_insn(), and disable cross call interrupts during the page table traversal to lock them in place while we operate. Reported-by: Al Viro <viro@ZenIV.linux.org.uk> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
b343ae51c1
commit
05c5e7698b
@ -453,6 +453,64 @@ static int save_fpu_state32(struct pt_regs *regs, __siginfo_fpu_t __user *fpu)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* The I-cache flush instruction only works in the primary ASI, which
|
||||
* right now is the nucleus, aka. kernel space.
|
||||
*
|
||||
* Therefore we have to kick the instructions out using the kernel
|
||||
* side linear mapping of the physical address backing the user
|
||||
* instructions.
|
||||
*/
|
||||
static void flush_signal_insns(unsigned long address)
|
||||
{
|
||||
unsigned long pstate, paddr;
|
||||
pte_t *ptep, pte;
|
||||
pgd_t *pgdp;
|
||||
pud_t *pudp;
|
||||
pmd_t *pmdp;
|
||||
|
||||
/* Commit all stores of the instructions we are about to flush. */
|
||||
wmb();
|
||||
|
||||
/* Disable cross-call reception. In this way even a very wide
|
||||
* munmap() on another cpu can't tear down the page table
|
||||
* hierarchy from underneath us, since that can't complete
|
||||
* until the IPI tlb flush returns.
|
||||
*/
|
||||
|
||||
__asm__ __volatile__("rdpr %%pstate, %0" : "=r" (pstate));
|
||||
__asm__ __volatile__("wrpr %0, %1, %%pstate"
|
||||
: : "r" (pstate), "i" (PSTATE_IE));
|
||||
|
||||
pgdp = pgd_offset(current->mm, address);
|
||||
if (pgd_none(*pgdp))
|
||||
goto out_irqs_on;
|
||||
pudp = pud_offset(pgdp, address);
|
||||
if (pud_none(*pudp))
|
||||
goto out_irqs_on;
|
||||
pmdp = pmd_offset(pudp, address);
|
||||
if (pmd_none(*pmdp))
|
||||
goto out_irqs_on;
|
||||
|
||||
ptep = pte_offset_map(pmdp, address);
|
||||
pte = *ptep;
|
||||
if (!pte_present(pte))
|
||||
goto out_unmap;
|
||||
|
||||
paddr = (unsigned long) page_address(pte_page(pte));
|
||||
|
||||
__asm__ __volatile__("flush %0 + %1"
|
||||
: /* no outputs */
|
||||
: "r" (paddr),
|
||||
"r" (address & (PAGE_SIZE - 1))
|
||||
: "memory");
|
||||
|
||||
out_unmap:
|
||||
pte_unmap(ptep);
|
||||
out_irqs_on:
|
||||
__asm__ __volatile__("wrpr %0, 0x0, %%pstate" : : "r" (pstate));
|
||||
|
||||
}
|
||||
|
||||
static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs,
|
||||
int signo, sigset_t *oldset)
|
||||
{
|
||||
@ -547,13 +605,7 @@ static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs,
|
||||
if (ka->ka_restorer) {
|
||||
regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer;
|
||||
} else {
|
||||
/* Flush instruction space. */
|
||||
unsigned long address = ((unsigned long)&(sf->insns[0]));
|
||||
pgd_t *pgdp = pgd_offset(current->mm, address);
|
||||
pud_t *pudp = pud_offset(pgdp, address);
|
||||
pmd_t *pmdp = pmd_offset(pudp, address);
|
||||
pte_t *ptep;
|
||||
pte_t pte;
|
||||
|
||||
regs->u_regs[UREG_I7] = (unsigned long) (&(sf->insns[0]) - 2);
|
||||
|
||||
@ -562,22 +614,7 @@ static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs,
|
||||
if (err)
|
||||
goto sigsegv;
|
||||
|
||||
preempt_disable();
|
||||
ptep = pte_offset_map(pmdp, address);
|
||||
pte = *ptep;
|
||||
if (pte_present(pte)) {
|
||||
unsigned long page = (unsigned long)
|
||||
page_address(pte_page(pte));
|
||||
|
||||
wmb();
|
||||
__asm__ __volatile__("flush %0 + %1"
|
||||
: /* no outputs */
|
||||
: "r" (page),
|
||||
"r" (address & (PAGE_SIZE - 1))
|
||||
: "memory");
|
||||
}
|
||||
pte_unmap(ptep);
|
||||
preempt_enable();
|
||||
flush_signal_insns(address);
|
||||
}
|
||||
return;
|
||||
|
||||
@ -687,12 +724,7 @@ static void setup_rt_frame32(struct k_sigaction *ka, struct pt_regs *regs,
|
||||
if (ka->ka_restorer)
|
||||
regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer;
|
||||
else {
|
||||
/* Flush instruction space. */
|
||||
unsigned long address = ((unsigned long)&(sf->insns[0]));
|
||||
pgd_t *pgdp = pgd_offset(current->mm, address);
|
||||
pud_t *pudp = pud_offset(pgdp, address);
|
||||
pmd_t *pmdp = pmd_offset(pudp, address);
|
||||
pte_t *ptep;
|
||||
|
||||
regs->u_regs[UREG_I7] = (unsigned long) (&(sf->insns[0]) - 2);
|
||||
|
||||
@ -704,21 +736,7 @@ static void setup_rt_frame32(struct k_sigaction *ka, struct pt_regs *regs,
|
||||
if (err)
|
||||
goto sigsegv;
|
||||
|
||||
preempt_disable();
|
||||
ptep = pte_offset_map(pmdp, address);
|
||||
if (pte_present(*ptep)) {
|
||||
unsigned long page = (unsigned long)
|
||||
page_address(pte_page(*ptep));
|
||||
|
||||
wmb();
|
||||
__asm__ __volatile__("flush %0 + %1"
|
||||
: /* no outputs */
|
||||
: "r" (page),
|
||||
"r" (address & (PAGE_SIZE - 1))
|
||||
: "memory");
|
||||
}
|
||||
pte_unmap(ptep);
|
||||
preempt_enable();
|
||||
flush_signal_insns(address);
|
||||
}
|
||||
return;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user