diff options
author | Andrei Vagin <avagin@google.com> | 2021-01-19 15:31:49 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-01-19 15:34:02 -0800 |
commit | 48ea2c34d1d3dead7727d9e2760b587c7609b14b (patch) | |
tree | 7b8e2c8bc3a2a1a8da7f780800e4d2aae674b943 | |
parent | be17b94446b2f96c2a3d531fe20271537c77c8aa (diff) |
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
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess.go | 12 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess_amd64.go | 3 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess_arm64.go | 36 | ||||
-rw-r--r-- | test/syscalls/BUILD | 2 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 6 | ||||
-rw-r--r-- | test/syscalls/linux/sigreturn_amd64.cc (renamed from test/syscalls/linux/sigiret.cc) | 0 | ||||
-rw-r--r-- | test/syscalls/linux/sigreturn_arm64.cc | 97 |
7 files changed, 148 insertions, 8 deletions
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)) + } +} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 0da35f7be..6ee2b73c1 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -569,7 +569,7 @@ syscall_test( # syscall_test(vfs2="True",test = "//test/syscalls/linux:sigaltstack_test") syscall_test( - test = "//test/syscalls/linux:sigiret_test", + test = "//test/syscalls/linux:sigreturn_test", ) syscall_test( diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index d184712e3..b37716c48 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -2191,11 +2191,11 @@ cc_binary( ) cc_binary( - name = "sigiret_test", + name = "sigreturn_test", testonly = 1, srcs = select_arch( - amd64 = ["sigiret.cc"], - arm64 = [], + amd64 = ["sigreturn_amd64.cc"], + arm64 = ["sigreturn_arm64.cc"], ), linkstatic = 1, deps = [ diff --git a/test/syscalls/linux/sigiret.cc b/test/syscalls/linux/sigreturn_amd64.cc index 6227774a4..6227774a4 100644 --- a/test/syscalls/linux/sigiret.cc +++ b/test/syscalls/linux/sigreturn_amd64.cc diff --git a/test/syscalls/linux/sigreturn_arm64.cc b/test/syscalls/linux/sigreturn_arm64.cc new file mode 100644 index 000000000..2c19e2984 --- /dev/null +++ b/test/syscalls/linux/sigreturn_arm64.cc @@ -0,0 +1,97 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <linux/unistd.h> +#include <signal.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/ucontext.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "test/util/logging.h" +#include "test/util/signal_util.h" +#include "test/util/test_util.h" +#include "test/util/timer_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +constexpr uint64_t kOrigX7 = 0xdeadbeeffacefeed; + +void sigvtalrm(int sig, siginfo_t* siginfo, void* _uc) { + ucontext_t* uc = reinterpret_cast<ucontext_t*>(_uc); + + // Verify that: + // - x7 value in mcontext_t matches kOrigX7. + if (uc->uc_mcontext.regs[7] == kOrigX7) { + // Modify the value x7 in the ucontext. This is the value seen by the + // application after the signal handler returns. + uc->uc_mcontext.regs[7] = ~kOrigX7; + } +} + +int testX7(uint64_t* val, uint64_t sysno, uint64_t tgid, uint64_t tid, + uint64_t signo) { + register uint64_t* x9 __asm__("x9") = val; + register uint64_t x8 __asm__("x8") = sysno; + register uint64_t x0 __asm__("x0") = tgid; + register uint64_t x1 __asm__("x1") = tid; + register uint64_t x2 __asm__("x2") = signo; + + // Initialize x7, send SIGVTALRM to itself and read x7. + __asm__( + "ldr x7, [x9, 0]\n" + "svc 0\n" + "str x7, [x9, 0]\n" + : "=r"(x0) + : "r"(x0), "r"(x1), "r"(x2), "r"(x9), "r"(x8) + : "x7"); + return x0; +} + +// 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. +// +// CheckR7 checks that the ptrace platform handles the x7 register properly. +TEST(SigreturnTest, CheckX7) { + // Setup signal handler for SIGVTALRM. + struct sigaction sa = {}; + sigfillset(&sa.sa_mask); + sa.sa_sigaction = sigvtalrm; + sa.sa_flags = SA_SIGINFO; + auto const action_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGVTALRM, sa)); + + auto const mask_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGVTALRM)); + + uint64_t x7 = kOrigX7; + + testX7(&x7, __NR_tgkill, getpid(), syscall(__NR_gettid), SIGVTALRM); + + // The following check verifies that %x7 was not clobbered + // when returning from the signal handler (via sigreturn(2)). + EXPECT_EQ(x7, ~kOrigX7); +} + +} // namespace + +} // namespace testing +} // namespace gvisor |