如何构造copy_to_user()调用失败的用户态测试场景?
copy_to_user()总不失败?我来给你掰扯清楚 哥们,我之前踩过这个坑!你现在的问题核心是对copy_to_user()的行为理解有偏差——它压根不会帮你检查用户态缓冲区的实际容量,只认你传的长度参数和地址的合法性。
先解释为啥你当前的代码没触发失败
不管你是用栈上的type to[1]还是malloc分配的内存,只要这个地址是合法的用户空间可写地址,哪怕你只分配了1个元素的空间,copy_to_user()还是会硬着头皮写完你指定的sizeof(type)*2字节。
这时候用户态其实已经发生了越界写入,但copy_to_user()根本不关心这个——它的职责只是把内核数据复制到你给的用户地址,只要地址属于当前进程的可写用户空间,就会返回0(表示全部复制完成)。至于你用户态缓冲区装不下,那是用户态代码的问题,内核不背锅。
比如你用malloc(sizeof(type)),glibc的分配器通常会多分配一些内存来做管理,所以你越界写的那部分刚好在分配器预留的空间里,内核完全能正常写入,自然不会返回错误。
怎么合法触发copy_to_user()失败(不用空指针)
要让它返回非0值,得让内核无法完成全部复制,这里给你两个靠谱的方法:
方法1:传递部分非法的用户地址
用mmap()分配一块刚好等于1个type大小的内存,这样缓冲区后面的地址直接不属于当前进程的地址空间,copy_to_user()写入到非法区域时就会失败,返回未复制的字节数。
用户态示例代码:
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/syscall.h> #include <unistd.h> typedef int type; // 假设你的type是int,按需替换 #define SYS_myfoo 1234 // 替换成你的系统调用号 int main() { // 分配刚好1个type的内存,后面是非法地址 type *to = mmap(NULL, sizeof(type), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (to == MAP_FAILED) { perror("mmap"); return 1; } long res = syscall(SYS_myfoo, to); printf("Syscall returned: %ld\n", res); // 应该返回sizeof(type) munmap(to, sizeof(type)); return 0; }
方法2:把用户态缓冲区设为只读
用mprotect()把用户态缓冲区改成只读权限,这样copy_to_user()尝试写入时会触发权限错误,直接返回全部未复制的字节数。
用户态示例代码片段:
type *to2 = malloc(sizeof(type)*2); if (!to2) { perror("malloc"); return 1; } // 把缓冲区设为只读 if (mprotect(to2, sizeof(type)*2, PROT_READ) == -1) { perror("mprotect"); free(to2); return 1; } long res = syscall(SYS_myfoo, to2); printf("Syscall with readonly buf returned: %ld\n", res); // 应该返回sizeof(type)*2 // 恢复权限再释放 mprotect(to2, sizeof(type)*2, PROT_READ | PROT_WRITE); free(to2);
内核侧的系统调用代码(参考)
确保你的内核代码正确返回copy_to_user()的结果:
#include <linux/syscalls.h> #include <linux/uaccess.h> typedef int type; SYSCALL_DEFINE1(myfoo, type __user *, to) { type from[2] = {0x1234, 0x5678}; int not_copied = copy_to_user(to, from, sizeof(type)*2); printk(KERN_INFO "copy_to_user returned: %d\n", not_copied); return not_copied; }
这样操作后,你就能看到copy_to_user()返回非0值,也就是你想要的失败状态了。
内容的提问来源于stack exchange,提问作者Jared

