From 48ea2c34d1d3dead7727d9e2760b587c7609b14b Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Tue, 19 Jan 2021 15:31:49 -0800 Subject: platform/ptrace: workaround a kernel ptrace issue on ARM64 On ARM64, when ptrace stops on a system call, it uses the x7 register to indicate whether the stop has been signalled from syscall entry or syscall exit. This means that we can't get a value of this register and we can't change it. More details are in the comment for tracehook_report_syscall in arch/arm64/kernel/ptrace.c. This happens only if we stop on a system call, so let's queue a signal, resume a stub thread and catch it on a signal handling. Fixes: #5238 PiperOrigin-RevId: 352668695 --- pkg/sentry/platform/ptrace/subprocess.go | 12 ++++++--- pkg/sentry/platform/ptrace/subprocess_amd64.go | 3 +++ pkg/sentry/platform/ptrace/subprocess_arm64.go | 36 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) (limited to 'pkg/sentry') diff --git a/pkg/sentry/platform/ptrace/subprocess.go b/pkg/sentry/platform/ptrace/subprocess.go index aacd7ce70..17fb0a0d8 100644 --- a/pkg/sentry/platform/ptrace/subprocess.go +++ b/pkg/sentry/platform/ptrace/subprocess.go @@ -550,6 +550,12 @@ func (s *subprocess) switchToApp(c *context, ac arch.Context) bool { // Wait for the syscall-enter stop. sig := t.wait(stopped) + if sig == syscall.SIGSTOP { + // SIGSTOP was delivered to another thread in the same thread + // group, which initiated another group stop. Just ignore it. + continue + } + // Refresh all registers. if err := t.getRegs(regs); err != nil { panic(fmt.Sprintf("ptrace get regs failed: %v", err)) @@ -566,13 +572,11 @@ func (s *subprocess) switchToApp(c *context, ac arch.Context) bool { // Is it a system call? if sig == (syscallEvent | syscall.SIGTRAP) { + s.arm64SyscallWorkaround(t, regs) + // Ensure registers are sane. updateSyscallRegs(regs) return true - } else if sig == syscall.SIGSTOP { - // SIGSTOP was delivered to another thread in the same thread - // group, which initiated another group stop. Just ignore it. - continue } // Grab signal information. diff --git a/pkg/sentry/platform/ptrace/subprocess_amd64.go b/pkg/sentry/platform/ptrace/subprocess_amd64.go index 020bbda79..04815282b 100644 --- a/pkg/sentry/platform/ptrace/subprocess_amd64.go +++ b/pkg/sentry/platform/ptrace/subprocess_amd64.go @@ -257,3 +257,6 @@ func probeSeccomp() bool { } } } + +func (s *subprocess) arm64SyscallWorkaround(t *thread, regs *arch.Registers) { +} diff --git a/pkg/sentry/platform/ptrace/subprocess_arm64.go b/pkg/sentry/platform/ptrace/subprocess_arm64.go index bd618fae8..416132967 100644 --- a/pkg/sentry/platform/ptrace/subprocess_arm64.go +++ b/pkg/sentry/platform/ptrace/subprocess_arm64.go @@ -21,6 +21,7 @@ import ( "strings" "syscall" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/seccomp" "gvisor.dev/gvisor/pkg/sentry/arch" @@ -172,3 +173,38 @@ func appendArchSeccompRules(rules []seccomp.RuleSet, defaultAction linux.BPFActi func probeSeccomp() bool { return true } + +func (s *subprocess) arm64SyscallWorkaround(t *thread, regs *arch.Registers) { + // On ARM64, when ptrace stops on a system call, it uses the x7 + // register to indicate whether the stop has been signalled from + // syscall entry or syscall exit. This means that we can't get a value + // of this register and we can't change it. More details are in the + // comment for tracehook_report_syscall in arch/arm64/kernel/ptrace.c. + // + // This happens only if we stop on a system call, so let's queue a + // signal, resume a stub thread and catch it on a signal handling. + t.NotifyInterrupt() + for { + if _, _, errno := syscall.RawSyscall6( + syscall.SYS_PTRACE, + unix.PTRACE_SYSEMU, + uintptr(t.tid), 0, 0, 0, 0); errno != 0 { + panic(fmt.Sprintf("ptrace sysemu failed: %v", errno)) + } + + // Wait for the syscall-enter stop. + sig := t.wait(stopped) + if sig == syscall.SIGSTOP { + // SIGSTOP was delivered to another thread in the same thread + // group, which initiated another group stop. Just ignore it. + continue + } + if sig == (syscallEvent | syscall.SIGTRAP) { + t.dumpAndPanic(fmt.Sprintf("unexpected syscall event")) + } + break + } + if err := t.getRegs(regs); err != nil { + panic(fmt.Sprintf("ptrace get regs failed: %v", err)) + } +} -- cgit v1.2.3