mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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.
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user