已开启
[Bug]: syscall_dispatcher 对未知 syscall 号执行用户态指针写入 #39
CupCupSir创建于 12 天前
openharmony_ci
12 天前 评论:
12 天前 评论:
感谢提交Issue!关于Issue的交互操作,请访问OpenHarmony社区支持命令清单。如果有问题,请联系 [@peterli](https://gitcode.com/peterli) [@heyanhong](https://gitcode.com/heyanhong) [@sjtugjy](https://gitcode.com/sjtugjy) 。如果需要调整订阅PR、Issue的变更状态,请访问链接。
Thanks for submitting the issue. For more commands, please visit OpenHarmony Command List. If you have any questions, please refer to committer gitcode for help. If you need to change the subscription of a Pull Request or Issue, please visit the link.


12 天前 添加了label:waiting_for_assign
问题概述
ChCore 为 musl 提供的 syscall 兼容分发器会在用户进程发起未知 Linux syscall 时,把 syscall 号转换为指针并写入整数零。调用进程可以通过 musl 的公开
syscall()接口控制这个编号;若编号恰好是该进程中已映射且可写的虚拟地址,四字节用户态数据会被清零。更常见的未知 syscall 编号是较小的未映射地址,此时写操作触发 EL0 页故障并终止当前进程,而不是返回ENOSYS。这不是 TEE 内核任意地址写,也没有由这条写指令建立权限提升能力。
dead()属于构建进libc_shared.so的用户态 libc 代码,执行于调用进程的 EL0 地址空间;ChCore 为进程切换独立的vmspace,内核高地址映射不允许 EL0 写入。能够主动选择一个本进程可写地址的恶意程序,本来就能直接修改该地址。我静态检查了 2026-09-01 远端
master789b0fec1597c80ca6cb2a11aab3222b02e333b6、发布标签、libc 构建流程、AArch64 页表权限和公开 GitCode Issue/MR;没有构建或运行 TEEOS。现有宿主机简化程序只能复现同一条 C 指针写入语句,不能证明 TEEOS 中的权限边界、目标地址可写性或生产调用链,因此不把它作为目标环境 PoC。最早可验证包含该实现的公开版本为OpenHarmony-v6.0-Beta1,已检查的 6.0、6.1 和 7.0 发布标签以及当前master均未修复。问题详情
user/system-services/chcore-libc/libchcore/porting/overrides/src/chcore-port/syscall_dispatcher.c不是内核 syscall 处理器。顶层Makefile先复制指定版本的 musl,再用libchcore/porting/overrides和补丁替换其 ChCore 适配文件。AArch64 的syscall_arch.h补丁把 musl 的__syscall0()至__syscall6()从直接执行svc改为调用该文件中的普通 C 函数。顶层构建随后把 musl 对象链接为
libc_shared.so。build/build_tee.sh将它复制到 ramdisk、预构建库目录和 framework 输出目录,然后构建 TEE framework 应用。因此,这段分发逻辑是 TEEOS 用户态程序所用 libc 的一部分,不是仅存在于测试目录的代码。musl 的公开
syscall(long n, ...)将调用者提供的n和最多六个参数交给__syscall()。在 AArch64 适配后,六参数形式会进入这里的__syscall6(n, ...)。调用者因而可以控制n,但执行上下文仍是调用者自己的用户进程。ChCore 启动用户线程时把
SPSR_EL1设置为SPSR_EL1_EL0t。调度切换通过进程的vmspace->pgtbl更新TTBR0_EL1。用户可写页使用EL0_RW权限,而内核位于KBASE开始的高地址区域,其启动页表项没有授予 EL0 访问权限。这些代码共同限定了dead()中普通指针写入的作用范围。根本原因
当前
master的__syscall6()对已实现的 Linux syscall 进行显式分发。未知编号进入default分支:default: dead(n); return chcore_syscall6(n, a, b, c, d, e, f);同一文件末尾的
dead()没有返回错误或明确终止进程,而是把n当作地址使用:void dead(long n) { printf("Unsupported syscall %d, bye.\n", n); volatile int *addr = (int *)n; *addr = 0; }同样的调用模式存在于
__syscall0()至__syscall5()的default分支。因此,问题并不局限于六参数 syscall。实际行为取决于编号对应地址在当前进程页表中的状态:
dead()在当前进程中写入四字节零,然后继续执行后续代码。kernel/arch/aarch64/irq/pgfault.c在无法处理该用户页故障时调用sys_exit_group(-1),终止当前进程。成功写零后,当前代码还会把未知 Linux syscall 编号原样传给
chcore_syscallN()。Linux ABI 编号和 ChCore 内核编号不是同一个命名空间,所以未知编号不应被无条件继续发送到内核。该透传应与危险写入一起删除;本文不把它进一步表述为已经动态验证的内核利用路径。这段实现由 2023 年的
update latest code and libc变更引入。该文件在后续一次清理提交后保持不变。OpenHarmony-v6.0-Beta1、OpenHarmony-v6.0-Release、OpenHarmony-v6.0.0.1-Release、OpenHarmony-v6.0.0.2-Release、OpenHarmony-v6.1-LTS、OpenHarmony-v6.1-Release、OpenHarmony-v7.0-Beta1、OpenHarmony-v7.0-Release和当前master的文件 blob 相同;仓库历史中没有修复提交或已修复发行版本。触发条件
TEEOS 用户态程序通过 musl 的公开
syscall()接口提交当前分发器未支持的 Linux syscall 编号时会进入该分支。编号若对应当前进程的可写映射,四字节数据被清零;编号对应未映射或不可写地址时,当前进程触发 EL0 页故障。现有源码未确认普通 CA 输入能够直接控制某个生产服务的 syscall 编号。影响分析
源码证明的最小问题是错误的未知 syscall 处理语义:本应返回“不支持”的调用,会改写当前进程内存或导致当前进程异常退出。
调用
syscall()的代码可以控制n。不过,将n选为已知的本进程可写地址并不会越过权限边界,因为同一代码已经能直接写该地址。它不能借此访问其他用户进程的独立vmspace,也不能绕过 EL0 对内核页的访问限制。因此,不能把这项能力描述为内核任意地址写或 TA 提权。对正常程序更实际的影响是兼容性和可用性。程序、运行库或新移植组件如果调用当前分发器尚未支持的 syscall,通常会在一个较小、未映射的 syscall 编号地址上触发页故障,导致调用进程退出,而不是收到
ENOSYS后采用兼容路径。若发生在关键用户态服务中,该服务会中断;具体服务恢复策略和整机影响取决于部署配置。当前仓库的生产构建确认会部署这份 libc,但静态搜索没有找到现有产品代码把外部请求直接转换成任意 syscall 编号,也没有确认某个默认业务流程必然进入未知分支。因此,本文不声称普通 CA 输入可以直接触发该行为,也不声称已观察到生产服务崩溃。
验证状态
按本次复核范围,没有在 TEEOS 中执行 PoC。已有宿主机简化程序只证明把整数强制转换为本进程可写指针后能够写零,以及不可访问地址会使该宿主进程收到
SIGSEGV;这属于 C 语句级演示,不能证明 TEEOS 内核写入或权限提升。以下目标结论均来自静态检查:syscall(long n, ...)保留调用者提供的n,并把它传给 ChCore 覆盖后的__syscall6()。__syscall0()至__syscall6()的未知分支都先调用dead(n)。dead()在执行任何真实svc之前以普通 C 指针写入零,因此写入发生在用户态。vmspace;内核高地址页没有 EL0 写权限。sys_exit_group(-1)。syscall_dispatcher.c、dead()、Unsupported syscall或同一根因的条目。syscall_dispatcher.c或删除该写入。已有的“不可信输入校验”MR修改了其他文件,不覆盖本问题。这些检查没有观察到真实写零、页故障或进程退出,也没有验证任何内核控制流影响。
修复建议
未知 Linux syscall 应在用户态分发器中直接返回
-ENOSYS。不要通过任意内存写入制造故障,也不要把未映射的 Linux syscall 编号原样传给 ChCore 内核 syscall 表。可以把
dead()改为返回错误的辅助函数,并让所有default分支立即返回:static long unsupported_syscall(long n) { printf("Unsupported syscall %ld.\n", n); return -ENOSYS; } /* __syscall0() ... __syscall6() */ default: return unsupported_syscall(n);这里同时把格式说明符从
%d修正为与long匹配的%ld。如果项目明确要求未知 syscall 终止调用方,也应调用清晰的用户进程退出路径,而不是解引用由 syscall 号构造的地址;但返回ENOSYS更符合 libc 调用者的常规兼容预期。建议增加以下回归测试:
-1且errno == ENOSYS,进程继续运行。__syscall0()至__syscall6()的未知分支,确认行为一致。svc。涉及文件
user/system-services/chcore-libc/libchcore/porting/overrides/src/chcore-port/syscall_dispatcher.cuser/system-services/chcore-libc/libchcore/porting/overrides/arch/aarch64/syscall_arch.hkernel/arch/aarch64/irq/pgfault.cMakefilebuild/build_tee.sh版本信息
当前问题是 ChCore 用户态 libc 对未知 syscall 的错误处理:调用者控制的编号被当作当前进程地址写零;地址不可访问时,当前进程因 EL0 页故障退出。最早可验证的受影响公开版本是
OpenHarmony-v6.0-Beta1,已检查的所有后续发布标签和当前master仍包含相同实现,尚无已确认的修复版本。该写入受调用进程页表和 EL0 权限限制,不能按原有描述升级为 TEE 内核任意地址写或权限提升。修复应删除指针解引用,让所有未知分支返回
ENOSYS,并停止把未知 Linux syscall 编号透传给 ChCore 内核。