如何将argv传递给Linux自定义内核模块中定义的系统调用
内核态自定义系统调用接收双参数的最佳实践
核心思路
内核处理用户态参数的核心是只拿必要信息,拒绝不可控的复杂结构,直接传递指针数组(比如argv)风险太高——用户态可以篡改指针指向,导致内核访问非法内存。下面是几种安全且高效的传参方案:
推荐方案
1. 独立传递字符串指针+长度
这是最直接的方式,用户态把两个参数的指针和各自长度传给系统调用,内核按以下流程处理:
- 先检查两个指针是否为
NULL,任一为空直接返回-EINVAL - 限制参数长度(比如设最大值为4096,避免恶意传递超大值),超过返回
-E2BIG - 用
copy_from_user分别把两个字符串拷贝到内核栈(长度小的话)或堆内存 - 手动给拷贝后的字符串加
\0结尾(防止用户态未传递终止符) - 最后做业务相关的有效性校验(比如字符集、格式等)
伪代码示例:
// 用户态调用 long sys_mycmd(const char *arg1, size_t len1, const char *arg2, size_t len2); // 内核态实现 SYSCALL_DEFINE4(mycmd, const char __user *, arg1, size_t, len1, const char __user *, arg2, size_t, len2) { #define MAX_ARG_LEN 4096 char buf1[MAX_ARG_LEN]; char buf2[MAX_ARG_LEN]; // 检查参数是否存在 if (!arg1 || !arg2) return -EINVAL; // 检查长度是否合法 if (len1 >= MAX_ARG_LEN || len2 >= MAX_ARG_LEN) return -E2BIG; // 拷贝用户态数据 if (copy_from_user(buf1, arg1, len1) || copy_from_user(buf2, arg2, len2)) return -EFAULT; // 确保字符串终止 buf1[len1] = '\0'; buf2[len2] = '\0'; // 这里做参数有效性校验,比如检查是否包含非法字符等 // ... return 0; }
2. 用结构体封装参数(适合未来扩展)
如果以后可能增加参数个数,可以定义一个用户态和内核态共用的结构体,把参数计数、指针、长度都装进去:
// 共用头文件(用户态和内核态都包含) struct mycmd_args { int argc; // 参数个数,这里必须是2 const char *arg1; size_t len1; const char *arg2; size_t len2; };
内核处理步骤:
- 先把整个结构体从用户态拷贝到内核
- 检查
argc是否为2,不是直接返回-EINVAL - 再依次检查每个字符串的指针和长度,拷贝并校验
这种方案扩展性强,后续加参数只需要修改结构体,不用调整系统调用的参数列表。
避坑提醒
- 别直接传
argv数组:用户态的argv是指针数组,内核无法验证每个指针的合法性,很容易被恶意利用导致内核崩溃或漏洞。 - 别用
\0拼接字符串:不仅冗余,还容易因为用户传递的字符串本身包含\0导致解析错误,可靠性差。
额外安全细节
- 内核拷贝数据时,短字符串用栈(效率高),长字符串用
kmalloc分配,用完记得释放。 - 对拷贝后的字符串做严格校验:比如禁止控制字符、限制允许的字符集、检查格式是否符合业务需求,避免注入类风险。
- 用户态提前检查
argc:用户程序在调用系统调用前先判断argc == 3(因为argv[0]是程序名),确保有两个有效参数再调用,减少内核的无效处理。
内容的提问来源于stack exchange,提问作者G. Ianni
相关产品推荐
相关产品推荐

