使用kretprobe挂钩__x64_sys_execve获取argv时触发页错误致系统崩溃
kretprobe挂钩__x64_sys_execve时argv处理导致内核崩溃的修复方案
问题定位
你碰到的核心问题是:用kretprobe挂钩__x64_sys_execve的entry handler时,处理char** argv参数引发内核页错误(权限违规),卸载模块时进程被杀、系统崩溃;仅读取filename时模块运行完全正常。环境为VMware Ubuntu22.04,内核版本6.5.0-41-generic。
根本原因
char** argv是用户态的二级指针,直接操作会踩两个关键坑:
- 需先读取用户态的指针数组本身,再逐个读取每个指针指向的字符串,不能直接用
strncpy_from_user操作二级指针 - 未验证用户态地址的合法性,导致内核访问了非法地址触发页错误;卸载时残留的非法地址引用进一步引发系统崩溃
修复思路与代码示例
1. 安全读取用户态二级指针的步骤
- 先用
copy_from_user读取argv数组中的单个字符串指针 - 用
access_ok验证指针的用户态合法性(新内核中strncpy_from_user内部会做校验,但显式验证更稳妥) - 设置参数个数上限(比如64个),避免无限循环或越界
- 每一步操作都要检查返回值,失败立即终止处理
2. 修复后的代码片段
#include <linux/kprobes.h> #include <linux/sched.h> #include <linux/uaccess.h> #define MAX_ARGC 64 #define MAX_STRLEN 256 static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { char __user *filename = (char __user *)regs->di; char __user __user **argv = (char __user __user **)regs->si; char filename_buf[MAX_STRLEN] = {0}; char argv_buf[MAX_ARGC][MAX_STRLEN] = {0}; int i = 0; // 读取filename(这部分你已验证正常) if (strncpy_from_user(filename_buf, filename, MAX_STRLEN-1) < 0) { pr_err("Failed to read execve filename\n"); return 0; } pr_info("Execve called: %s\n", filename_buf); // 安全读取argv参数 while (i < MAX_ARGC) { char __user *arg_ptr; // 先读取argv数组中的单个指针 if (copy_from_user(&arg_ptr, &argv[i], sizeof(char __user *))) { pr_err("Failed to get argv[%d] pointer\n", i); break; } if (!arg_ptr) { // 遇到NULL,argv参数结束 break; } // 验证地址是否为合法用户态地址 if (!access_ok(arg_ptr, MAX_STRLEN)) { pr_err("argv[%d] points to invalid user address\n", i); break; } // 读取字符串内容 if (strncpy_from_user(argv_buf[i], arg_ptr, MAX_STRLEN-1) < 0) { pr_err("Failed to read argv[%d] content\n", i); break; } pr_info("argv[%d]: %s\n", i, argv_buf[i]); i++; } return 0; } static struct kretprobe execve_kretprobe = { .kp.symbol_name = "__x64_sys_execve", .entry_handler = entry_handler, }; static int __init execve_kretprobe_init(void) { int ret = register_kretprobe(&execve_kretprobe); if (ret < 0) { pr_err("register_kretprobe failed: %d\n", ret); return ret; } pr_info("kretprobe registered for __x64_sys_execve\n"); return 0; } static void __exit execve_kretprobe_exit(void) { unregister_kretprobe(&execve_kretprobe); pr_info("kretprobe unregistered\n"); } module_init(execve_kretprobe_init); module_exit(execve_kretprobe_exit); MODULE_LICENSE("GPL");
额外注意事项
- 编译模块时必须确保内核头文件版本与运行内核(6.5.0-41-generic)完全匹配,否则可能出现隐性兼容性问题
- 测试时先在简单场景验证(比如执行
ls、echo等基础命令),避免在复杂进程(如桌面应用)上测试 - 卸载模块前,尽量确保没有正在执行
execve的进程,或等待所有kretprobe实例处理完成,避免残留引用引发崩溃 - 内核页错误提示权限违规,本质是内核直接访问了未通过安全函数校验的用户态地址,必须严格使用
copy_from_user、strncpy_from_user等内核提供的用户态访问函数
内容的提问来源于stack exchange,提问作者sena
相关产品推荐
相关产品推荐

