Open-sourcing the systrap platform.

The systrap platform like the ptrace platform uses stub processes to manage
the user address space. The difference is how they intercept system calls and
other events like memory faults, exceptions, etc.

In case of systrap, all events that have to be handled by the Sentry trigger
signals that are handled by a custom signal handler installed on stub
processes. The signal handler switches control to the Sentry.

Here are a few other optimizations:
* On x86, system calls can be replaced with a function call to remove overhead
  of signals.
* For fast interactions of sentry and stub processes, futex wait/wake can
  be a bottle neck, so we use a polling mode.

The platform is launched for the purpose of testing and gathering initial
feedback. It is not yet ready for use in production.

PiperOrigin-RevId: 511650064
This commit is contained in:
Andrei Vagin
2023-02-22 18:22:49 -08:00
committed by gVisor bot
parent be86ca7d22
commit 192bfb03fb
62 changed files with 6646 additions and 0 deletions
+8
View File
@@ -314,6 +314,14 @@ steps:
agents:
<<: *kvm_agents
arch: "amd64"
- <<: *common
<<: *docker
<<: *source_test
label: ":rocket: Systrap tests"
command: make systrap-tests
agents:
<<: *kvm_agents
arch: "amd64"
- <<: *common
<<: *docker
label: ":weight_lifter: Fsstress test"
+5
View File
@@ -298,6 +298,11 @@ kvm-tests: load-basic $(RUNTIME_BIN)
@$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS))
.PHONY: kvm-tests
systrap-tests: load-basic $(RUNTIME_BIN)
@$(call install_runtime,$(RUNTIME),--platform=systrap)
@$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS))
.PHONY: systrap-tests
iptables-tests: load-iptables $(RUNTIME_BIN)
@sudo modprobe iptable_filter
@sudo modprobe ip6table_filter
+3
View File
@@ -192,6 +192,9 @@ analyzers:
- pkg/sentry/fsutil/host_file_mapper_unsafe.go # Special case.
- pkg/sentry/platform/kvm/bluepill_unsafe.go # Special case.
- pkg/sentry/platform/kvm/machine_unsafe.go # Special case.
- pkg/sentry/platform/systrap/stub_unsafe.go # Special case.
- pkg/sentry/platform/systrap/syscall_thread_unsafe.go # Special case.
- pkg/sentry/platform/systrap/sysmsg_thread_unsafe.go # Special case.
- pkg/sentry/platform/safecopy/safecopy_unsafe.go # Special case.
- pkg/sentry/usage/memory_unsafe.go # Special case.
- pkg/sentry/vfs/mount_unsafe.go # Special case.
+6
View File
@@ -168,6 +168,12 @@ func (app *runApp) execute(t *Task) taskRunState {
// a pending signal, causing another interruption, but that signal should
// not interact with the interrupted syscall.)
if t.haveSyscallReturn {
if err := t.p.PullFullState(t.MemoryManager().AddressSpace(), t.Arch()); err != nil {
t.Warningf("Unable to pull a full state: %v", err)
t.PrepareExit(linux.WaitStatusExit(int32(ExtractErrno(err, -1))))
return (*runExit)(nil)
}
if sre, ok := linuxerr.SyscallRestartErrorFromReturn(t.Arch().Return()); ok {
if sre == linuxerr.ERESTART_RESTARTBLOCK {
t.Debugf("Restarting syscall %d with restart block: not interrupted by handled signal", t.Arch().SyscallNo())
+103
View File
@@ -0,0 +1,103 @@
load("//tools:arch.bzl", "arch_genrule", "select_arch")
load("//tools:defs.bzl", "go_library")
load("//tools/nogo:defs.bzl", "nogo_facts")
load("//tools/go_generics:defs.bzl", "go_template_instance")
package(licenses = ["notice"])
nogo_facts(
name = "stub_impl",
srcs = [
"stub_defs.go",
"syscall_thread_defs.go",
],
output = "stub_impl.s",
template = select_arch(
amd64 = "stub_amd64.s",
arm64 = "stub_arm64.s",
),
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/hostarch",
"@org_golang_x_sys//unix:go_default_library",
],
)
arch_genrule(
name = "stub_impl_arch",
src = ":stub_impl",
template = "stub_impl_%s.s",
)
go_template_instance(
name = "subprocess_list",
out = "subprocess_list.go",
package = "systrap",
prefix = "subprocess",
template = "//pkg/ilist:generic_list",
types = {
"Linker": "*subprocess",
"Element": "*subprocess",
},
)
go_library(
name = "systrap",
srcs = [
"filters.go",
"filters_amd64.go",
"filters_arm64.go",
"lib_amd64.s",
"lib_arm64.s",
"stub_defs.go",
"stub_unsafe.go",
"subprocess.go",
"subprocess_amd64.go",
"subprocess_amd64_unsafe.go",
"subprocess_arm64.go",
"subprocess_arm64_unsafe.go",
"subprocess_linux.go",
"subprocess_linux_unsafe.go",
"subprocess_list.go",
"subprocess_pool.go",
"subprocess_unsafe.go",
"syscall_thread.go",
"syscall_thread_amd64.go",
"syscall_thread_arm64.go",
"syscall_thread_defs.go",
"syscall_thread_unsafe.go",
"sysmsg_thread.go",
"sysmsg_thread_amd64.go",
"sysmsg_thread_arm64.go",
"sysmsg_thread_unsafe.go",
"systrap.go",
"systrap_amd64.go",
"systrap_arm64.go",
"systrap_arm64_unsafe.go",
"systrap_unsafe.go",
":stub_impl_arch",
],
visibility = ["//:sandbox"],
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/context",
"//pkg/cpuid",
"//pkg/hostarch",
"//pkg/log",
"//pkg/memutil",
"//pkg/pool",
"//pkg/safecopy",
"//pkg/seccomp",
"//pkg/sentry/arch",
"//pkg/sentry/memmap",
"//pkg/sentry/pgalloc",
"//pkg/sentry/platform",
"//pkg/sentry/platform/interrupt",
"//pkg/sentry/platform/systrap/sysmsg",
"//pkg/sentry/platform/systrap/usertrap",
"//pkg/sentry/usage",
"@org_golang_x_sys//unix:go_default_library",
],
)
+33
View File
@@ -0,0 +1,33 @@
# The systrap platform
This platform is similar with the ptrace platform with the difference how system
calls, page-faults and other exceptions handled.
The kernel allows setting seccomp filters (SECCOMP_RET_TRAP), so that each time
when a thread tries to call a filtered system call, it will receive the SIGSYS
signal.
With this kernel feature, all stub thread events what have to be handled in the
sentry triggers signals. This means that they can be handled from a signal
handler.
The systrap platform includes the sysmsg module which implements a stub signal
handler and a protocol of communications of stub threads and the Sentry.
The initializations of a new stub thread includes next steps:
* installing seccomp filters to trap all user system calls.
* setting an alternate signal stack which is shared with the Sentry.
* setting the sysmsg signal handler for SIGSYS, SIGSEGV, SIGBUS, SIGFPE,
SIGTRAP, SIGILL.
User code is executed in context of a stub thread. When it calls a system call
or triggers page-fault, the signal handler is started. It notifies the Sentry
about a new signal, then the Sentry handles this event and notifies the system
thread back that it can continue running.
When the kernel prepares to execute the signal handler, it generates a signal
frame which contains a process state (registers, FPU state, etc). Then when the
kernel resumes a process, the process state is restored from this frame. The
signal frame is saved on a signal handler stack which is shared with the Sentry.
This allows us to read and modify the thread state from the Sentry.
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2019 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.
package systrap
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/seccomp"
)
// SyscallFilters returns syscalls made exclusively by the systrap platform.
func (p *Systrap) SyscallFilters() seccomp.SyscallRules {
r := seccomp.SyscallRules{
unix.SYS_PTRACE: {
{
seccomp.EqualTo(unix.PTRACE_ATTACH),
},
{
seccomp.EqualTo(unix.PTRACE_CONT),
seccomp.MatchAny{},
seccomp.EqualTo(0),
seccomp.EqualTo(0),
},
{
seccomp.EqualTo(unix.PTRACE_GETEVENTMSG),
},
{
seccomp.EqualTo(unix.PTRACE_GETREGSET),
seccomp.MatchAny{},
seccomp.EqualTo(linux.NT_PRSTATUS),
},
{
seccomp.EqualTo(unix.PTRACE_GETSIGINFO),
},
{
seccomp.EqualTo(unix.PTRACE_SETOPTIONS),
seccomp.MatchAny{},
seccomp.EqualTo(0),
seccomp.EqualTo(unix.PTRACE_O_TRACESYSGOOD | unix.PTRACE_O_TRACEEXIT | unix.PTRACE_O_EXITKILL),
},
{
seccomp.EqualTo(unix.PTRACE_SETREGSET),
seccomp.MatchAny{},
seccomp.EqualTo(linux.NT_PRSTATUS),
},
{
seccomp.EqualTo(linux.PTRACE_SETSIGMASK),
seccomp.MatchAny{},
seccomp.EqualTo(8),
},
{
seccomp.EqualTo(unix.PTRACE_SYSEMU),
seccomp.MatchAny{},
seccomp.EqualTo(0),
seccomp.EqualTo(0),
},
{
seccomp.EqualTo(unix.PTRACE_DETACH),
},
},
unix.SYS_TGKILL: {},
unix.SYS_WAIT4: {},
}
r.Merge(p.archSyscallFilters())
return r
}
@@ -0,0 +1,27 @@
// Copyright 2023 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.
//go:build amd64
// +build amd64
package systrap
import (
"gvisor.dev/gvisor/pkg/seccomp"
)
// SyscallFilters returns syscalls made exclusively by the systrap platform.
func (*Systrap) archSyscallFilters() seccomp.SyscallRules {
return seccomp.SyscallRules{}
}
@@ -0,0 +1,42 @@
// Copyright 2023 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.
//go:build arm64
// +build arm64
package systrap
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/seccomp"
)
// SyscallFilters returns syscalls made exclusively by the systrap platform.
func (*Systrap) archSyscallFilters() seccomp.SyscallRules {
return seccomp.SyscallRules{
unix.SYS_PTRACE: {
{
seccomp.EqualTo(unix.PTRACE_GETREGSET),
seccomp.MatchAny{},
seccomp.EqualTo(linux.NT_ARM_TLS),
},
{
seccomp.EqualTo(unix.PTRACE_SETREGSET),
seccomp.MatchAny{},
seccomp.EqualTo(linux.NT_ARM_TLS),
},
},
}
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2022 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 "funcdata.h"
#include "textflag.h"
TEXT ·spinloop(SB),NOSPLIT,$0
PAUSE
RET
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2022 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 "funcdata.h"
#include "textflag.h"
TEXT ·spinloop(SB),NOSPLIT,$0
YIELD
RET
+211
View File
@@ -0,0 +1,211 @@
// Copyright 2018 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 "funcdata.h"
#include "textflag.h"
#define SYS_GETPID {{ .import.unix.Constants.SYS_GETPID }}
#define SYS_EXIT {{ .import.unix.Constants.SYS_EXIT }}
#define SYS_KILL {{ .import.unix.Constants.SYS_KILL }}
#define SYS_GETPPID {{ .import.unix.Constants.SYS_GETPPID }}
#define SIGKILL {{ .import.unix.Constants.SIGKILL }}
#define SIGSTOP {{ .import.unix.Constants.SIGSTOP }}
#define SYS_PRCTL {{ .import.unix.Constants.SYS_PRCTL }}
#define PR_SET_PDEATHSIG {{ .import.unix.Constants.PR_SET_PDEATHSIG }}
#define SYS_FUTEX {{ .import.unix.Constants.SYS_FUTEX }}
#define FUTEX_WAKE {{ .import.linux.Constants.FUTEX_WAKE }}
#define FUTEX_WAIT {{ .import.linux.Constants.FUTEX_WAIT }}
#define NEW_STUB {{ .Constants._NEW_STUB }}
#define RUN_SYSCALL_LOOP {{ .Constants._RUN_SYSCALL_LOOP }}
// syscallSentryMessage offsets.
#define SENTRY_MESSAGE_STATE {{ .syscallSentryMessage.state.Offset }}
#define SENTRY_MESSAGE_SYSNO {{ .syscallSentryMessage.sysno.Offset }}
#define SENTRY_MESSAGE_ARG0 ({{ .syscallSentryMessage.args.Offset }} + 0*8)
#define SENTRY_MESSAGE_ARG1 ({{ .syscallSentryMessage.args.Offset }} + 1*8)
#define SENTRY_MESSAGE_ARG2 ({{ .syscallSentryMessage.args.Offset }} + 2*8)
#define SENTRY_MESSAGE_ARG3 ({{ .syscallSentryMessage.args.Offset }} + 3*8)
#define SENTRY_MESSAGE_ARG4 ({{ .syscallSentryMessage.args.Offset }} + 4*8)
#define SENTRY_MESSAGE_ARG5 ({{ .syscallSentryMessage.args.Offset }} + 5*8)
// syscallStubMessage offsets.
#define STUB_MESSAGE_OFFSET {{ .Constants.syscallStubMessageOffset }}
#define STUB_MESSAGE_RET {{ .syscallStubMessage.ret.Offset }}
// initStubProcess bootstraps the child and sends itself SIGSTOP to wait for attach.
//
// R15 contains the expected PPID. R15 is used instead of a more typical DI
// since syscalls will clobber DI and createStub wants to pass a new PPID to
// grandchildren.
//
// This should not be used outside the context of a new ptrace child (as the
// function is otherwise a bunch of nonsense).
TEXT ·initStubProcess(SB),NOSPLIT,$0
begin:
// N.B. This loop only executes in the context of a single-threaded
// fork child.
MOVQ $SYS_PRCTL, AX
MOVQ $PR_SET_PDEATHSIG, DI
MOVQ $SIGKILL, SI
SYSCALL
CMPQ AX, $0
JNE error
// If the parent already died before we called PR_SET_DEATHSIG then
// we'll have an unexpected PPID.
MOVQ $SYS_GETPPID, AX
SYSCALL
CMPQ AX, $0
JL error
CMPQ AX, R15
JNE parent_dead
MOVQ $SYS_GETPID, AX
SYSCALL
CMPQ AX, $0
JL error
MOVQ $0, BX
// SIGSTOP to wait for attach.
//
// The SYSCALL instruction will be used for future syscall injection by
// thread.syscall.
MOVQ AX, DI
MOVQ $SYS_KILL, AX
MOVQ $SIGSTOP, SI
SYSCALL
// The sentry sets BX to $NEW_STUB when creating stub process.
CMPQ BX, $NEW_STUB
JE clone
// The sentry sets BX to $RUN_SYSCALL_LOOP when requesting a syscall
// thread.
CMPQ BX, $RUN_SYSCALL_LOOP
JE syscall_loop
// 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.
CMPQ AX, $0
JE begin
// The clone syscall returns a non-zero value.
JMP done
error:
// Exit with -errno.
MOVQ AX, DI
NEGQ DI
MOVQ $SYS_EXIT, AX
SYSCALL
HLT
parent_dead:
MOVQ $SYS_EXIT, AX
MOVQ $1, DI
SYSCALL
HLT
// syscall_loop handles requests from the Sentry to execute syscalls.
// Look at syscall_thread for more details.
//
// syscall_loop is running without using the stack because it can be
// compromised by sysmsg (guest) threads that run in the same address
// space.
syscall_loop:
// while (sentryMessage->state != R13) {
// futex(sentryMessage->state, FUTEX_WAIT, 0, NULL, NULL, 0);
// }
MOVQ R12, DI
MOVQ $FUTEX_WAIT, SI
MOVQ $0, R10
MOVQ $0, R8
MOVQ $0, R9
wait_for_syscall:
MOVL SENTRY_MESSAGE_STATE(DI), DX
CMPL DX, R13
JE execute_syscall
MOVQ $SYS_FUTEX, AX
SYSCALL
JMP wait_for_syscall
execute_syscall:
// ret = syscall(sysno, args...)
MOVQ SENTRY_MESSAGE_SYSNO(R12), AX
MOVQ SENTRY_MESSAGE_ARG0(R12), DI
MOVQ SENTRY_MESSAGE_ARG1(R12), SI
MOVQ SENTRY_MESSAGE_ARG2(R12), DX
MOVQ SENTRY_MESSAGE_ARG3(R12), R10
MOVQ SENTRY_MESSAGE_ARG4(R12), R8
MOVQ SENTRY_MESSAGE_ARG5(R12), R9
SYSCALL
// stubMessage->ret = ret
MOVQ AX, (STUB_MESSAGE_OFFSET + STUB_MESSAGE_RET)(R12)
// for {
// if futex(sentryMessage->state, FUTEX_WAKE, 1) == 1 {
// break;
// }
// }
MOVQ R12, DI
MOVQ $FUTEX_WAKE, SI
MOVQ $1, DX
MOVQ $0, R10
MOVQ $0, R8
MOVQ $0, R9
wake_up_sentry:
MOVQ $SYS_FUTEX, AX
SYSCALL
// futex returns the number of waiters that were woken up. If futex
// returns 0 here, it means that the Sentry has not called futex_wait
// yet and we need to try again. The value of sentryMessage->state
// isn't changed, so futex_wake is the only way to wake up the Sentry.
CMPQ AX, $1
JNE wake_up_sentry
INCL R13
JMP syscall_loop
// func addrOfInitStubProcess() uintptr
TEXT ·addrOfInitStubProcess(SB), $0-8
MOVQ $·initStubProcess(SB), AX
MOVQ AX, ret+0(FP)
RET
// stubCall calls the stub function at the given address with the given PPID.
//
// This is a distinct function because stub, above, may be mapped at any
// arbitrary location, and stub has a specific binary API (see above).
TEXT ·stubCall(SB),NOSPLIT,$0-16
MOVQ addr+0(FP), AX
MOVQ pid+8(FP), R15
JMP AX
+205
View File
@@ -0,0 +1,205 @@
// Copyright 2019 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 "funcdata.h"
#include "textflag.h"
#define SYS_GETPID {{ .import.unix.Constants.SYS_GETPID }}
#define SYS_EXIT {{ .import.unix.Constants.SYS_EXIT }}
#define SYS_KILL {{ .import.unix.Constants.SYS_KILL }}
#define SYS_GETPPID {{ .import.unix.Constants.SYS_GETPPID }}
#define SIGKILL {{ .import.unix.Constants.SIGKILL }}
#define SIGSTOP {{ .import.unix.Constants.SIGSTOP }}
#define SYS_PRCTL {{ .import.unix.Constants.SYS_PRCTL }}
#define PR_SET_PDEATHSIG {{ .import.unix.Constants.PR_SET_PDEATHSIG }}
#define SYS_FUTEX {{ .import.unix.Constants.SYS_FUTEX }}
#define FUTEX_WAKE {{ .import.linux.Constants.FUTEX_WAKE }}
#define FUTEX_WAIT {{ .import.linux.Constants.FUTEX_WAIT }}
#define NEW_STUB {{ .Constants._NEW_STUB }}
#define RUN_SYSCALL_LOOP {{ .Constants._RUN_SYSCALL_LOOP }}
// syscallSentryMessage offsets.
#define SENTRY_MESSAGE_STATE {{ .syscallSentryMessage.state.Offset }}
#define SENTRY_MESSAGE_SYSNO {{ .syscallSentryMessage.sysno.Offset }}
#define SENTRY_MESSAGE_ARG0 ({{ .syscallSentryMessage.args.Offset }} + 0*8)
#define SENTRY_MESSAGE_ARG1 ({{ .syscallSentryMessage.args.Offset }} + 1*8)
#define SENTRY_MESSAGE_ARG2 ({{ .syscallSentryMessage.args.Offset }} + 2*8)
#define SENTRY_MESSAGE_ARG3 ({{ .syscallSentryMessage.args.Offset }} + 3*8)
#define SENTRY_MESSAGE_ARG4 ({{ .syscallSentryMessage.args.Offset }} + 4*8)
#define SENTRY_MESSAGE_ARG5 ({{ .syscallSentryMessage.args.Offset }} + 5*8)
// syscallStubMessage offsets.
#define STUB_MESSAGE_OFFSET {{ .Constants.syscallStubMessageOffset }}
#define STUB_MESSAGE_RET {{ .syscallStubMessage.ret.Offset }}
// initStubProcess bootstraps the child and sends itself SIGSTOP to wait for attach.
//
// R7 contains the expected PPID.
//
// This should not be used outside the context of a new ptrace child (as the
// function is otherwise a bunch of nonsense).
TEXT ·initStubProcess(SB),NOSPLIT,$0
begin:
// N.B. This loop only executes in the context of a single-threaded
// fork child.
MOVD $SYS_PRCTL, R8
MOVD $PR_SET_PDEATHSIG, R0
MOVD $SIGKILL, R1
SVC
CMN $4095, R0
BCS error
// If the parent already died before we called PR_SET_DEATHSIG then
// we'll have an unexpected PPID.
MOVD $SYS_GETPPID, R8
SVC
CMP R0, R7
BNE parent_dead
MOVD $SYS_GETPID, R8
SVC
CMP $0x0, R0
BLT error
MOVD $0, R9
// SIGSTOP to wait for attach.
//
// The SYSCALL instruction will be used for future syscall injection by
// thread.syscall.
MOVD $SYS_KILL, R8
MOVD $SIGSTOP, R1
SVC
// The sentry sets R9 to $NEW_STUB when creating stub process.
CMP $NEW_STUB, R9
BEQ clone
// The sentry sets R9 to $RUN_SYSCALL_LOOP when creating a new syscall
// thread.
CMP $RUN_SYSCALL_LOOP, R9
BEQ syscall_loop
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.
CMP $0, R0
BEQ begin
// The clone system call returned a non-zero value.
B done
error:
// Exit with -errno.
NEG R0, R0
MOVD $SYS_EXIT, R8
SVC
HLT
parent_dead:
MOVD $SYS_EXIT, R8
MOVD $1, R0
SVC
HLT
// syscall_loop handles requests from the Sentry to execute syscalls.
// Look at syscall_thread for more details.
//
// syscall_loop is running without using the stack because it can be
// compromised by sysmsg (guest) threads that run in the same address
// space.
syscall_loop:
// while (sentryMessage->state != R13) {
// futex(sentryMessage->state, FUTEX_WAIT, 0, NULL, NULL, 0);
// }
MOVD R12, R0
MOVD $FUTEX_WAIT, R1
MOVD $0, R3
MOVD $0, R4
MOVD $0, R5
wait_for_syscall:
// Move the sentry message state to R2.
MOVW SENTRY_MESSAGE_STATE(R12), R2
CMPW R2, R13
BEQ execute_syscall
MOVD $SYS_FUTEX, R8
SVC
JMP wait_for_syscall
execute_syscall:
MOVD SENTRY_MESSAGE_SYSNO(R12), R8
MOVD SENTRY_MESSAGE_ARG0(R12), R0
MOVD SENTRY_MESSAGE_ARG1(R12), R1
MOVD SENTRY_MESSAGE_ARG2(R12), R2
MOVD SENTRY_MESSAGE_ARG3(R12), R3
MOVD SENTRY_MESSAGE_ARG4(R12), R4
MOVD SENTRY_MESSAGE_ARG5(R12), R5
SVC
// stubMessage->ret = ret
MOVD R0, (STUB_MESSAGE_OFFSET + STUB_MESSAGE_RET)(R12)
// for {
// if futex(sentryMessage->state, FUTEX_WAKE, 1) == 1 {
// break;
// }
// }
MOVD $FUTEX_WAKE, R1
MOVD $1, R2
MOVD $0, R3
MOVD $0, R4
MOVD $0, R5
MOVD $SYS_FUTEX, R8
wake_up_sentry:
MOVD R12, R0
SVC
// futex returns the number of waiters that were woken up. If futex
// returns 0 here, it means that the Sentry has not called futex_wait
// yet and we need to try again. The value of sentryMessage->state
// isn't changed, so futex_wake is the only way to wake up the Sentry.
CMP $1, R0
BNE wake_up_sentry
ADDW $1, R13, R13
JMP syscall_loop
// func addrOfInitStubProcess() uintptr
TEXT ·addrOfInitStubProcess(SB), $0-8
MOVD $·initStubProcess(SB), R0
MOVD R0, ret+0(FP)
RET
// stubCall calls the stub function at the given address with the given PPID.
//
// This is a distinct function because stub, above, may be mapped at any
// arbitrary location, and stub has a specific binary API (see above).
TEXT ·stubCall(SB),NOSPLIT,$0-16
MOVD addr+0(FP), R0
MOVD pid+8(FP), R7
B (R0)
+27
View File
@@ -0,0 +1,27 @@
// 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.
package systrap
import (
// Required for fact extraction.
_ "golang.org/x/sys/unix"
_ "gvisor.dev/gvisor/pkg/abi/linux"
)
// _NEW_STUB is the value of the BX register when a new stub thread is created.
const _NEW_STUB = 1
// _NEW_STUB is the value of the BX register when the syscall loop is executed.
const _RUN_SYSCALL_LOOP = 5
+219
View File
@@ -0,0 +1,219 @@
// Copyright 2018 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.
package systrap
import (
"math/rand"
"reflect"
"unsafe"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/safecopy"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
// initStubProcess is defined in arch-specific assembly.
func initStubProcess()
// addrOfInitStubProcess returns the start address of initStubProcess.
//
// In Go 1.17+, Go references to assembly functions resolve to an ABIInternal
// wrapper function rather than the function itself. We must reference from
// assembly to get the ABI0 (i.e., primary) address.
func addrOfInitStubProcess() uintptr
// stubCall calls the stub at the given address with the given pid.
func stubCall(addr, pid uintptr)
// unsafeSlice returns a slice for the given address and length.
func unsafeSlice(addr uintptr, length int) (slice []byte) {
sh := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
sh.Data = addr
sh.Len = length
sh.Cap = length
return
}
// prepareSeccompRules compiles stub process seccomp filters and fill
// the sock_fprog structure. So the stub process will only need to call
// seccomp system call to apply these filters.
//
//go:nosplit
func prepareSeccompRules(stubSysmsgStart, stubSysmsgRules, stubSysmsgRulesLen uintptr) {
instrs := sysmsgThreadRules(stubSysmsgStart)
progLen := len(instrs) * int(unsafe.Sizeof(linux.BPFInstruction{}))
progPtr := stubSysmsgRules + unsafe.Sizeof(linux.SockFprog{})
if progLen+int(unsafe.Sizeof(linux.SockFprog{})) > int(stubSysmsgRulesLen) {
panic("not enough space for sysmsg seccomp rules")
}
var targetSlice []linux.BPFInstruction
sh := (*reflect.SliceHeader)(unsafe.Pointer(&targetSlice))
sh.Data = progPtr
sh.Cap = len(instrs)
sh.Len = sh.Cap
copy(targetSlice, instrs)
// stubSysmsgRules and progPtr are addresses from a stub mapping which
// is mapped once and never moved, so it is safe to use unsafe.Pointer
// this way for them.
sockProg := (*linux.SockFprog)(unsafe.Pointer(stubSysmsgRules))
sockProg.Len = uint16(len(instrs))
sockProg.Filter = (*linux.BPFInstruction)(unsafe.Pointer(progPtr))
// Make the seccomp rules stub read-only.
if _, _, errno := unix.RawSyscall(
unix.SYS_MPROTECT,
stubSysmsgRules,
stubSysmsgRulesLen,
unix.PROT_READ); errno != 0 {
panic("mprotect failed: " + errno.Error())
}
}
// stubInit allocates and initializes the stub memory region which includes:
// - the stub code to do initial initialization of a stub process.
// - the sysmsg signal handler code to notify sentry about new events such as
// system calls, memory faults, etc.
// - precompiled seccomp rules to trap application system calls.
// - reserved space for stub-thread stack regions.
func stubInit() {
// *--------stubStart-------------------*
// |--------stubInitProcess-------------|
// | stub code to init stub processes |
// |--------stubSysmsgStart-------------|
// | sysmsg code |
// |--------stubSysmsgRuleStart---------|
// | precompiled sysmsg seccomp rules |
// |--------guard page------------------|
// |--------random gap------------------|
// | |
// |--------stubSysmsgStack-------------|
// | Reserved space for per-thread |
// | sysmsg stacks. |
// *------------------------------------*
pageMask := uintptr(hostarch.PageSize - 1)
// Grab the existing stub.
procStubBegin := addrOfInitStubProcess()
procStubLen := int(safecopy.FindEndAddress(procStubBegin) - procStubBegin)
procStubSlice := unsafeSlice(procStubBegin, procStubLen)
mapLen := (uintptr(procStubLen) + pageMask) & ^pageMask
stubSysmsgStart = mapLen
stubSysmsgLen := len(sysmsg.SighandlerBlob)
mapLen += (uintptr(stubSysmsgLen) + pageMask) & ^pageMask
stubSysmsgRules = mapLen
stubSysmsgRulesLen = hostarch.PageSize * 4
mapLen += stubSysmsgRulesLen
stubROMapEnd = mapLen
// Add a guard page.
mapLen += hostarch.PageSize
stubSysmsgStack = mapLen
// Allocate maxGuestThreads plus ONE because each per-thread stack
// has to be aligned to sysmsg.PerThreadMemSize.
// Look at sysmsg/sighandler.c:sysmsg_addr() for more details.
mapLen += sysmsg.PerThreadMemSize * (maxGuestThreads + 1)
// Randomize stubStart address.
randomOffset := uintptr(rand.Uint64() * hostarch.PageSize)
maxRandomOffset := maxRandomOffsetOfStubAddress - mapLen
stubStart = uintptr(0)
for offset := uintptr(0); offset < maxRandomOffset; offset += hostarch.PageSize {
stubStart = maxStubUserAddress + (randomOffset+offset)%maxRandomOffset
// Map the target address for the stub.
//
// We don't use FIXED here because we don't want to unmap
// something that may have been there already. We just walk
// down the address space until we find a place where the stub
// can be placed.
addr, _, _ := unix.RawSyscall6(
unix.SYS_MMAP,
stubStart,
stubROMapEnd,
unix.PROT_WRITE|unix.PROT_READ,
unix.MAP_PRIVATE|unix.MAP_ANONYMOUS,
0 /* fd */, 0 /* offset */)
if addr == stubStart {
break
}
if addr != 0 {
// Unmap the region we've mapped accidentally.
unix.RawSyscall(unix.SYS_MUNMAP, addr, stubROMapEnd, 0)
}
stubStart = uintptr(0)
}
if stubStart == 0 {
// This will happen only if we exhaust the entire address
// space, and it will take a long, long time.
panic("failed to map stub")
}
// Randomize stubSysmsgStack address.
gap := uintptr(rand.Uint64()) * hostarch.PageSize % (maximumUserAddress - stubStart - mapLen)
stubSysmsgStack += uintptr(gap)
// Copy the stub to the address.
targetSlice := unsafeSlice(stubStart, procStubLen)
copy(targetSlice, procStubSlice)
stubInitProcess = stubStart
stubSysmsgStart += stubStart
stubSysmsgStack += stubStart
stubROMapEnd += stubStart
// Align stubSysmsgStack to the per-thread stack size.
// Look at sysmsg/sighandler.c:sysmsg_addr() for more details.
if offset := stubSysmsgStack % sysmsg.PerThreadMemSize; offset != 0 {
stubSysmsgStack += sysmsg.PerThreadMemSize - offset
}
stubSysmsgRules += stubStart
targetSlice = unsafeSlice(stubSysmsgStart, stubSysmsgLen)
copy(targetSlice, sysmsg.SighandlerBlob)
// Initialize stub globals
p := (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_deep_sleep_timeout)))
*p = deepSleepTimeout
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_handshake_timeout)))
*p = handshakeTimeout
archState := (*sysmsg.ArchState)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_arch_state)))
archState.Init()
prepareSeccompRules(stubSysmsgStart, stubSysmsgRules, stubSysmsgRulesLen)
// Make the stub executable.
if _, _, errno := unix.RawSyscall(
unix.SYS_MPROTECT,
stubStart,
stubROMapEnd-stubStart,
unix.PROT_EXEC|unix.PROT_READ); errno != 0 {
panic("mprotect failed: " + errno.Error())
}
// Set the end.
stubEnd = stubStart + mapLen + uintptr(gap)
log.Debugf("stubStart %x stubSysmsgStart %x stubSysmsgStack %x, mapLen %x", stubStart, stubSysmsgStart, stubSysmsgStack, mapLen)
log.Debugf(archState.String())
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,315 @@
// Copyright 2018 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.
//go:build amd64
// +build amd64
package systrap
import (
"fmt"
"runtime"
"strings"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
const (
// initRegsRipAdjustment is the size of the syscall instruction.
initRegsRipAdjustment = 2
)
// resetSysemuRegs sets up emulation registers.
//
// This should be called prior to calling sysemu.
func (t *thread) resetSysemuRegs(regs *arch.Registers) {
regs.Cs = t.initRegs.Cs
regs.Ss = t.initRegs.Ss
regs.Ds = t.initRegs.Ds
regs.Es = t.initRegs.Es
regs.Fs = t.initRegs.Fs
regs.Gs = t.initRegs.Gs
}
// createSyscallRegs sets up syscall registers.
//
// This should be called to generate registers for a system call.
func createSyscallRegs(initRegs *arch.Registers, sysno uintptr, args ...arch.SyscallArgument) arch.Registers {
// Copy initial registers.
regs := *initRegs
// Set our syscall number.
regs.Rax = uint64(sysno)
if len(args) >= 1 {
regs.Rdi = args[0].Uint64()
}
if len(args) >= 2 {
regs.Rsi = args[1].Uint64()
}
if len(args) >= 3 {
regs.Rdx = args[2].Uint64()
}
if len(args) >= 4 {
regs.R10 = args[3].Uint64()
}
if len(args) >= 5 {
regs.R8 = args[4].Uint64()
}
if len(args) >= 6 {
regs.R9 = args[5].Uint64()
}
return regs
}
// isSingleStepping determines if the registers indicate single-stepping.
func isSingleStepping(regs *arch.Registers) bool {
return (regs.Eflags & arch.X86TrapFlag) != 0
}
// updateSyscallRegs updates registers after finishing sysemu.
func updateSyscallRegs(regs *arch.Registers) {
// Ptrace puts -ENOSYS in rax on syscall-enter-stop.
regs.Rax = regs.Orig_rax
}
// syscallReturnValue extracts a sensible return from registers.
func syscallReturnValue(regs *arch.Registers) (uintptr, error) {
rval := int64(regs.Rax)
if rval < 0 {
return 0, unix.Errno(-rval)
}
return uintptr(rval), nil
}
func dumpRegs(regs *arch.Registers) string {
var m strings.Builder
fmt.Fprintf(&m, "Registers:\n")
fmt.Fprintf(&m, "\tR15\t = %016x\n", regs.R15)
fmt.Fprintf(&m, "\tR14\t = %016x\n", regs.R14)
fmt.Fprintf(&m, "\tR13\t = %016x\n", regs.R13)
fmt.Fprintf(&m, "\tR12\t = %016x\n", regs.R12)
fmt.Fprintf(&m, "\tRbp\t = %016x\n", regs.Rbp)
fmt.Fprintf(&m, "\tRbx\t = %016x\n", regs.Rbx)
fmt.Fprintf(&m, "\tR11\t = %016x\n", regs.R11)
fmt.Fprintf(&m, "\tR10\t = %016x\n", regs.R10)
fmt.Fprintf(&m, "\tR9\t = %016x\n", regs.R9)
fmt.Fprintf(&m, "\tR8\t = %016x\n", regs.R8)
fmt.Fprintf(&m, "\tRax\t = %016x\n", regs.Rax)
fmt.Fprintf(&m, "\tRcx\t = %016x\n", regs.Rcx)
fmt.Fprintf(&m, "\tRdx\t = %016x\n", regs.Rdx)
fmt.Fprintf(&m, "\tRsi\t = %016x\n", regs.Rsi)
fmt.Fprintf(&m, "\tRdi\t = %016x\n", regs.Rdi)
fmt.Fprintf(&m, "\tOrig_rax = %016x\n", regs.Orig_rax)
fmt.Fprintf(&m, "\tRip\t = %016x\n", regs.Rip)
fmt.Fprintf(&m, "\tCs\t = %016x\n", regs.Cs)
fmt.Fprintf(&m, "\tEflags\t = %016x\n", regs.Eflags)
fmt.Fprintf(&m, "\tRsp\t = %016x\n", regs.Rsp)
fmt.Fprintf(&m, "\tSs\t = %016x\n", regs.Ss)
fmt.Fprintf(&m, "\tFs_base\t = %016x\n", regs.Fs_base)
fmt.Fprintf(&m, "\tGs_base\t = %016x\n", regs.Gs_base)
fmt.Fprintf(&m, "\tDs\t = %016x\n", regs.Ds)
fmt.Fprintf(&m, "\tEs\t = %016x\n", regs.Es)
fmt.Fprintf(&m, "\tFs\t = %016x\n", regs.Fs)
fmt.Fprintf(&m, "\tGs\t = %016x\n", regs.Gs)
return m.String()
}
// adjustInitregsRip adjust the current register RIP value to
// be just before the system call instruction excution
func (t *thread) adjustInitRegsRip() {
t.initRegs.Rip -= initRegsRipAdjustment
}
// Pass the expected PPID to the child via R15 when creating stub process.
func initChildProcessPPID(initregs *arch.Registers, ppid int32) {
initregs.R15 = uint64(ppid)
// Rbx has to be set to 1 when creating stub process.
initregs.Rbx = _NEW_STUB
}
// patchSignalInfo patches the signal info to account for hitting the seccomp
// filters from vsyscall emulation, specified below. We allow for SIGSYS as a
// synchronous trap, but patch the structure to appear like a SIGSEGV with the
// Rip as the faulting address.
//
// Note that this should only be called after verifying that the signalInfo has
// been generated by the kernel.
// Returns true if the signal info was patched, false otherwise.
func maybePatchSignalInfo(regs *arch.Registers, signalInfo *linux.SignalInfo) bool {
if signalInfo.Addr() < linux.VSyscallStartAddr ||
signalInfo.Addr() >= linux.VSyscallEndAddr {
return false
}
// The syscall event was triggered from vsyscall emulation.
signalInfo.Signo = int32(linux.SIGSEGV)
// Unwind the kernel emulation, if any has occurred. A SIGSYS is delivered
// with the si_call_addr field pointing to the current RIP. This field
// aligns with the si_addr field for a SIGSEGV, so we don't need to touch
// anything there. We do need to unwind emulation however, so we set the
// instruction pointer to the faulting value, and "unpop" the stack.
regs.Rip = signalInfo.Addr()
regs.Rsp -= 8
return true
}
// enableCpuidFault enables cpuid-faulting.
//
// This may fail on older kernels or hardware, so we just disregard the result.
// Host CPUID will be enabled.
//
// This is safe to call in an afterFork context.
//
//go:nosplit
//go:norace
func enableCpuidFault() {
unix.RawSyscall6(unix.SYS_ARCH_PRCTL, linux.ARCH_SET_CPUID, 0, 0, 0, 0, 0)
}
// appendArchSeccompRules append architecture specific seccomp rules when creating BPF program.
// Ref attachedThread() for more detail.
func appendArchSeccompRules(rules []seccomp.RuleSet) []seccomp.RuleSet {
return append(rules, []seccomp.RuleSet{
// Rules for trapping vsyscall access.
{
Rules: seccomp.SyscallRules{
unix.SYS_GETTIMEOFDAY: {},
unix.SYS_TIME: {},
unix.SYS_GETCPU: {}, // SYS_GETCPU was not defined in package syscall on amd64.
},
Action: linux.SECCOMP_RET_TRAP,
Vsyscall: true,
},
{
Rules: seccomp.SyscallRules{
unix.SYS_ARCH_PRCTL: []seccomp.Rule{
{seccomp.EqualTo(linux.ARCH_SET_CPUID), seccomp.EqualTo(0)},
{seccomp.EqualTo(linux.ARCH_SET_FS)},
{seccomp.EqualTo(linux.ARCH_GET_FS)},
},
},
Action: linux.SECCOMP_RET_ALLOW,
},
}...)
}
func (s *subprocess) PullFullState(c *context, ac *arch.Context64) error {
// Reset necessary registers.
regs := &ac.StateData().Regs
sysThread, err := s.getSysmsgThread(regs, c, ac)
if err != nil {
return err
}
msg := sysThread.msg
// In case of EventTypeSyscallTrap, we have only syscall argument
// registers and we need to trigger a signal in the stub process to get
// a full set of registers and an FPU state.
//
// In other cases, we have the full set of registers and need only copy
// the FPU state from a signal frame.
if msg.Type != sysmsg.EventTypeSyscallTrap {
s.saveFPState(msg, sysThread.fpuStateToMsgOffset, c, ac)
return nil
}
// In case of EventTypeSyscallTrap, the Sentry knows only the syscall
// number and syscall arguments and the target thread is stopped in the
// syshandler stub function. We need to ask syshandler to trigger a
// real syscall to get the full state.
msg.Regs = regs.PtraceRegs
sysThread.waitEvent(sysmsg.StateSigact)
if msg.Err != 0 {
panic(fmt.Sprintf("stub thread failed: err %d line %d: %s", msg.Err, msg.Line, msg))
}
if msg.Type != sysmsg.EventTypeSyscall {
panic(fmt.Sprintf("unknown message type: type %v: %s", msg.Type, msg))
}
sysThread.fpuStateToMsgOffset, err = msg.FPUStateOffset()
if err != nil {
return err
}
// When we are triggering the real syscall instruction, we don't
// restore all syscall arguments and even the syscall number.
msg.Regs.Rax = regs.Rax
msg.Regs.Orig_rax = regs.Orig_rax
msg.Regs.Rdi = regs.Rdi
msg.Regs.Rsi = regs.Rsi
msg.Regs.Rdx = regs.Rdx
msg.Regs.R10 = regs.R10
msg.Regs.R8 = regs.R8
msg.Regs.R9 = regs.R9
regs.PtraceRegs = msg.Regs
// The thread has restored all registers that could be changed in
// the syshandler stub function, but it is still in this function. We
// know the return address and let's set it so to be not affected if
// the stub code will be changed after save/restore.
regs.Rip = msg.RetAddr
s.saveFPState(msg, sysThread.fpuStateToMsgOffset, c, ac)
c.signalInfo = msg.SignalInfo
return nil
}
func restoreArchSpecificState(regs *arch.Registers, t *thread, sysThread *sysmsgThread, msg *sysmsg.Msg, _ *arch.Context64) {
regs.Gs_base = msg.Self
// Switching gs_base is a rare operation, therefore checking that we need to do
// so is better done in the sentry, because doing so on a host that doesn't
// have FSGSBASE instructions enabled is quite expensive since it would require
// an ARCH_PRCTL syscall.
if regs.Gs_base != sysThread.gsBase {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
t.attach()
var r arch.Registers
if err := t.getRegs(&r); err != nil {
panic(fmt.Sprintf("ptrace get regs failed: %v", err))
}
r.Gs_base = regs.Gs_base
if err := t.setRegs(&r); err != nil {
panic(fmt.Sprintf("ptrace set regs failed: %v", err))
}
if _, _, errno := unix.RawSyscall6(unix.SYS_PTRACE, unix.PTRACE_DETACH, uintptr(t.tid), 0, 0, 0, 0); errno != 0 {
panic(fmt.Sprintf("ptrace detach failed: %v", errno))
}
sysThread.gsBase = regs.Gs_base
}
}
func archSpecificSysThreadInit(sysThread *sysmsgThread, regs *arch.Registers) {
regs.Gs_base = sysThread.msg.Self
sysThread.gsBase = regs.Gs_base
}
func retrieveArchSpecificState(regs *arch.Registers, msg *sysmsg.Msg, _ *thread, ac *arch.Context64) {
}
@@ -0,0 +1,45 @@
// Copyright 2018 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.
//go:build amd64
// +build amd64
package systrap
import (
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
func (s *subprocess) restoreFPState(msg *sysmsg.Msg, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
// c.needRestoreFPState is changed only from the task goroutine, so it can
// be accessed without locks.
if !c.needRestoreFPState {
return
}
c.needRestoreFPState = false
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
dst := unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), c.fpLen)
copy(dst, src)
}
func (s *subprocess) saveFPState(msg *sysmsg.Msg, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), c.fpLen)
dst := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
copy(dst, src)
}
@@ -0,0 +1,213 @@
// Copyright 2019 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.
//go:build arm64
// +build arm64
package systrap
import (
"fmt"
"strings"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
const (
// initRegsRipAdjustment is the size of the svc instruction.
initRegsRipAdjustment = 4
)
// resetSysemuRegs sets up emulation registers.
//
// This should be called prior to calling sysemu.
func (t *thread) resetSysemuRegs(regs *arch.Registers) {
}
// createSyscallRegs sets up syscall registers.
//
// This should be called to generate registers for a system call.
func createSyscallRegs(initRegs *arch.Registers, sysno uintptr, args ...arch.SyscallArgument) arch.Registers {
// Copy initial registers (Pc, Sp, etc.).
regs := *initRegs
// Set our syscall number.
// r8 for the syscall number.
// r0-r6 is used to store the parameters.
regs.Regs[8] = uint64(sysno)
if len(args) >= 1 {
regs.Regs[0] = args[0].Uint64()
}
if len(args) >= 2 {
regs.Regs[1] = args[1].Uint64()
}
if len(args) >= 3 {
regs.Regs[2] = args[2].Uint64()
}
if len(args) >= 4 {
regs.Regs[3] = args[3].Uint64()
}
if len(args) >= 5 {
regs.Regs[4] = args[4].Uint64()
}
if len(args) >= 6 {
regs.Regs[5] = args[5].Uint64()
}
return regs
}
// isSingleStepping determines if the registers indicate single-stepping.
func isSingleStepping(regs *arch.Registers) bool {
// Refer to the ARM SDM D2.12.3: software step state machine
// return (regs.Pstate.SS == 1) && (MDSCR_EL1.SS == 1).
//
// Since the host Linux kernel will set MDSCR_EL1.SS on our behalf
// when we call a single-step ptrace command, we only need to check
// the Pstate.SS bit here.
return (regs.Pstate & arch.ARMTrapFlag) != 0
}
// updateSyscallRegs updates registers after finishing sysemu.
func updateSyscallRegs(regs *arch.Registers) {
// No special work is necessary.
return
}
// syscallReturnValue extracts a sensible return from registers.
func syscallReturnValue(regs *arch.Registers) (uintptr, error) {
rval := int64(regs.Regs[0])
if rval < 0 {
return 0, unix.Errno(-rval)
}
return uintptr(rval), nil
}
func dumpRegs(regs *arch.Registers) string {
var m strings.Builder
fmt.Fprintf(&m, "Registers:\n")
for i := 0; i < 31; i++ {
fmt.Fprintf(&m, "\tRegs[%d]\t = %016x\n", i, regs.Regs[i])
}
fmt.Fprintf(&m, "\tSp\t = %016x\n", regs.Sp)
fmt.Fprintf(&m, "\tPc\t = %016x\n", regs.Pc)
fmt.Fprintf(&m, "\tPstate\t = %016x\n", regs.Pstate)
return m.String()
}
// adjustInitregsRip adjust the current register RIP value to
// be just before the system call instruction excution
func (t *thread) adjustInitRegsRip() {
t.initRegs.Pc -= initRegsRipAdjustment
}
// Pass the expected PPID to the child via X7 when creating stub process
func initChildProcessPPID(initregs *arch.Registers, ppid int32) {
initregs.Regs[7] = uint64(ppid)
// R9 has to be set to 1 when creating stub process.
initregs.Regs[9] = _NEW_STUB
}
func maybePatchSignalInfo(regs *arch.Registers, signalInfo *linux.SignalInfo) (patched bool) {
// vsyscall emulation is not supported on ARM64. No need to patch anything.
return false
}
// Noop on arm64.
//
//go:nosplit
func enableCpuidFault() {
}
// appendArchSeccompRules append architecture specific seccomp rules when creating BPF program.
// Ref attachedThread() for more detail.
func appendArchSeccompRules(rules []seccomp.RuleSet) []seccomp.RuleSet {
return rules
}
// probeSeccomp returns true if seccomp is run after ptrace notifications,
// which is generally the case for kernel version >= 4.8.
//
// On arm64, the support of PTRACE_SYSEMU was added in the 5.3 kernel, so
// probeSeccomp can always return true.
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 := unix.RawSyscall6(
unix.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 == unix.SIGSTOP {
// SIGSTOP was delivered to another thread in the same thread
// group, which initiated another group stop. Just ignore it.
continue
}
if sig == (syscallEvent | unix.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))
}
}
func (s *subprocess) PullFullState(c *context, ac *arch.Context64) error {
// We do not support syscall trap in ARM64 so just get the fp state from the
// signal frame and we are done.
regs := &ac.StateData().Regs
sysThread, err := s.getSysmsgThread(regs, c, ac)
if err != nil {
return err
}
s.saveFPState(sysThread.msg, sysThread.fpuStateToMsgOffset, c, ac)
return nil
}
func restoreArchSpecificState(regs *arch.Registers, t *thread, _ *sysmsgThread, msg *sysmsg.Msg, ac *arch.Context64) {
msg.TLS = uint64(ac.TLS())
}
func archSpecificSysThreadInit(sysThread *sysmsgThread, regs *arch.Registers) {
}
func retrieveArchSpecificState(regs *arch.Registers, msg *sysmsg.Msg, t *thread, ac *arch.Context64) {
if !ac.SetTLS(uintptr(msg.TLS)) {
panic(fmt.Sprintf("ac.SetTLS(%+v) failed", msg.TLS))
}
}
@@ -0,0 +1,51 @@
// Copyright 2019 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.
//go:build arm64
// +build arm64
package systrap
import (
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
// Signal frames for ARM64 include 8 byte magic header before the floating point
// context.
//
// See: arch/arm64/include/uapi/asm/sigcontext.h
const sigFrameMagicHeaderLen = 8
func (s *subprocess) restoreFPState(msg *sysmsg.Msg, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
// c.needRestoreFPState is changed only from the task goroutine, so it can
// be accessed without locks.
if !c.needRestoreFPState {
return
}
c.needRestoreFPState = false
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
dst := unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset)+uintptr(sigFrameMagicHeaderLen), c.fpLen)
copy(dst, src)
}
func (s *subprocess) saveFPState(msg *sysmsg.Msg, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset)+uintptr(sigFrameMagicHeaderLen), c.fpLen)
dst := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
copy(dst, src)
}

Some files were not shown because too many files have changed in this diff Show More