Android MSM内核copy_to_user仅支持128字节以内拷贝问题问询
从你的描述和日志来看,核心问题是copy_to_user在拷贝超过128字节时完全失败(返回未拷贝字节数等于请求长度),但access_ok验证用户缓冲区权限是通过的。这种现象在Android设备上通常和几个特定因素有关,下面一步步分析排查:
1. 先明确copy_to_user的真实返回值
你的代码里用sent -= copy_to_user(...),虽然能得到最终成功拷贝的字节数,但看不到copy_to_user本身返回的未拷贝字节数。建议修改日志打印,直接输出这个关键值:
unsigned long not_copied = copy_to_user(buffer, mybuf + tail, sent); pr_err("MYLOGGER: copy_to_user returned %lu (unable to copy), requested %lu bytes\n", not_copied, sent); sent -= not_copied;
这能帮你确认是整个缓冲区都无法拷贝,还是部分失败——从当前日志看是全部失败,但明确这一点很重要。
2. 排查用户缓冲区的页面有效性
access_ok只是验证地址在用户空间范围内,不代表对应的物理页面存在或可写。当dd用大bs时,会使用堆缓冲区,而堆内存可能是离散的,甚至存在页面未被分配的情况(比如COW机制下的空白页面)。你可以在驱动里检查每个用户页面的可用性:
#include <linux/mm.h> #include <linux/sched.h> // 检查用户缓冲区的每个页面是否可写 struct page *page; unsigned long user_addr = (unsigned long)buffer; size_t remaining = sent; int page_idx = 0; while (remaining > 0) { page = follow_page(current->mm, user_addr, FOLL_WRITE); if (!page) { pr_err("MYLOGGER: Failed to get user page at offset %d, addr %lx\n", page_idx, user_addr); break; } put_page(page); size_t step = min(PAGE_SIZE, remaining); user_addr += step; remaining -= step; page_idx++; }
如果输出某个页面找不到,那说明用户缓冲区对应的物理页面尚未分配,这时候copy_to_user会失败。这种情况下,你可以先让用户空间预填充缓冲区(比如用memset),或者在驱动里调用get_user_pages强制分配页面。
3. 重点排查SELinux权限(Android设备的常见坑)
Android的SELinux强制访问控制会严格限制内核驱动对用户空间内存的访问。当dd使用栈缓冲区(小bs)时,栈内存的SELinux标签可能允许驱动访问;而堆内存的标签不同,被SELinux规则阻止了。
临时验证方法:在adb shell里执行setenforce 0关闭SELinux强制模式,然后重新测试dd if=/dev/mylog bs=4096。如果此时能正常读取,那就是SELinux的问题。
永久解决方法:需要添加对应的SELinux规则。在你的设备源码的device/sepolicy/private/目录下创建或修改规则文件,添加:
allow mylogger_device appdomain:memprotect rw; allow mylogger_device appdomain:process getattr;
(mylogger_device是你的驱动设备的SELinux类型,需要和你在驱动里设置的struct device的security属性一致)然后重新编译SEPolicy并刷入设备。
4. 检查内核缓冲区的有效性
确保mybuf + tail指向的是有效的内核可访问内存:
- 如果
mybuf是用vmalloc分配的,虽然copy_to_user支持虚拟地址,但某些ARM64内核版本对vmalloc内存的拷贝有特殊限制,建议换成kmalloc或__get_free_pages分配连续物理内存试试。 - 确认
tail + sent没有超出mybuf的分配长度,避免越界访问导致的未定义行为。
5. 其他可能的排查点
- 检查内核版本:Nexus 5X的内核通常是Android 7.0-8.1的版本,某些旧版本可能存在
copy_to_user的小bug,可以尝试升级内核源码到对应分支的最新版本。 - 验证进程上下文:确保
dev_read是在用户进程上下文执行的(不是中断上下文),current->mm指向正确的用户进程地址空间——从你的描述看pid一致,这一点应该没问题,但可以用in_interrupt()验证一下。
按照上面的步骤排查,大概率能定位到问题,尤其是SELinux和用户页面有效性这两个Android特有的因素。
内容的提问来源于stack exchange,提问作者tux_mind

