内核模块开发:内核到用户空间传数组遇段错误问题排查
问题重现
用户空间通过IOCTL尝试获取内核数组:
ret_val = ioctl(filedes, IOCTL_PRINT, localarray);
内核空间使用copy_to_user传递数据:
ret = copy_to_user((int *)ioctl_param, kernel_array, 15 * sizeof(int));
运行后内核日志能正常输出数组内容,但用户空间程序触发段错误,错误日志:
[ 461.909674] Data value in index 0 : 21
[ 461.909678] Data value in index 1 : 20
...(省略中间数组输出)
[ 461.909740] Data value in index 14 : 7
[ 461.910279] io[1799]: segfault at c0000000d ip 0000000c0000000d sp 00007fffaa57cd40 error 14 in io[556f58436000+1000]
[ 461.910288] Code: Unable to access opcode bytes at RIP 0xbffffffe3.
疑问:是否是指针解引用出现了问题?
问题分析
段错误的核心原因是用户空间程序尝试访问内核空间地址(c0000000d),这属于非法内存访问(用户空间无权访问内核地址空间)。导致该问题的常见原因如下:
IOCTL命令定义错误:
若未正确使用_IOR(从内核读数据到用户空间)宏定义IOCTL命令,会导致内核与用户空间对参数的理解不一致。比如误用_IOW(往内核写数据)宏,会让内核把用户空间传递的指针当成数值处理,反之亦然,最终导致内存地址混淆。内核未验证用户空间地址有效性:
直接将ioctl_param转换为指针并调用copy_to_user,未先通过access_ok验证地址是否属于用户空间可访问范围。如果ioctl_param是非法地址(比如内核地址),copy_to_user会返回错误,但如果忽略该返回值,用户空间后续访问未正确填充的内存时会触发段错。用户空间数组声明错误:
若localarray是未初始化的指针(而非固定大小的数组),其指向的地址是随机的,可能恰好落在内核地址空间,导致后续访问触发段错误。忽略
copy_to_user返回值:copy_to_user返回未成功复制的字节数,若返回值不为0,说明数据传递失败,此时内核应返回-EFAULT给用户空间,避免用户空间继续访问无效内存。
解决方案
针对上述问题,按以下步骤修复:
修正IOCTL命令定义:
确保使用_IOR宏定义从内核读取数据的命令:#define IOCTL_PRINT _IOR('k', 1, int[15])其中
'k'是魔数,1是命令编号,int[15]是数据类型。内核中验证用户地址并处理错误:
在调用copy_to_user前,先验证地址有效性,并检查返回值:long ioctl_handler(struct file *file, unsigned int cmd, unsigned long arg) { int ret; int kernel_array[15] = {21,20,19,...,7}; // 示例内核数组 int __user *user_array = (int __user *)arg; if (cmd != IOCTL_PRINT) { return -ENOTTY; } // 验证用户空间地址可写 if (!access_ok(VERIFY_WRITE, user_array, 15 * sizeof(int))) { return -EFAULT; } // 复制数据到用户空间 ret = copy_to_user(user_array, kernel_array, 15 * sizeof(int)); if (ret != 0) { // 部分数据未复制,返回错误 return -EFAULT; } return 0; }用户空间正确声明数组:
确保localarray是足够大小的数组,而非未初始化的指针:int localarray[15]; // 正确声明 // 而非 int *localarray; (未分配内存的指针会导致随机地址) ret_val = ioctl(filedes, IOCTL_PRINT, localarray); if (ret_val == -1) { perror("ioctl failed"); exit(EXIT_FAILURE); }用户空间检查IOCTL返回值:
调用ioctl后必须检查返回值,若失败则终止操作,避免访问无效内存。
内容的提问来源于stack exchange,提问作者Dillip Murali

