fix(vkernel/intr): syscall 来源标记改放内核栈(可嵌套),修 app 调完 .so 后跑飞
- 现象:app 在用户态成功调 .so(“[so] call returned”)后立刻跑飞:用户态 #BP,rip=0x7c0b9(既不在 app 映像 0x400000-0x41c824、也不在 app 栈 0xbb11000)、rsp=0 - 根因:来源标记原来放在全局 _syscall_from_kernel,但 syscall 会**嵌套**:app 的 SO_CALL1 → 内核侧执行 .so → .so 内部又发 3 次 syscall(SERIAL_PUTS,内核态发起)→ 内层把标记改成 1 → 外层(用户态)返回时误走内核返回分支:jmp rcx 回到用户 rip 却不换回用户栈 → app 用内核栈继续跑 → 栈错位跑飞 - 改为把来源标记 push 在内核栈上(入口 push 0/1,出口 cmp [rsp] 判定),每层各压各的、天然可嵌套;内核返回路径 add rsp,8 弹掉本层标记槽 - 对齐保持:and rsp,~15 之后的深度 = 补齐8 + arg6 8 + call 8 = 24 ≡ 8 (mod 16) ✓(标记槽在 and 之前,不参与)
This commit is contained in:
1 parent
5916822184
commit
c232c0c0d5
1 file changed
+15
-13
+15
-13
@@ -11,9 +11,6 @@
|
||||
.comm _fault_rsp, 8, 8
|
||||
.comm _fault_rbp, 8, 8
|
||||
.comm _fault_rax, 8, 8
|
||||
# syscall 入口来源标记(1 = 内核态发起):出口据此决定 sysretq(用户)还是 jmp rcx(内核)。
|
||||
# 刻意不用 gs:[…]:引导早期内核可能还没 swapgs 过(GS ≠ &_per_cpu),gs 相对写会打偏地址。
|
||||
.comm _syscall_from_kernel, 8, 8
|
||||
# ISR 入口暂存 caller-saved 寄存器(rax/rcx/r8-r11):本宏会用它们当临时寄存器
|
||||
# (读 RIP/CS/RFLAGS、算 is_user),若不保存就会破坏被中断代码在这些寄存器里的
|
||||
# 活跃值 → 概率性内存破坏/异常。出口(iretq 前)恢复。
|
||||
@@ -440,10 +437,10 @@ syscall_from_user:
|
||||
swapgs
|
||||
mov gs:[0x0], rsp
|
||||
mov rsp, gs:[0x8]
|
||||
mov qword ptr [_syscall_from_kernel], 0 # 标记:用户态发起(出口据此选 sysretq)
|
||||
push 0 # 来源标记(用户态发起):出口据此选 sysretq
|
||||
jmp syscall_common
|
||||
syscall_from_kernel:
|
||||
mov qword ptr [_syscall_from_kernel], 1 # 标记:内核态发起(出口据此 jmp rcx 返回,不 swapgs)
|
||||
push 1 # 来源标记(内核态发起):出口据此 jmp rcx 返回,且不 swapgs
|
||||
syscall_common:
|
||||
push rcx
|
||||
push r11
|
||||
@@ -461,9 +458,11 @@ syscall_common:
|
||||
push r15
|
||||
mov rbp, rsp
|
||||
and rsp, ~15
|
||||
# 对齐补齐:被调者入口必须满足 SysV(rsp ≡ 8 (mod 16),栈上 1 个参数时要求调用前 rsp ≡ 8)。
|
||||
# 只 `and rsp,~15`(≡0) 再 push arg6 会让 handler 入口 ≡ 0 → 一旦其调用树里出现
|
||||
# rsp 相对的 16 字节对齐 SSE(内核里 printf 就有 movaps %xmm0,0x60(%rsp))即 #GP。
|
||||
# 对齐补齐:被调者入口必须满足 SysV(rsp ≡ 8 (mod 16);栈上 1 个参数时要求调用前 rsp ≡ 8)。
|
||||
# 注意 `and rsp,~15` 已把 rsp 归零,所以只有**它之后**的深度才影响对齐:
|
||||
# 补齐(8) + arg6(8) + call(8) = 24 ≡ 8 ✓。标记槽与 14 个寄存器都在 and 之前,不参与。
|
||||
# (去掉这里的 `sub rsp,8` 会让 handler 入口 ≡ 0 → 其调用树里 rsp 相对的 16 字节对齐 SSE
|
||||
# 例如内核 printf 的 movaps %xmm0,0x60(%rsp) 就 #GP。)
|
||||
sub rsp, 8
|
||||
push r9
|
||||
mov r9, r8
|
||||
@@ -489,18 +488,21 @@ syscall_common:
|
||||
pop rbx
|
||||
pop r11
|
||||
pop rcx
|
||||
# 返回去向改用**入口记下的 _syscall_from_kernel 标记**。原来用 `mov rax,rcx; shr rax,47`
|
||||
# 判:① 会把 rax 里的返回值覆盖掉(app 的 trampoline 就靠它取结果);② 用户 rip 在 0x40xxxx、
|
||||
# 内核 rip 在 0x10xxxx,两者都 < 2^47,根本无法区分,内核态发起的 syscall 会误走 sysretq。
|
||||
cmp qword ptr [_syscall_from_kernel], 0
|
||||
# 返回去向用**栈上的来源标记**([rsp],入口 push 0/1)。必须是栈上而不是全局:syscall 会
|
||||
# 嵌套(app 的 SO_CALL1 → 内核调 .so → .so 内部又 syscall 打日志),全局标记会被内层覆盖,
|
||||
# 外层(用户态)就会误走内核返回分支 → jmp rcx 回用户 rip 却不换回用户栈,app 用内核栈继续跑。
|
||||
# 原来用 `mov rax,rcx; shr rax,47` 判还有第二个错:覆盖 rax 里的返回值,且用户 rip(0x40xxxx)
|
||||
# 与内核 rip(0x10xxxx) 都 < 2^47 无法区分。
|
||||
cmp qword ptr [rsp], 0
|
||||
jne syscall_ret_kernel
|
||||
syscall_ret_user:
|
||||
mov rsp, gs:[0x0]
|
||||
mov rsp, gs:[0x0] # 用户路径直接切回用户栈(本层标记槽留在内核栈,下次入口由 TSS.RSP0 重置)
|
||||
swapgs
|
||||
push r11
|
||||
popfq
|
||||
sysretq
|
||||
syscall_ret_kernel:
|
||||
add rsp, 8 # 内核路径:弹掉本层标记槽,恢复到入口 rsp
|
||||
push r11
|
||||
popfq
|
||||
jmp rcx
|
||||
|
||||
Reference in new issue
Block a user