Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 05c5e769 authored by David S. Miller's avatar David S. Miller
Browse files

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: default avatarAl Viro <viro@ZenIV.linux.org.uk>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent b343ae51
Loading
Loading
Loading
Loading
+60 −42
Original line number Diff line number Diff line
@@ -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;