Skip to content

Commit 9f7853d

Browse files
committed
powerpc/mm: Fix set_memory_*() against concurrent accesses
Laurent reported that STRICT_MODULE_RWX was causing intermittent crashes on one of his systems: kernel tried to execute exec-protected page (c008000004073278) - exploit attempt? (uid: 0) BUG: Unable to handle kernel instruction fetch Faulting instruction address: 0xc008000004073278 Oops: Kernel access of bad area, sig: 11 [#1] LE PAGE_SIZE=64K MMU=Radix SMP NR_CPUS=2048 NUMA pSeries Modules linked in: drm virtio_console fuse drm_panel_orientation_quirks ... CPU: 3 PID: 44 Comm: kworker/3:1 Not tainted 5.14.0-rc4+ #12 Workqueue: events control_work_handler [virtio_console] NIP: c008000004073278 LR: c008000004073278 CTR: c0000000001e9de0 REGS: c00000002e4ef7e0 TRAP: 0400 Not tainted (5.14.0-rc4+) MSR: 800000004280b033 <SF,VEC,VSX,EE,FP,ME,IR,DR,RI,LE> CR: 24002822 XER: 200400cf ... NIP fill_queue+0xf0/0x210 [virtio_console] LR fill_queue+0xf0/0x210 [virtio_console] Call Trace: fill_queue+0xb4/0x210 [virtio_console] (unreliable) add_port+0x1a8/0x470 [virtio_console] control_work_handler+0xbc/0x1e8 [virtio_console] process_one_work+0x290/0x590 worker_thread+0x88/0x620 kthread+0x194/0x1a0 ret_from_kernel_thread+0x5c/0x64 Jordan, Fabiano & Murilo were able to reproduce and identify that the problem is caused by the call to module_enable_ro() in do_init_module(), which happens after the module's init function has already been called. Our current implementation of change_page_attr() is not safe against concurrent accesses, because it invalidates the PTE before flushing the TLB and then installing the new PTE. That leaves a window in time where there is no valid PTE for the page, if another CPU tries to access the page at that time we see something like the fault above. We can't simply switch to set_pte_at()/flush TLB, because our hash MMU code doesn't handle a set_pte_at() of a valid PTE. See [1]. But we do have pte_update(), which replaces the old PTE with the new, meaning there's no window where the PTE is invalid. And the hash MMU version hash__pte_update() deals with synchronising the hash page table correctly. [1]: https://lore.kernel.org/linuxppc-dev/[email protected]/ Fixes: 1f9ad21 ("powerpc/mm: Implement set_memory() routines") Reported-by: Laurent Vivier <[email protected]> Reviewed-by: Christophe Leroy <[email protected]> Reviewed-by: Murilo Opsfelder Araújo <[email protected]> Tested-by: Laurent Vivier <[email protected]> Signed-off-by: Fabiano Rosas <[email protected]> Signed-off-by: Michael Ellerman <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent ef486bf commit 9f7853d

File tree

1 file changed

+10
-13
lines changed

1 file changed

+10
-13
lines changed

arch/powerpc/mm/pageattr.c

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,12 @@
1818
/*
1919
* Updates the attributes of a page in three steps:
2020
*
21-
* 1. invalidate the page table entry
22-
* 2. flush the TLB
23-
* 3. install the new entry with the updated attributes
24-
*
25-
* Invalidating the pte means there are situations where this will not work
26-
* when in theory it should.
27-
* For example:
28-
* - removing write from page whilst it is being executed
29-
* - setting a page read-only whilst it is being read by another CPU
21+
* 1. take the page_table_lock
22+
* 2. install the new entry with the updated attributes
23+
* 3. flush the TLB
3024
*
25+
* This sequence is safe against concurrent updates, and also allows updating the
26+
* attributes of a page currently being executed or accessed.
3127
*/
3228
static int change_page_attr(pte_t *ptep, unsigned long addr, void *data)
3329
{
@@ -36,9 +32,7 @@ static int change_page_attr(pte_t *ptep, unsigned long addr, void *data)
3632

3733
spin_lock(&init_mm.page_table_lock);
3834

39-
/* invalidate the PTE so it's safe to modify */
40-
pte = ptep_get_and_clear(&init_mm, addr, ptep);
41-
flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
35+
pte = ptep_get(ptep);
4236

4337
/* modify the PTE bits as desired, then apply */
4438
switch (action) {
@@ -59,11 +53,14 @@ static int change_page_attr(pte_t *ptep, unsigned long addr, void *data)
5953
break;
6054
}
6155

62-
set_pte_at(&init_mm, addr, ptep, pte);
56+
pte_update(&init_mm, addr, ptep, ~0UL, pte_val(pte), 0);
6357

6458
/* See ptesync comment in radix__set_pte_at() */
6559
if (radix_enabled())
6660
asm volatile("ptesync": : :"memory");
61+
62+
flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
63+
6764
spin_unlock(&init_mm.page_table_lock);
6865

6966
return 0;

0 commit comments

Comments
 (0)