2020-04-27 19:00:07 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Shadow Call Stack support .
*
* Copyright ( C ) 2019 Google LLC
*/
# include <linux/kasan.h>
2020-04-27 19:00:08 +03:00
# include <linux/mm.h>
2020-04-27 19:00:07 +03:00
# include <linux/scs.h>
# include <linux/slab.h>
2020-04-27 19:00:08 +03:00
# include <linux/vmstat.h>
2020-04-27 19:00:07 +03:00
static struct kmem_cache * scs_cache ;
2020-05-15 16:43:11 +03:00
static void __scs_account ( void * s , int account )
{
struct page * scs_page = virt_to_page ( s ) ;
mod_zone_page_state ( page_zone ( scs_page ) , NR_KERNEL_SCS_KB ,
account * ( SCS_SIZE / SZ_1K ) ) ;
}
2020-04-27 19:00:07 +03:00
static void * scs_alloc ( int node )
{
2020-05-15 16:43:11 +03:00
void * s = kmem_cache_alloc_node ( scs_cache , GFP_SCS , node ) ;
if ( ! s )
return NULL ;
2020-04-27 19:00:07 +03:00
2020-05-15 16:43:11 +03:00
* __scs_magic ( s ) = SCS_END_MAGIC ;
/*
* Poison the allocation to catch unintentional accesses to
* the shadow stack when KASAN is enabled .
*/
kasan_poison_object_data ( scs_cache , s ) ;
__scs_account ( s , 1 ) ;
2020-04-27 19:00:07 +03:00
return s ;
}
static void scs_free ( void * s )
{
2020-05-15 16:43:11 +03:00
__scs_account ( s , - 1 ) ;
2020-04-27 19:00:07 +03:00
kasan_unpoison_object_data ( scs_cache , s ) ;
kmem_cache_free ( scs_cache , s ) ;
}
void __init scs_init ( void )
{
scs_cache = kmem_cache_create ( " scs_cache " , SCS_SIZE , 0 , 0 , NULL ) ;
}
int scs_prepare ( struct task_struct * tsk , int node )
{
void * s = scs_alloc ( node ) ;
if ( ! s )
return - ENOMEM ;
2020-05-15 16:11:05 +03:00
task_scs ( tsk ) = task_scs_sp ( tsk ) = s ;
2020-04-27 19:00:07 +03:00
return 0 ;
}
2020-04-27 19:00:09 +03:00
static void scs_check_usage ( struct task_struct * tsk )
{
static unsigned long highest ;
unsigned long * p , prev , curr = highest , used = 0 ;
if ( ! IS_ENABLED ( CONFIG_DEBUG_STACK_USAGE ) )
return ;
for ( p = task_scs ( tsk ) ; p < __scs_magic ( tsk ) ; + + p ) {
if ( ! READ_ONCE_NOCHECK ( * p ) )
break ;
2020-06-03 14:16:37 +03:00
used + = sizeof ( * p ) ;
2020-04-27 19:00:09 +03:00
}
while ( used > curr ) {
prev = cmpxchg_relaxed ( & highest , curr , used ) ;
if ( prev = = curr ) {
pr_info ( " %s (%d): highest shadow stack usage: %lu bytes \n " ,
tsk - > comm , task_pid_nr ( tsk ) , used ) ;
break ;
}
curr = prev ;
}
}
2020-04-27 19:00:07 +03:00
void scs_release ( struct task_struct * tsk )
{
void * s = task_scs ( tsk ) ;
if ( ! s )
return ;
2020-05-15 16:56:05 +03:00
WARN ( task_scs_end_corrupted ( tsk ) ,
" corrupted shadow stack detected when freeing task \n " ) ;
2020-04-27 19:00:09 +03:00
scs_check_usage ( tsk ) ;
2020-04-27 19:00:07 +03:00
scs_free ( s ) ;
}