355 lines
8.8 KiB
C
355 lines
8.8 KiB
C
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
|
|
/*
|
|
*
|
|
* (C) COPYRIGHT 2023-2024 ARM Limited. All rights reserved.
|
|
*
|
|
* This program is free software and is provided to you under the terms of the
|
|
* GNU General Public License version 2 as published by the Free Software
|
|
* Foundation, and any use by you of this program is subject to the terms
|
|
* of such GNU license.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you can access it online at
|
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
|
*
|
|
*/
|
|
|
|
/* Kernel probe functionality.
|
|
*/
|
|
|
|
#ifdef CONFIG_KPROBES
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/version.h>
|
|
#include <linux/delay.h>
|
|
#include <kutf/kutf_kprobe.h>
|
|
|
|
#define KUTF_KP_REG_MIN_ARGS 3
|
|
#define KUTF_KP_UNREG_MIN_ARGS 2
|
|
#define KUTF_KP_FUNC_NAME_ARG 0
|
|
#define KUTF_KP_FUNC_ENTRY_EXIT_ARG 1
|
|
#define KUTF_KP_FUNC_HANDLER_ARG 2
|
|
|
|
#define KUTF_KP_WRITE_BUFSIZE 4096
|
|
|
|
/* Stores address to kernel function 'kallsyms_lookup_name'
|
|
* as 'kallsyms_lookup_name' is no longer exported from 5.7 kernel.
|
|
*/
|
|
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
|
|
kallsyms_lookup_name_t kutf_ksym_lookup_name;
|
|
|
|
static ssize_t kutf_kp_reg_debugfs_write(struct file *file, const char __user *user_buf,
|
|
size_t count, loff_t *ppos);
|
|
|
|
static ssize_t kutf_kp_unreg_debugfs_write(struct file *file, const char __user *user_buf,
|
|
size_t count, loff_t *ppos);
|
|
|
|
static LIST_HEAD(kp_list);
|
|
static DEFINE_MUTEX(kp_list_lock);
|
|
|
|
/**
|
|
* struct kutf_kp_data - Structure which holds data per kprobe instance
|
|
* @kretp: reference to kernel ret probe
|
|
* @entry: true if this probe is for function entry.Otherwise it is exit.
|
|
* @argc: Number of arguments to be passed to probe handler
|
|
* @argv: arguments passed to probe handler
|
|
* @kp_handler: Actual handler which is called when probe is triggered
|
|
* @list: node for adding to kp_list
|
|
*/
|
|
struct kutf_kp_data {
|
|
struct kretprobe kretp;
|
|
bool entry;
|
|
int argc;
|
|
char **argv;
|
|
kutf_kp_handler kp_handler;
|
|
struct list_head list;
|
|
};
|
|
|
|
const struct file_operations kutf_kp_reg_debugfs_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
.write = kutf_kp_reg_debugfs_write,
|
|
};
|
|
|
|
const struct file_operations kutf_kp_unreg_debugfs_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
.write = kutf_kp_unreg_debugfs_write,
|
|
};
|
|
|
|
struct kprobe kutf_kallsym_kp = { .symbol_name = "kallsyms_lookup_name" };
|
|
|
|
void kutf_kp_delay_handler(int argc, char **argv)
|
|
{
|
|
long delay;
|
|
|
|
if ((!argv) || (!argv[0]))
|
|
return;
|
|
|
|
if (kstrtol(argv[0], 0, &delay))
|
|
return;
|
|
|
|
mdelay(delay);
|
|
}
|
|
|
|
void kutf_kp_sample_kernel_function(void)
|
|
{
|
|
pr_debug("%s called\n", __func__);
|
|
}
|
|
EXPORT_SYMBOL(kutf_kp_sample_kernel_function);
|
|
|
|
void kutf_kp_sample_handler(int argc, char **argv)
|
|
{
|
|
int i = 0;
|
|
|
|
for (; i < argc; i++)
|
|
pr_info("%s %s\n", __func__, argv[i]);
|
|
}
|
|
EXPORT_SYMBOL(kutf_kp_sample_handler);
|
|
|
|
static int kutf_call_kp_handler(struct kretprobe *p)
|
|
{
|
|
struct kutf_kp_data *kp_p;
|
|
kutf_kp_handler kp_handler;
|
|
|
|
kp_p = (struct kutf_kp_data *)p;
|
|
kp_handler = kp_p->kp_handler;
|
|
|
|
if (kp_handler) {
|
|
/* Arguments to registered handler starts after
|
|
* KUTF_KP_REG_MIN_ARGS.
|
|
*/
|
|
(*kp_handler)((kp_p->argc) - KUTF_KP_REG_MIN_ARGS,
|
|
&(kp_p->argv[KUTF_KP_REG_MIN_ARGS]));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int kutf_kretp_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
|
{
|
|
#if (KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE)
|
|
return kutf_call_kp_handler(ri->rph->rp);
|
|
#else
|
|
return kutf_call_kp_handler(ri->rp);
|
|
#endif
|
|
}
|
|
|
|
static kutf_kp_handler kutf_get_kp_handler(char **argv)
|
|
{
|
|
if ((NULL == argv) || (NULL == kutf_ksym_lookup_name))
|
|
return NULL;
|
|
|
|
return (kutf_kp_handler)((*kutf_ksym_lookup_name)(argv[KUTF_KP_FUNC_HANDLER_ARG]));
|
|
}
|
|
|
|
static ssize_t kutf_kp_reg_debugfs_write(struct file *file, const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int argc = 0;
|
|
int ret = count;
|
|
char **argv;
|
|
char *kbuf;
|
|
char *func_name;
|
|
struct kutf_kp_data *kp_p;
|
|
struct kutf_kp_data *kp_iter;
|
|
|
|
if (count >= KUTF_KP_WRITE_BUFSIZE)
|
|
return -EINVAL;
|
|
|
|
kbuf = memdup_user_nul(user_buf, count);
|
|
if (IS_ERR(kbuf))
|
|
return -ENOMEM;
|
|
|
|
argv = argv_split(GFP_KERNEL, kbuf, &argc);
|
|
if (!argv) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
if (argc < KUTF_KP_REG_MIN_ARGS) {
|
|
pr_debug("Insufficient args in reg kprobe:%d\n", argc);
|
|
ret = -EINVAL;
|
|
goto argv_out;
|
|
}
|
|
|
|
kp_p = kzalloc(sizeof(struct kutf_kp_data), GFP_KERNEL);
|
|
if (!kp_p)
|
|
goto argv_out;
|
|
|
|
if (!(strcmp(argv[KUTF_KP_FUNC_ENTRY_EXIT_ARG], "entry"))) {
|
|
kp_p->kretp.entry_handler = kutf_kretp_handler;
|
|
kp_p->entry = 1;
|
|
} else if (!(strcmp(argv[KUTF_KP_FUNC_ENTRY_EXIT_ARG], "exit"))) {
|
|
kp_p->kretp.handler = kutf_kretp_handler;
|
|
} else {
|
|
pr_err("Invalid arg:%s passed in reg kprobe\n", argv[KUTF_KP_FUNC_ENTRY_EXIT_ARG]);
|
|
ret = -EINVAL;
|
|
kfree(kp_p);
|
|
goto argv_out;
|
|
}
|
|
|
|
func_name = argv[KUTF_KP_FUNC_NAME_ARG];
|
|
|
|
mutex_lock(&kp_list_lock);
|
|
list_for_each_entry(kp_iter, &kp_list, list) {
|
|
if ((kp_iter->entry == kp_p->entry) &&
|
|
(!(strcmp(kp_iter->kretp.kp.symbol_name, func_name)))) {
|
|
ret = -EEXIST;
|
|
kfree(kp_p);
|
|
mutex_unlock(&kp_list_lock);
|
|
goto argv_out;
|
|
}
|
|
}
|
|
|
|
kp_p->kretp.kp.symbol_name = func_name;
|
|
kp_p->kp_handler = kutf_get_kp_handler(argv);
|
|
if (!(kp_p->kp_handler)) {
|
|
pr_debug("cannot find addr for handler:%s\n", argv[KUTF_KP_FUNC_HANDLER_ARG]);
|
|
ret = -EINVAL;
|
|
kfree(kp_p);
|
|
mutex_unlock(&kp_list_lock);
|
|
goto argv_out;
|
|
}
|
|
kp_p->argc = argc;
|
|
kp_p->argv = argv;
|
|
ret = register_kretprobe(&kp_p->kretp);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
kfree(kp_p);
|
|
mutex_unlock(&kp_list_lock);
|
|
goto argv_out;
|
|
}
|
|
INIT_LIST_HEAD(&kp_p->list);
|
|
list_add(&kp_p->list, &kp_list);
|
|
|
|
mutex_unlock(&kp_list_lock);
|
|
|
|
ret = count;
|
|
goto out;
|
|
|
|
argv_out:
|
|
argv_free(argv);
|
|
|
|
out:
|
|
kfree(kbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t kutf_kp_unreg_debugfs_write(struct file *file, const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int argc = 0;
|
|
int ret = -EINVAL;
|
|
char **argv = NULL;
|
|
char *kbuf;
|
|
char *func_name;
|
|
struct kutf_kp_data *kp_iter;
|
|
bool entry;
|
|
|
|
if (count >= KUTF_KP_WRITE_BUFSIZE)
|
|
return -EINVAL;
|
|
|
|
kbuf = memdup_user_nul(user_buf, count);
|
|
if (IS_ERR(kbuf))
|
|
return -ENOMEM;
|
|
|
|
argv = argv_split(GFP_KERNEL, kbuf, &argc);
|
|
if (!argv) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
if (argc < KUTF_KP_UNREG_MIN_ARGS) {
|
|
pr_debug("Insufficient args in unreg kprobe:%d\n", argc);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (!(strcmp(argv[KUTF_KP_FUNC_ENTRY_EXIT_ARG], "entry")))
|
|
entry = 1;
|
|
else if (!(strcmp(argv[KUTF_KP_FUNC_ENTRY_EXIT_ARG], "exit")))
|
|
entry = 0;
|
|
else {
|
|
pr_err("Invalid arg:%s passed in unreg kprobe\n",
|
|
argv[KUTF_KP_FUNC_ENTRY_EXIT_ARG]);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
func_name = argv[KUTF_KP_FUNC_NAME_ARG];
|
|
|
|
mutex_lock(&kp_list_lock);
|
|
list_for_each_entry(kp_iter, &kp_list, list) {
|
|
if ((kp_iter->entry == entry) &&
|
|
(!(strcmp(func_name, kp_iter->kretp.kp.symbol_name)))) {
|
|
unregister_kretprobe(&kp_iter->kretp);
|
|
argv_free(kp_iter->argv);
|
|
list_del(&kp_iter->list);
|
|
kfree(kp_iter);
|
|
ret = count;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&kp_list_lock);
|
|
out:
|
|
argv_free(argv);
|
|
kfree(kbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __init kutf_kprobe_init(struct dentry *base_dir)
|
|
{
|
|
struct dentry *kutf_kp_reg_debugfs_file;
|
|
struct dentry *kutf_kp_unreg_debugfs_file;
|
|
|
|
if (!(register_kprobe(&kutf_kallsym_kp))) {
|
|
/* After kernel 5.7, 'kallsyms_lookup_name' is no longer
|
|
* exported. So we need this workaround to get the
|
|
* addr of 'kallsyms_lookup_name'. This will be used later
|
|
* in kprobe handler function to call the registered
|
|
* handler for a probe from the name passed from userspace.
|
|
*/
|
|
kutf_ksym_lookup_name = (kallsyms_lookup_name_t)kutf_kallsym_kp.addr;
|
|
unregister_kprobe(&kutf_kallsym_kp);
|
|
kutf_kp_reg_debugfs_file = debugfs_create_file("register_kprobe", 0200, base_dir,
|
|
NULL, &kutf_kp_reg_debugfs_fops);
|
|
if (IS_ERR_OR_NULL(kutf_kp_reg_debugfs_file))
|
|
pr_err("Failed to create kprobe reg debugfs file");
|
|
|
|
kutf_kp_unreg_debugfs_file = debugfs_create_file(
|
|
"unregister_kprobe", 0200, base_dir, NULL, &kutf_kp_unreg_debugfs_fops);
|
|
if (IS_ERR_OR_NULL(kutf_kp_unreg_debugfs_file)) {
|
|
pr_err("Failed to create kprobe unreg debugfs file");
|
|
debugfs_remove(kutf_kp_reg_debugfs_file);
|
|
}
|
|
} else
|
|
pr_info("kallsyms_lookup_name addr not available\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void kutf_kprobe_exit(void)
|
|
{
|
|
struct kutf_kp_data *kp_iter;
|
|
struct kutf_kp_data *n;
|
|
|
|
mutex_lock(&kp_list_lock);
|
|
|
|
list_for_each_entry_safe(kp_iter, n, &kp_list, list) {
|
|
unregister_kretprobe(&kp_iter->kretp);
|
|
argv_free(kp_iter->argv);
|
|
list_del(&kp_iter->list);
|
|
kfree(kp_iter);
|
|
}
|
|
|
|
mutex_unlock(&kp_list_lock);
|
|
}
|
|
|
|
#endif
|