2019-04-04 22:14:09 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* functions to patch RO kernel text during runtime
*
* Copyright ( c ) 2019 Sven Schnelle < svens @ stackframe . org >
*/
# include <linux/kernel.h>
# include <linux/spinlock.h>
# include <linux/kprobes.h>
# include <linux/mm.h>
# include <linux/stop_machine.h>
# include <asm/cacheflush.h>
# include <asm/fixmap.h>
# include <asm/patch.h>
struct patch {
void * addr ;
2019-06-05 23:32:17 +03:00
u32 * insn ;
unsigned int len ;
2019-04-04 22:14:09 +03:00
} ;
2019-06-05 23:32:17 +03:00
static DEFINE_RAW_SPINLOCK ( patch_lock ) ;
2019-06-05 23:32:18 +03:00
static void __kprobes * patch_map ( void * addr , int fixmap , unsigned long * flags ,
int * need_unmap )
{
2019-04-04 22:14:09 +03:00
unsigned long uintaddr = ( uintptr_t ) addr ;
bool module = ! core_kernel_text ( uintaddr ) ;
struct page * page ;
2019-06-05 23:32:17 +03:00
* need_unmap = 0 ;
2019-04-04 22:14:09 +03:00
if ( module & & IS_ENABLED ( CONFIG_STRICT_MODULE_RWX ) )
page = vmalloc_to_page ( addr ) ;
else if ( ! module & & IS_ENABLED ( CONFIG_STRICT_KERNEL_RWX ) )
page = virt_to_page ( addr ) ;
else
return addr ;
2019-06-05 23:32:17 +03:00
* need_unmap = 1 ;
2019-04-04 22:14:09 +03:00
set_fixmap ( fixmap , page_to_phys ( page ) ) ;
2019-06-05 23:32:18 +03:00
if ( flags )
raw_spin_lock_irqsave ( & patch_lock , * flags ) ;
else
__acquire ( & patch_lock ) ;
2019-04-04 22:14:09 +03:00
return ( void * ) ( __fix_to_virt ( fixmap ) + ( uintaddr & ~ PAGE_MASK ) ) ;
}
2019-06-05 23:32:18 +03:00
static void __kprobes patch_unmap ( int fixmap , unsigned long * flags )
2019-04-04 22:14:09 +03:00
{
clear_fixmap ( fixmap ) ;
2019-06-05 23:32:18 +03:00
if ( flags )
raw_spin_unlock_irqrestore ( & patch_lock , * flags ) ;
else
__release ( & patch_lock ) ;
2019-04-04 22:14:09 +03:00
}
2019-06-05 23:32:17 +03:00
void __kprobes __patch_text_multiple ( void * addr , u32 * insn , unsigned int len )
{
unsigned long start = ( unsigned long ) addr ;
unsigned long end = ( unsigned long ) addr + len ;
2019-06-05 23:32:18 +03:00
unsigned long flags ;
2019-06-05 23:32:17 +03:00
u32 * p , * fixmap ;
int mapped ;
/* Make sure we don't have any aliases in cache */
flush_kernel_vmap_range ( addr , len ) ;
flush_icache_range ( start , end ) ;
2019-06-05 23:32:18 +03:00
p = fixmap = patch_map ( addr , FIX_TEXT_POKE0 , & flags , & mapped ) ;
2019-06-05 23:32:17 +03:00
while ( len > = 4 ) {
* p + + = * insn + + ;
addr + = sizeof ( u32 ) ;
len - = sizeof ( u32 ) ;
if ( len & & offset_in_page ( addr ) = = 0 ) {
/*
* We ' re crossing a page boundary , so
* need to remap
*/
flush_kernel_vmap_range ( ( void * ) fixmap ,
( p - fixmap ) * sizeof ( * p ) ) ;
if ( mapped )
2019-06-05 23:32:18 +03:00
patch_unmap ( FIX_TEXT_POKE0 , & flags ) ;
p = fixmap = patch_map ( addr , FIX_TEXT_POKE0 , & flags ,
& mapped ) ;
2019-06-05 23:32:17 +03:00
}
}
flush_kernel_vmap_range ( ( void * ) fixmap , ( p - fixmap ) * sizeof ( * p ) ) ;
if ( mapped )
2019-06-05 23:32:18 +03:00
patch_unmap ( FIX_TEXT_POKE0 , & flags ) ;
2019-06-05 23:32:17 +03:00
flush_icache_range ( start , end ) ;
}
void __kprobes __patch_text ( void * addr , u32 insn )
2019-04-04 22:14:09 +03:00
{
2019-06-05 23:32:17 +03:00
__patch_text_multiple ( addr , & insn , sizeof ( insn ) ) ;
2019-04-04 22:14:09 +03:00
}
static int __kprobes patch_text_stop_machine ( void * data )
{
struct patch * patch = data ;
2019-06-05 23:32:17 +03:00
__patch_text_multiple ( patch - > addr , patch - > insn , patch - > len ) ;
2019-04-04 22:14:09 +03:00
return 0 ;
}
void __kprobes patch_text ( void * addr , unsigned int insn )
{
2019-06-05 23:32:17 +03:00
struct patch patch = {
. addr = addr ,
. insn = & insn ,
. len = sizeof ( insn ) ,
} ;
stop_machine_cpuslocked ( patch_text_stop_machine , & patch , NULL ) ;
}
void __kprobes patch_text_multiple ( void * addr , u32 * insn , unsigned int len )
{
2019-04-04 22:14:09 +03:00
struct patch patch = {
. addr = addr ,
. insn = insn ,
2019-06-05 23:32:17 +03:00
. len = len
2019-04-04 22:14:09 +03:00
} ;
stop_machine_cpuslocked ( patch_text_stop_machine , & patch , NULL ) ;
}