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,导致后续栈帧被破坏,程序崩溃。