2024-03-21 19:36:32 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include <linux/codetag.h>
# include <linux/idr.h>
# include <linux/kallsyms.h>
# include <linux/module.h>
# include <linux/seq_buf.h>
# include <linux/slab.h>
2024-03-21 19:36:34 +03:00
# include <linux/vmalloc.h>
2024-03-21 19:36:32 +03:00
struct codetag_type {
struct list_head link ;
unsigned int count ;
struct idr mod_idr ;
struct rw_semaphore mod_lock ; /* protects mod_idr */
struct codetag_type_desc desc ;
} ;
struct codetag_range {
struct codetag * start ;
struct codetag * stop ;
} ;
struct codetag_module {
struct module * mod ;
struct codetag_range range ;
} ;
static DEFINE_MUTEX ( codetag_lock ) ;
static LIST_HEAD ( codetag_types ) ;
void codetag_lock_module_list ( struct codetag_type * cttype , bool lock )
{
if ( lock )
down_read ( & cttype - > mod_lock ) ;
else
up_read ( & cttype - > mod_lock ) ;
}
2024-03-21 19:36:54 +03:00
bool codetag_trylock_module_list ( struct codetag_type * cttype )
{
return down_read_trylock ( & cttype - > mod_lock ) ! = 0 ;
}
2024-03-21 19:36:32 +03:00
struct codetag_iterator codetag_get_ct_iter ( struct codetag_type * cttype )
{
struct codetag_iterator iter = {
. cttype = cttype ,
. cmod = NULL ,
. mod_id = 0 ,
. ct = NULL ,
} ;
return iter ;
}
static inline struct codetag * get_first_module_ct ( struct codetag_module * cmod )
{
return cmod - > range . start < cmod - > range . stop ? cmod - > range . start : NULL ;
}
static inline
struct codetag * get_next_module_ct ( struct codetag_iterator * iter )
{
struct codetag * res = ( struct codetag * )
( ( char * ) iter - > ct + iter - > cttype - > desc . tag_size ) ;
return res < iter - > cmod - > range . stop ? res : NULL ;
}
struct codetag * codetag_next_ct ( struct codetag_iterator * iter )
{
struct codetag_type * cttype = iter - > cttype ;
struct codetag_module * cmod ;
struct codetag * ct ;
lockdep_assert_held ( & cttype - > mod_lock ) ;
if ( unlikely ( idr_is_empty ( & cttype - > mod_idr ) ) )
return NULL ;
ct = NULL ;
while ( true ) {
cmod = idr_find ( & cttype - > mod_idr , iter - > mod_id ) ;
/* If module was removed move to the next one */
if ( ! cmod )
cmod = idr_get_next_ul ( & cttype - > mod_idr ,
& iter - > mod_id ) ;
/* Exit if no more modules */
if ( ! cmod )
break ;
if ( cmod ! = iter - > cmod ) {
iter - > cmod = cmod ;
ct = get_first_module_ct ( cmod ) ;
} else
ct = get_next_module_ct ( iter ) ;
if ( ct )
break ;
iter - > mod_id + + ;
}
iter - > ct = ct ;
return ct ;
}
void codetag_to_text ( struct seq_buf * out , struct codetag * ct )
{
if ( ct - > modname )
seq_buf_printf ( out , " %s:%u [%s] func:%s " ,
ct - > filename , ct - > lineno ,
ct - > modname , ct - > function ) ;
else
seq_buf_printf ( out , " %s:%u func:%s " ,
ct - > filename , ct - > lineno , ct - > function ) ;
}
static inline size_t range_size ( const struct codetag_type * cttype ,
const struct codetag_range * range )
{
return ( ( char * ) range - > stop - ( char * ) range - > start ) /
cttype - > desc . tag_size ;
}
# ifdef CONFIG_MODULES
static void * get_symbol ( struct module * mod , const char * prefix , const char * name )
{
DECLARE_SEQ_BUF ( sb , KSYM_NAME_LEN ) ;
const char * buf ;
2024-03-21 19:36:33 +03:00
void * ret ;
2024-03-21 19:36:32 +03:00
seq_buf_printf ( & sb , " %s%s " , prefix , name ) ;
if ( seq_buf_has_overflowed ( & sb ) )
return NULL ;
buf = seq_buf_str ( & sb ) ;
2024-03-21 19:36:33 +03:00
preempt_disable ( ) ;
ret = mod ?
2024-03-21 19:36:32 +03:00
( void * ) find_kallsyms_symbol_value ( mod , buf ) :
( void * ) kallsyms_lookup_name ( buf ) ;
2024-03-21 19:36:33 +03:00
preempt_enable ( ) ;
return ret ;
2024-03-21 19:36:32 +03:00
}
static struct codetag_range get_section_range ( struct module * mod ,
const char * section )
{
return ( struct codetag_range ) {
get_symbol ( mod , " __start_ " , section ) ,
get_symbol ( mod , " __stop_ " , section ) ,
} ;
}
static int codetag_module_init ( struct codetag_type * cttype , struct module * mod )
{
struct codetag_range range ;
struct codetag_module * cmod ;
int err ;
range = get_section_range ( mod , cttype - > desc . section ) ;
if ( ! range . start | | ! range . stop ) {
pr_warn ( " Failed to load code tags of type %s from the module %s \n " ,
cttype - > desc . section ,
mod ? mod - > name : " (built-in) " ) ;
return - EINVAL ;
}
/* Ignore empty ranges */
if ( range . start = = range . stop )
return 0 ;
BUG_ON ( range . start > range . stop ) ;
cmod = kmalloc ( sizeof ( * cmod ) , GFP_KERNEL ) ;
if ( unlikely ( ! cmod ) )
return - ENOMEM ;
cmod - > mod = mod ;
cmod - > range = range ;
down_write ( & cttype - > mod_lock ) ;
err = idr_alloc ( & cttype - > mod_idr , cmod , 0 , 0 , GFP_KERNEL ) ;
2024-03-21 19:36:33 +03:00
if ( err > = 0 ) {
2024-03-21 19:36:32 +03:00
cttype - > count + = range_size ( cttype , & range ) ;
2024-03-21 19:36:33 +03:00
if ( cttype - > desc . module_load )
cttype - > desc . module_load ( cttype , cmod ) ;
}
2024-03-21 19:36:32 +03:00
up_write ( & cttype - > mod_lock ) ;
if ( err < 0 ) {
kfree ( cmod ) ;
return err ;
}
return 0 ;
}
2024-03-21 19:36:33 +03:00
void codetag_load_module ( struct module * mod )
{
struct codetag_type * cttype ;
if ( ! mod )
return ;
mutex_lock ( & codetag_lock ) ;
list_for_each_entry ( cttype , & codetag_types , link )
codetag_module_init ( cttype , mod ) ;
mutex_unlock ( & codetag_lock ) ;
}
2024-03-21 19:36:34 +03:00
bool codetag_unload_module ( struct module * mod )
2024-03-21 19:36:33 +03:00
{
struct codetag_type * cttype ;
2024-03-21 19:36:34 +03:00
bool unload_ok = true ;
2024-03-21 19:36:33 +03:00
if ( ! mod )
2024-03-21 19:36:34 +03:00
return true ;
2024-03-21 19:36:33 +03:00
mutex_lock ( & codetag_lock ) ;
list_for_each_entry ( cttype , & codetag_types , link ) {
struct codetag_module * found = NULL ;
struct codetag_module * cmod ;
unsigned long mod_id , tmp ;
down_write ( & cttype - > mod_lock ) ;
idr_for_each_entry_ul ( & cttype - > mod_idr , cmod , tmp , mod_id ) {
if ( cmod - > mod & & cmod - > mod = = mod ) {
found = cmod ;
break ;
}
}
if ( found ) {
if ( cttype - > desc . module_unload )
2024-03-21 19:36:34 +03:00
if ( ! cttype - > desc . module_unload ( cttype , cmod ) )
unload_ok = false ;
2024-03-21 19:36:33 +03:00
cttype - > count - = range_size ( cttype , & cmod - > range ) ;
idr_remove ( & cttype - > mod_idr , mod_id ) ;
kfree ( cmod ) ;
}
up_write ( & cttype - > mod_lock ) ;
}
mutex_unlock ( & codetag_lock ) ;
2024-03-21 19:36:34 +03:00
return unload_ok ;
2024-03-21 19:36:33 +03:00
}
2024-03-21 19:36:32 +03:00
# else /* CONFIG_MODULES */
static int codetag_module_init ( struct codetag_type * cttype , struct module * mod ) { return 0 ; }
# endif /* CONFIG_MODULES */
struct codetag_type *
codetag_register_type ( const struct codetag_type_desc * desc )
{
struct codetag_type * cttype ;
int err ;
BUG_ON ( desc - > tag_size < = 0 ) ;
cttype = kzalloc ( sizeof ( * cttype ) , GFP_KERNEL ) ;
if ( unlikely ( ! cttype ) )
return ERR_PTR ( - ENOMEM ) ;
cttype - > desc = * desc ;
idr_init ( & cttype - > mod_idr ) ;
init_rwsem ( & cttype - > mod_lock ) ;
err = codetag_module_init ( cttype , NULL ) ;
if ( unlikely ( err ) ) {
kfree ( cttype ) ;
return ERR_PTR ( err ) ;
}
mutex_lock ( & codetag_lock ) ;
list_add_tail ( & cttype - > link , & codetag_types ) ;
mutex_unlock ( & codetag_lock ) ;
return cttype ;
}