mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -257,3 +257,6 @@ func probeSeccomp() bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subprocess) arm64SyscallWorkaround(t *thread, regs *arch.Registers) {
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user