diff options
| author | David S. Miller <davem@davemloft.net> | 2010-09-21 02:24:52 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2010-09-21 02:24:52 -0400 |
| commit | 05c5e7698bdc54b3079a3517d86077f49ebcc788 (patch) | |
| tree | 07fe8d2d072d4d143c4edf4fea01ddcaff37840f | |
| parent | b343ae51c116dffaef07a8596661774c12212b66 (diff) | |
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>
| -rw-r--r-- | arch/sparc/kernel/signal32.c | 102 |
1 files changed, 60 insertions, 42 deletions
diff --git a/arch/sparc/kernel/signal32.c b/arch/sparc/kernel/signal32.c index ea22cd373c64..76b67c4c6aa4 100644 --- a/arch/sparc/kernel/signal32.c +++ b/arch/sparc/kernel/signal32.c | |||
| @@ -453,6 +453,64 @@ static int save_fpu_state32(struct pt_regs *regs, __siginfo_fpu_t __user *fpu) | |||
| 453 | return err; | 453 | return err; |
| 454 | } | 454 | } |
| 455 | 455 | ||
| 456 | /* The I-cache flush instruction only works in the primary ASI, which | ||
| 457 | * right now is the nucleus, aka. kernel space. | ||
| 458 | * | ||
| 459 | * Therefore we have to kick the instructions out using the kernel | ||
| 460 | * side linear mapping of the physical address backing the user | ||
| 461 | * instructions. | ||
| 462 | */ | ||
| 463 | static void flush_signal_insns(unsigned long address) | ||
| 464 | { | ||
| 465 | unsigned long pstate, paddr; | ||
| 466 | pte_t *ptep, pte; | ||
| 467 | pgd_t *pgdp; | ||
| 468 | pud_t *pudp; | ||
| 469 | pmd_t *pmdp; | ||
| 470 | |||
| 471 | /* Commit all stores of the instructions we are about to flush. */ | ||
| 472 | wmb(); | ||
| 473 | |||
| 474 | /* Disable cross-call reception. In this way even a very wide | ||
| 475 | * munmap() on another cpu can't tear down the page table | ||
| 476 | * hierarchy from underneath us, since that can't complete | ||
| 477 | * until the IPI tlb flush returns. | ||
| 478 | */ | ||
| 479 | |||
| 480 | __asm__ __volatile__("rdpr %%pstate, %0" : "=r" (pstate)); | ||
| 481 | __asm__ __volatile__("wrpr %0, %1, %%pstate" | ||
| 482 | : : "r" (pstate), "i" (PSTATE_IE)); | ||
| 483 | |||
| 484 | pgdp = pgd_offset(current->mm, address); | ||
| 485 | if (pgd_none(*pgdp)) | ||
| 486 | goto out_irqs_on; | ||
| 487 | pudp = pud_offset(pgdp, address); | ||
| 488 | if (pud_none(*pudp)) | ||
| 489 | goto out_irqs_on; | ||
| 490 | pmdp = pmd_offset(pudp, address); | ||
| 491 | if (pmd_none(*pmdp)) | ||
| 492 | goto out_irqs_on; | ||
| 493 | |||
| 494 | ptep = pte_offset_map(pmdp, address); | ||
| 495 | pte = *ptep; | ||
| 496 | if (!pte_present(pte)) | ||
| 497 | goto out_unmap; | ||
| 498 | |||
| 499 | paddr = (unsigned long) page_address(pte_page(pte)); | ||
| 500 | |||
| 501 | __asm__ __volatile__("flush %0 + %1" | ||
| 502 | : /* no outputs */ | ||
| 503 | : "r" (paddr), | ||
| 504 | "r" (address & (PAGE_SIZE - 1)) | ||
| 505 | : "memory"); | ||
| 506 | |||
| 507 | out_unmap: | ||
| 508 | pte_unmap(ptep); | ||
| 509 | out_irqs_on: | ||
| 510 | __asm__ __volatile__("wrpr %0, 0x0, %%pstate" : : "r" (pstate)); | ||
| 511 | |||
| 512 | } | ||
| 513 | |||
| 456 | static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs, | 514 | static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs, |
| 457 | int signo, sigset_t *oldset) | 515 | int signo, sigset_t *oldset) |
| 458 | { | 516 | { |
| @@ -547,13 +605,7 @@ static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs, | |||
| 547 | if (ka->ka_restorer) { | 605 | if (ka->ka_restorer) { |
| 548 | regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer; | 606 | regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer; |
| 549 | } else { | 607 | } else { |
| 550 | /* Flush instruction space. */ | ||
| 551 | unsigned long address = ((unsigned long)&(sf->insns[0])); | 608 | unsigned long address = ((unsigned long)&(sf->insns[0])); |
| 552 | pgd_t *pgdp = pgd_offset(current->mm, address); | ||
| 553 | pud_t *pudp = pud_offset(pgdp, address); | ||
| 554 | pmd_t *pmdp = pmd_offset(pudp, address); | ||
| 555 | pte_t *ptep; | ||
| 556 | pte_t pte; | ||
| 557 | 609 | ||
| 558 | regs->u_regs[UREG_I7] = (unsigned long) (&(sf->insns[0]) - 2); | 610 | regs->u_regs[UREG_I7] = (unsigned long) (&(sf->insns[0]) - 2); |
| 559 | 611 | ||
| @@ -562,22 +614,7 @@ static void setup_frame32(struct k_sigaction *ka, struct pt_regs *regs, | |||
| 562 | if (err) | 614 | if (err) |
| 563 | goto sigsegv; | 615 | goto sigsegv; |
| 564 | 616 | ||
| 565 | preempt_disable(); | 617 | flush_signal_insns(address); |
| 566 | ptep = pte_offset_map(pmdp, address); | ||
| 567 | pte = *ptep; | ||
| 568 | if (pte_present(pte)) { | ||
| 569 | unsigned long page = (unsigned long) | ||
| 570 | page_address(pte_page(pte)); | ||
| 571 | |||
| 572 | wmb(); | ||
| 573 | __asm__ __volatile__("flush %0 + %1" | ||
| 574 | : /* no outputs */ | ||
| 575 | : "r" (page), | ||
| 576 | "r" (address & (PAGE_SIZE - 1)) | ||
| 577 | : "memory"); | ||
| 578 | } | ||
| 579 | pte_unmap(ptep); | ||
| 580 | preempt_enable(); | ||
| 581 | } | 618 | } |
| 582 | return; | 619 | return; |
| 583 | 620 | ||
| @@ -687,12 +724,7 @@ static void setup_rt_frame32(struct k_sigaction *ka, struct pt_regs *regs, | |||
| 687 | if (ka->ka_restorer) | 724 | if (ka->ka_restorer) |
| 688 | regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer; | 725 | regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer; |
| 689 | else { | 726 | else { |
| 690 | /* Flush instruction space. */ | ||
| 691 | unsigned long address = ((unsigned long)&(sf->insns[0])); | 727 | unsigned long address = ((unsigned long)&(sf->insns[0])); |
| 692 | pgd_t *pgdp = pgd_offset(current->mm, address); | ||
| 693 | pud_t *pudp = pud_offset(pgdp, address); | ||
| 694 | pmd_t *pmdp = pmd_offset(pudp, address); | ||
| 695 | pte_t *ptep; | ||
| 696 | 728 | ||
| 697 | regs->u_regs[UREG_I7] = (unsigned long) (&(sf->insns[0]) - 2); | 729 | regs->u_regs[UREG_I7] = (unsigned long) (&(sf->insns[0]) - 2); |
| 698 | 730 | ||
| @@ -704,21 +736,7 @@ static void setup_rt_frame32(struct k_sigaction *ka, struct pt_regs *regs, | |||
| 704 | if (err) | 736 | if (err) |
| 705 | goto sigsegv; | 737 | goto sigsegv; |
| 706 | 738 | ||
| 707 | preempt_disable(); | 739 | flush_signal_insns(address); |
| 708 | ptep = pte_offset_map(pmdp, address); | ||
| 709 | if (pte_present(*ptep)) { | ||
| 710 | unsigned long page = (unsigned long) | ||
| 711 | page_address(pte_page(*ptep)); | ||
| 712 | |||
| 713 | wmb(); | ||
| 714 | __asm__ __volatile__("flush %0 + %1" | ||
| 715 | : /* no outputs */ | ||
| 716 | : "r" (page), | ||
| 717 | "r" (address & (PAGE_SIZE - 1)) | ||
| 718 | : "memory"); | ||
| 719 | } | ||
| 720 | pte_unmap(ptep); | ||
| 721 | preempt_enable(); | ||
| 722 | } | 740 | } |
| 723 | return; | 741 | return; |
| 724 | 742 | ||
