summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrei Vagin <avagin@google.com>2021-01-19 15:31:49 -0800
committergVisor bot <gvisor-bot@google.com>2021-01-19 15:34:02 -0800
commit48ea2c34d1d3dead7727d9e2760b587c7609b14b (patch)
tree7b8e2c8bc3a2a1a8da7f780800e4d2aae674b943
parentbe17b94446b2f96c2a3d531fe20271537c77c8aa (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.go12
-rw-r--r--pkg/sentry/platform/ptrace/subprocess_amd64.go3
-rw-r--r--pkg/sentry/platform/ptrace/subprocess_arm64.go36
-rw-r--r--test/syscalls/BUILD2
-rw-r--r--test/syscalls/linux/BUILD6
-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.cc97
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