2018-01-12 20:56:03 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* fail_function . c : Function - based error injection
*/
# include <linux/error-injection.h>
# include <linux/debugfs.h>
# include <linux/fault-inject.h>
# include <linux/kallsyms.h>
# include <linux/kprobes.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
static int fei_kprobe_handler ( struct kprobe * kp , struct pt_regs * regs ) ;
2018-03-12 13:00:49 +03:00
static void fei_post_handler ( struct kprobe * kp , struct pt_regs * regs ,
unsigned long flags )
{
/*
* A dummy post handler is required to prohibit optimizing , because
* jump optimization does not support execution path overriding .
*/
}
2018-01-12 20:56:03 +03:00
struct fei_attr {
struct list_head list ;
struct kprobe kp ;
unsigned long retval ;
} ;
static DEFINE_MUTEX ( fei_lock ) ;
static LIST_HEAD ( fei_attr_list ) ;
static DECLARE_FAULT_ATTR ( fei_fault_attr ) ;
static struct dentry * fei_debugfs_dir ;
static unsigned long adjust_error_retval ( unsigned long addr , unsigned long retv )
{
switch ( get_injectable_error_type ( addr ) ) {
case EI_ETYPE_NULL :
2020-12-16 07:47:10 +03:00
return 0 ;
2018-01-12 20:56:03 +03:00
case EI_ETYPE_ERRNO :
if ( retv < ( unsigned long ) - MAX_ERRNO )
return ( unsigned long ) - EINVAL ;
break ;
case EI_ETYPE_ERRNO_NULL :
if ( retv ! = 0 & & retv < ( unsigned long ) - MAX_ERRNO )
return ( unsigned long ) - EINVAL ;
break ;
2020-12-16 07:47:10 +03:00
case EI_ETYPE_TRUE :
return 1 ;
2018-01-12 20:56:03 +03:00
}
return retv ;
}
static struct fei_attr * fei_attr_new ( const char * sym , unsigned long addr )
{
struct fei_attr * attr ;
attr = kzalloc ( sizeof ( * attr ) , GFP_KERNEL ) ;
if ( attr ) {
attr - > kp . symbol_name = kstrdup ( sym , GFP_KERNEL ) ;
if ( ! attr - > kp . symbol_name ) {
kfree ( attr ) ;
return NULL ;
}
attr - > kp . pre_handler = fei_kprobe_handler ;
2018-03-12 13:00:49 +03:00
attr - > kp . post_handler = fei_post_handler ;
2018-01-12 20:56:03 +03:00
attr - > retval = adjust_error_retval ( addr , 0 ) ;
INIT_LIST_HEAD ( & attr - > list ) ;
}
return attr ;
}
static void fei_attr_free ( struct fei_attr * attr )
{
if ( attr ) {
kfree ( attr - > kp . symbol_name ) ;
kfree ( attr ) ;
}
}
static struct fei_attr * fei_attr_lookup ( const char * sym )
{
struct fei_attr * attr ;
list_for_each_entry ( attr , & fei_attr_list , list ) {
if ( ! strcmp ( attr - > kp . symbol_name , sym ) )
return attr ;
}
return NULL ;
}
static bool fei_attr_is_valid ( struct fei_attr * _attr )
{
struct fei_attr * attr ;
list_for_each_entry ( attr , & fei_attr_list , list ) {
if ( attr = = _attr )
return true ;
}
return false ;
}
static int fei_retval_set ( void * data , u64 val )
{
struct fei_attr * attr = data ;
unsigned long retv = ( unsigned long ) val ;
int err = 0 ;
mutex_lock ( & fei_lock ) ;
/*
* Since this operation can be done after retval file is removed ,
* It is safer to check the attr is still valid before accessing
* its member .
*/
if ( ! fei_attr_is_valid ( attr ) ) {
err = - ENOENT ;
goto out ;
}
if ( attr - > kp . addr ) {
if ( adjust_error_retval ( ( unsigned long ) attr - > kp . addr ,
val ) ! = retv )
err = - EINVAL ;
}
if ( ! err )
attr - > retval = val ;
out :
mutex_unlock ( & fei_lock ) ;
return err ;
}
static int fei_retval_get ( void * data , u64 * val )
{
struct fei_attr * attr = data ;
int err = 0 ;
mutex_lock ( & fei_lock ) ;
/* Here we also validate @attr to ensure it still exists. */
if ( ! fei_attr_is_valid ( attr ) )
err = - ENOENT ;
else
* val = attr - > retval ;
mutex_unlock ( & fei_lock ) ;
return err ;
}
DEFINE_DEBUGFS_ATTRIBUTE ( fei_retval_ops , fei_retval_get , fei_retval_set ,
" %llx \n " ) ;
2019-01-22 18:21:44 +03:00
static void fei_debugfs_add_attr ( struct fei_attr * attr )
2018-01-12 20:56:03 +03:00
{
struct dentry * dir ;
dir = debugfs_create_dir ( attr - > kp . symbol_name , fei_debugfs_dir ) ;
2019-01-22 18:21:44 +03:00
debugfs_create_file ( " retval " , 0600 , dir , attr , & fei_retval_ops ) ;
2018-01-12 20:56:03 +03:00
}
static void fei_debugfs_remove_attr ( struct fei_attr * attr )
{
struct dentry * dir ;
dir = debugfs_lookup ( attr - > kp . symbol_name , fei_debugfs_dir ) ;
2018-10-31 01:04:51 +03:00
debugfs_remove_recursive ( dir ) ;
2018-01-12 20:56:03 +03:00
}
static int fei_kprobe_handler ( struct kprobe * kp , struct pt_regs * regs )
{
struct fei_attr * attr = container_of ( kp , struct fei_attr , kp ) ;
if ( should_fail ( & fei_fault_attr , 1 ) ) {
regs_set_return_value ( regs , attr - > retval ) ;
override_function_with_return ( regs ) ;
return 1 ;
}
return 0 ;
}
NOKPROBE_SYMBOL ( fei_kprobe_handler )
static void * fei_seq_start ( struct seq_file * m , loff_t * pos )
{
mutex_lock ( & fei_lock ) ;
return seq_list_start ( & fei_attr_list , * pos ) ;
}
static void fei_seq_stop ( struct seq_file * m , void * v )
{
mutex_unlock ( & fei_lock ) ;
}
static void * fei_seq_next ( struct seq_file * m , void * v , loff_t * pos )
{
return seq_list_next ( v , & fei_attr_list , pos ) ;
}
static int fei_seq_show ( struct seq_file * m , void * v )
{
struct fei_attr * attr = list_entry ( v , struct fei_attr , list ) ;
2019-03-25 22:32:28 +03:00
seq_printf ( m , " %ps \n " , attr - > kp . addr ) ;
2018-01-12 20:56:03 +03:00
return 0 ;
}
static const struct seq_operations fei_seq_ops = {
. start = fei_seq_start ,
. next = fei_seq_next ,
. stop = fei_seq_stop ,
. show = fei_seq_show ,
} ;
static int fei_open ( struct inode * inode , struct file * file )
{
return seq_open ( file , & fei_seq_ops ) ;
}
static void fei_attr_remove ( struct fei_attr * attr )
{
fei_debugfs_remove_attr ( attr ) ;
unregister_kprobe ( & attr - > kp ) ;
list_del ( & attr - > list ) ;
fei_attr_free ( attr ) ;
}
static void fei_attr_remove_all ( void )
{
struct fei_attr * attr , * n ;
list_for_each_entry_safe ( attr , n , & fei_attr_list , list ) {
fei_attr_remove ( attr ) ;
}
}
static ssize_t fei_write ( struct file * file , const char __user * buffer ,
size_t count , loff_t * ppos )
{
struct fei_attr * attr ;
unsigned long addr ;
char * buf , * sym ;
int ret ;
/* cut off if it is too long */
if ( count > KSYM_NAME_LEN )
count = KSYM_NAME_LEN ;
2022-08-26 10:33:35 +03:00
buf = memdup_user_nul ( buffer , count ) ;
if ( IS_ERR ( buf ) )
return PTR_ERR ( buf ) ;
2018-01-12 20:56:03 +03:00
sym = strstrip ( buf ) ;
mutex_lock ( & fei_lock ) ;
/* Writing just spaces will remove all injection points */
if ( sym [ 0 ] = = ' \0 ' ) {
fei_attr_remove_all ( ) ;
ret = count ;
goto out ;
}
/* Writing !function will remove one injection point */
if ( sym [ 0 ] = = ' ! ' ) {
attr = fei_attr_lookup ( sym + 1 ) ;
if ( ! attr ) {
ret = - ENOENT ;
goto out ;
}
fei_attr_remove ( attr ) ;
ret = count ;
goto out ;
}
addr = kallsyms_lookup_name ( sym ) ;
if ( ! addr ) {
ret = - EINVAL ;
goto out ;
}
if ( ! within_error_injection_list ( addr ) ) {
ret = - ERANGE ;
goto out ;
}
if ( fei_attr_lookup ( sym ) ) {
ret = - EBUSY ;
goto out ;
}
attr = fei_attr_new ( sym , addr ) ;
if ( ! attr ) {
ret = - ENOMEM ;
goto out ;
}
ret = register_kprobe ( & attr - > kp ) ;
2022-08-26 10:33:36 +03:00
if ( ret ) {
2022-08-26 10:33:37 +03:00
fei_attr_free ( attr ) ;
2022-08-26 10:33:36 +03:00
goto out ;
2018-01-12 20:56:03 +03:00
}
2022-08-26 10:33:36 +03:00
fei_debugfs_add_attr ( attr ) ;
list_add_tail ( & attr - > list , & fei_attr_list ) ;
ret = count ;
2018-01-12 20:56:03 +03:00
out :
mutex_unlock ( & fei_lock ) ;
2020-11-18 16:49:31 +03:00
kfree ( buf ) ;
2018-01-12 20:56:03 +03:00
return ret ;
}
static const struct file_operations fei_ops = {
. open = fei_open ,
. read = seq_read ,
. write = fei_write ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
static int __init fei_debugfs_init ( void )
{
struct dentry * dir ;
dir = fault_create_debugfs_attr ( " fail_function " , NULL ,
& fei_fault_attr ) ;
if ( IS_ERR ( dir ) )
return PTR_ERR ( dir ) ;
/* injectable attribute is just a symlink of error_inject/list */
2019-01-22 18:21:44 +03:00
debugfs_create_symlink ( " injectable " , dir , " ../error_injection/list " ) ;
2018-01-12 20:56:03 +03:00
2019-01-22 18:21:44 +03:00
debugfs_create_file ( " inject " , 0600 , dir , NULL , & fei_ops ) ;
2018-01-12 20:56:03 +03:00
fei_debugfs_dir = dir ;
return 0 ;
}
late_initcall ( fei_debugfs_init ) ;