mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
platform/syscall: use syscall + int3 to execute a system call in a stub process
Right now, we need to call ptrace(PTRACE_SYSCALL) and wait() twice to execute one system call in a stub process. With these changes, we will need to call ptrace + wait only once. In addition, this allows to workaround the kernel bug when a stub process doesn't stop on syscall-exit-stop and starts executing the next system call. Reported-by: syzbot+37143cafa8dc3b5008ee@syzkaller.appspotmail.com PiperOrigin-RevId: 288393029
This commit is contained in:
@@ -64,6 +64,8 @@ begin:
|
||||
CMPQ AX, $0
|
||||
JL error
|
||||
|
||||
MOVQ $0, BX
|
||||
|
||||
// SIGSTOP to wait for attach.
|
||||
//
|
||||
// The SYSCALL instruction will be used for future syscall injection by
|
||||
@@ -73,23 +75,26 @@ begin:
|
||||
MOVQ $SIGSTOP, SI
|
||||
SYSCALL
|
||||
|
||||
// The tracer may "detach" and/or allow code execution here in three cases:
|
||||
//
|
||||
// 1. New (traced) stub threads are explicitly detached by the
|
||||
// goroutine in newSubprocess. However, they are detached while in
|
||||
// group-stop, so they do not execute code here.
|
||||
//
|
||||
// 2. If a tracer thread exits, it implicitly detaches from the stub,
|
||||
// potentially allowing code execution here. However, the Go runtime
|
||||
// never exits individual threads, so this case never occurs.
|
||||
//
|
||||
// 3. subprocess.createStub clones a new stub process that is untraced,
|
||||
// The sentry sets BX to 1 when creating stub process.
|
||||
CMPQ BX, $1
|
||||
JE clone
|
||||
|
||||
// Notify the Sentry that syscall exited.
|
||||
done:
|
||||
INT $3
|
||||
// Be paranoid.
|
||||
JMP done
|
||||
clone:
|
||||
// subprocess.createStub clones a new stub process that is untraced,
|
||||
// thus executing this code. We setup the PDEATHSIG before SIGSTOPing
|
||||
// ourselves for attach by the tracer.
|
||||
//
|
||||
// R15 has been updated with the expected PPID.
|
||||
JMP begin
|
||||
CMPQ AX, $0
|
||||
JE begin
|
||||
|
||||
// The clone syscall returns a non-zero value.
|
||||
JMP done
|
||||
error:
|
||||
// Exit with -errno.
|
||||
MOVQ AX, DI
|
||||
|
||||
@@ -59,6 +59,8 @@ begin:
|
||||
CMP $0x0, R0
|
||||
BLT error
|
||||
|
||||
MOVD $0, R9
|
||||
|
||||
// SIGSTOP to wait for attach.
|
||||
//
|
||||
// The SYSCALL instruction will be used for future syscall injection by
|
||||
@@ -66,22 +68,26 @@ begin:
|
||||
MOVD $SYS_KILL, R8
|
||||
MOVD $SIGSTOP, R1
|
||||
SVC
|
||||
// The tracer may "detach" and/or allow code execution here in three cases:
|
||||
//
|
||||
// 1. New (traced) stub threads are explicitly detached by the
|
||||
// goroutine in newSubprocess. However, they are detached while in
|
||||
// group-stop, so they do not execute code here.
|
||||
//
|
||||
// 2. If a tracer thread exits, it implicitly detaches from the stub,
|
||||
// potentially allowing code execution here. However, the Go runtime
|
||||
// never exits individual threads, so this case never occurs.
|
||||
//
|
||||
// 3. subprocess.createStub clones a new stub process that is untraced,
|
||||
|
||||
// The sentry sets R9 to 1 when creating stub process.
|
||||
CMP $1, R9
|
||||
BEQ clone
|
||||
|
||||
done:
|
||||
// Notify the Sentry that syscall exited.
|
||||
BRK $3
|
||||
B done // Be paranoid.
|
||||
clone:
|
||||
// subprocess.createStub clones a new stub process that is untraced,
|
||||
// thus executing this code. We setup the PDEATHSIG before SIGSTOPing
|
||||
// ourselves for attach by the tracer.
|
||||
//
|
||||
// R7 has been updated with the expected PPID.
|
||||
B begin
|
||||
CMP $0, R0
|
||||
BEQ begin
|
||||
|
||||
// The clone system call returned a non-zero value.
|
||||
B done
|
||||
|
||||
error:
|
||||
// Exit with -errno.
|
||||
|
||||
@@ -430,13 +430,15 @@ func (t *thread) syscall(regs *syscall.PtraceRegs) (uintptr, error) {
|
||||
}
|
||||
|
||||
for {
|
||||
// Execute the syscall instruction.
|
||||
if _, _, errno := syscall.RawSyscall6(syscall.SYS_PTRACE, syscall.PTRACE_SYSCALL, uintptr(t.tid), 0, 0, 0, 0); errno != 0 {
|
||||
// Execute the syscall instruction. The task has to stop on the
|
||||
// trap instruction which is right after the syscall
|
||||
// instruction.
|
||||
if _, _, errno := syscall.RawSyscall6(syscall.SYS_PTRACE, syscall.PTRACE_CONT, uintptr(t.tid), 0, 0, 0, 0); errno != 0 {
|
||||
panic(fmt.Sprintf("ptrace syscall-enter failed: %v", errno))
|
||||
}
|
||||
|
||||
sig := t.wait(stopped)
|
||||
if sig == (syscallEvent | syscall.SIGTRAP) {
|
||||
if sig == syscall.SIGTRAP {
|
||||
// Reached syscall-enter-stop.
|
||||
break
|
||||
} else {
|
||||
@@ -448,18 +450,6 @@ func (t *thread) syscall(regs *syscall.PtraceRegs) (uintptr, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the actual system call.
|
||||
if _, _, errno := syscall.RawSyscall6(syscall.SYS_PTRACE, syscall.PTRACE_SYSCALL, uintptr(t.tid), 0, 0, 0, 0); errno != 0 {
|
||||
panic(fmt.Sprintf("ptrace syscall-enter failed: %v", errno))
|
||||
}
|
||||
|
||||
// Wait for syscall-exit-stop. "[Signal-delivery-stop] never happens
|
||||
// between syscall-enter-stop and syscall-exit-stop; it happens *after*
|
||||
// syscall-exit-stop.)" - ptrace(2), "Syscall-stops"
|
||||
if sig := t.wait(stopped); sig != (syscallEvent | syscall.SIGTRAP) {
|
||||
t.dumpAndPanic(fmt.Sprintf("wait failed: expected SIGTRAP, got %v [%d]", sig, sig))
|
||||
}
|
||||
|
||||
// Grab registers.
|
||||
if err := t.getRegs(regs); err != nil {
|
||||
panic(fmt.Sprintf("ptrace get regs failed: %v", err))
|
||||
|
||||
@@ -141,9 +141,11 @@ func (t *thread) adjustInitRegsRip() {
|
||||
t.initRegs.Rip -= initRegsRipAdjustment
|
||||
}
|
||||
|
||||
// Pass the expected PPID to the child via R15 when creating stub process
|
||||
// Pass the expected PPID to the child via R15 when creating stub process.
|
||||
func initChildProcessPPID(initregs *syscall.PtraceRegs, ppid int32) {
|
||||
initregs.R15 = uint64(ppid)
|
||||
// Rbx has to be set to 1 when creating stub process.
|
||||
initregs.Rbx = 1
|
||||
}
|
||||
|
||||
// patchSignalInfo patches the signal info to account for hitting the seccomp
|
||||
|
||||
@@ -127,6 +127,8 @@ func (t *thread) adjustInitRegsRip() {
|
||||
// Pass the expected PPID to the child via X7 when creating stub process
|
||||
func initChildProcessPPID(initregs *syscall.PtraceRegs, ppid int32) {
|
||||
initregs.Regs[7] = uint64(ppid)
|
||||
// R9 has to be set to 1 when creating stub process.
|
||||
initregs.Regs[9] = 1
|
||||
}
|
||||
|
||||
// patchSignalInfo patches the signal info to account for hitting the seccomp
|
||||
|
||||
Reference in New Issue
Block a user