NDK vfork implementation: push {r4, r7} mov r7, #190 ; 0xbe svc 0x00000000 pop {r4, r7} movs r0, r0 bxpl lr b 0x1475c bionic C vfork implentation(KitKat): ENTRY(vfork) mov ip, r7 ldr r7, =__NR_vfork swi #0 mov r7, ip cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b __set_errno END(vfork)
与KitKat的vfork实现比较,感觉问题出在 push {r4, r7}
不知道NDK下个版本是否会修改vfork的实现,这样很容易导致栈内存出问题。
临时解决方法:
my_vfork.S
/* autogenerated by gensyscalls.py */ #include <asm/unistd.h> #include <linux/err.h> #include <machine/asm.h> ENTRY(my_vfork) mov ip, r7 ldr r7, =__NR_vfork swi #0 mov r7, ip cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b my_set_errno END(my_vfork)
---------------------------------------------------------------------------------------
test.c
int my_set_errno(int n) { errno = n; return -1; } int test(char** argv) { int status; #if 0 pid_t pid = vfork(); #else pid_t pid = my_vfork(); #endif if (pid <0) { fprintf(stderr, "vfork failed(%s)\n", strerror(errno)); return; } if (pid == 0) { execvp(argv[0], argv); _exit(111); }else { pid = waitpid(0, &status, 0); } return 0; }
==============================================================================================
使用 gdb 跟踪该缺陷
# gdb --args /data/local/tmp/vfork ls -l Reading symbols from /data/local/tmp/vfork...done. (gdb) b vfork Breakpoint 1 at 0x9684: file bionic/libc/arch-arm/syscalls/vfork.S, line 13. (gdb) set follow-fork-mode child (gdb) display /4i $pc-8 (gdb) r Starting program: /data/local/tmp/vfork ls -l Breakpoint 1, vfork () at bionic/libc/arch-arm/syscalls/vfork.S:13 13 bionic/libc/arch-arm/syscalls/vfork.S: No such file or directory. 1: x/4i $pc-8 0x967c <_exit+28>: nop ; (mov r0, r0) 0x9680 <vfork>: push {r4, r7} => 0x9684 <vfork+4>: mov r7, #190 ; 0xbe 0x9688 <vfork+8>: svc 0x00000000 (gdb) info registers r0 0xbeb2f8e4 3199400164 r1 0xbeb2faa8 3199400616 r2 0x0 0 r3 0xc 12 r4 0x3 3 r5 0xbeb2f970 3199400304 r6 0xbeb2f8ec 3199400172 r7 0xbeb2f8f8 3199400184 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0xbeb2f95c 3199400284 r12 0x0 0 sp 0xbeb2f8c0 0xbeb2f8c0 lr 0x8165 33125 pc 0x9684 0x9684 <vfork+4> cpsr 0x80000010 -2147483632 (gdb) x/4xw $sp 0xbeb2f8c0: 0x00000003 0xbeb2f8f8 0xbeb2f8e4 0xbeb2f8e4 (gdb) disassemble $pc Dump of assembler code for function vfork: 0x00009680 <+0>: push {r4, r7} => 0x00009684 <+4>: mov r7, #190 ; 0xbe 0x00009688 <+8>: svc 0x00000000 0x0000968c <+12>: pop {r4, r7} 0x00009690 <+16>: movs r0, r0 0x00009694 <+20>: bxpl lr 0x00009698 <+24>: b 0x145c4 0x0000969c <+28>: nop ; (mov r0, r0) End of assembler dump. (gdb) b *0x968c Breakpoint 2 at 0x968c: file bionic/libc/arch-arm/syscalls/vfork.S, line 15. (gdb) b execvp Breakpoint 3 at 0x9a98: file bionic/libc/unistd/exec.c, line 202. (gdb) c Continuing. [New process 5934] [Switching to process 5934] Breakpoint 2, vfork () at bionic/libc/arch-arm/syscalls/vfork.S:15 15 in bionic/libc/arch-arm/syscalls/vfork.S 1: x/4i $pc-8 0x9684 <vfork+4>: mov r7, #190 ; 0xbe 0x9688 <vfork+8>: svc 0x00000000 => 0x968c <vfork+12>: pop {r4, r7} 0x9690 <vfork+16>: movs r0, r0 (gdb) x/4xw $sp 0xbeb2f8c0: 0x00000003 0xbeb2f8f8 0xbeb2f8e4 0xbeb2f8e4 (gdb) ni vfork () at bionic/libc/arch-arm/syscalls/vfork.S:16 16 in bionic/libc/arch-arm/syscalls/vfork.S 1: x/4i $pc-8 0x9688 <vfork+8>: svc 0x00000000 0x968c <vfork+12>: pop {r4, r7} => 0x9690 <vfork+16>: movs r0, r0 0x9694 <vfork+20>: bxpl lr (gdb) 17 in bionic/libc/arch-arm/syscalls/vfork.S 1: x/4i $pc-8 0x968c <vfork+12>: pop {r4, r7} 0x9690 <vfork+16>: movs r0, r0 => 0x9694 <vfork+20>: bxpl lr 0x9698 <vfork+24>: b 0x145c4 (gdb) test (argv=0x1f78028) at jni/vfork.c:48 48 jni/vfork.c: No such file or directory. 1: x/4i $pc-8 0x815c <test>: push {r0, r1, r2, r3, r4, lr} 0x815e <test+2>: str r0, [sp, #4] 0x8160 <test+4>: blx 0x9680 <vfork> => 0x8164 <test+8>: cmp r0, #0 (gdb) 0x00008166 48 in jni/vfork.c 1: x/4i $pc-8 0x815e <test+2>: str r0, [sp, #4] 0x8160 <test+4>: blx 0x9680 <vfork> 0x8164 <test+8>: cmp r0, #0 => 0x8166 <test+10>: bge.n 0x8186 <test+42> (gdb) 53 in jni/vfork.c 1: x/4i $pc-8 0x817e <test+34>: adds r0, #168 ; 0xa8 0x8180 <test+36>: bl 0x9d10 <fprintf> 0x8184 <test+40>: b.n 0x81a6 <test+74> => 0x8186 <test+42>: cmp r0, #0 (gdb) 0x00008188 53 in jni/vfork.c 1: x/4i $pc-8 0x8180 <test+36>: bl 0x9d10 <fprintf> 0x8184 <test+40>: b.n 0x81a6 <test+74> 0x8186 <test+42>: cmp r0, #0 => 0x8188 <test+44>: bne.n 0x819a <test+62> (gdb) 55 in jni/vfork.c 1: x/4i $pc-8 0x8182 <test+38>: stc2l 0, cr14, [r6, #60] ; 0x3c 0x8186 <test+42>: cmp r0, #0 0x8188 <test+44>: bne.n 0x819a <test+62> => 0x818a <test+46>: ldr r3, [sp, #4] (gdb) 0x0000818c 55 in jni/vfork.c 1: x/4i $pc-8 0x8184 <test+40>: b.n 0x81a6 <test+74> 0x8186 <test+42>: cmp r0, #0 0x8188 <test+44>: bne.n 0x819a <test+62> 0x818a <test+46>: ldr r3, [sp, #4] (gdb) 0x0000818e 55 in jni/vfork.c 1: x/4i $pc-8 0x8186 <test+42>: cmp r0, #0 0x8188 <test+44>: bne.n 0x819a <test+62> 0x818a <test+46>: ldr r3, [sp, #4] 0x818c <test+48>: ldr r0, [r3, #0] (gdb) c Continuing. Breakpoint 3, execvp (name=0x1f78028 "ls", argv=0xbeb2f8e4) at bionic/libc/unistd/exec.c:202 202 bionic/libc/unistd/exec.c: No such file or directory. 1: x/4i $pc-8 0x9a90 <execvp>: push {r4, r5, r6, r7, lr} 0x9a92 <execvp+2>: ldr r5, [pc, #464] ; (0x9c64 <execvp+468>) 0x9a94 <execvp+4>: ldr r4, [pc, #464] ; (0x9c68 <execvp+472>) 0x9a96 <execvp+6>: add sp, r5 (gdb) info registers r0 0x1f78028 32997416 r1 0xbeb2f8e4 3199400164 r2 0x0 0 r3 0xbeb2f8e4 3199400164 r4 0xffffff9c 4294967196 r5 0xffffefb4 4294963124 r6 0xbeb2f8ec 3199400172 r7 0xbeb2f8f8 3199400184 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0xbeb2f95c 3199400284 r12 0x0 0 sp 0xbeb2e868 0xbeb2e868 lr 0x8195 33173 pc 0x9a98 0x9a98 <execvp+8> cpsr 0x80000030 -2147483600 (gdb)
最终子进程
0x0000968c <+12>: pop {r4, r7}
sp 恢复,而父进程要等到子进程执行到execve才恢复执行,届时sp才能恢复,
而当子进程执行完vfork,执行进入execvp时,执行push指令压入几个寄存器,
0x9a90 <execvp>: push {r4, r5, r6, r7, lr}
导致父进程在vfork中压入sp的r4, r7被这里的r7,lr取代,最终子进程执行exeve后,父进程vfork从svc后继续执行
0x0000968c <+12>: pop {r4, r7}
弹出的r7已被修改为0x8195,导致后续栈帧被破坏,程序崩溃。