mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
On ARM64 with Pointer Authentication (PAC), the sentry's Go runtime runs inside the KVM guest at Guest EL1. When the runtime calls a VDSO function such as _kernel_getrandom, the VDSO's paciasp signs the return address using whatever PAC state the guest happens to have. The subsequent SVC traps to El1_sync, the vCPU exits, and bluepillArchExit copies the VDSO PC into the host's signal context. After sigreturn resumes the host thread inside the VDSO, the VDSO epilogue's autiasp tries to verify the (now-stale) return address with the host's PAC keys. Because host and guest PAC keys are independent (and gVisor does not enable KVM_ARM_VCPU_PTRAUTH*), verification fails and the kernel raises SIGILL. gVisor's process-wide SIGILL handler then misinterprets R8 (which still contains VDSO state, not a vCPU pointer) and crashes with SIGSEGV. The crash is 100% reproducible on PAC-capable ARM64 hardware with workloads that do many fork/exec operations, e.g.: ``` runsc --platform=kvm do /bin/bash -c \ 'for i in $(seq 1 500); do /bin/echo x > /dev/null; done' ``` PAC keys are per-process secrets and cannot be read from userspace, so there is no way to synchronise them between Guest EL1 and Host EL0. Instead, disable the host's address-authentication PAC keys via PR_PAC_SET_ENABLED_KEYS on every host thread that ever holds a vCPU. With keys disabled, the host's paciasp/autiasp execute as NOPs and no longer try to verify the (mismatched) tag set by the guest, removing the crash. Only the four address keys (IA, IB, DA, DB) are passed; the generic key (APGA) is rejected by the kernel for this prctl and is unused by the VDSO. The prctl is invoked from loadSegments(), which runs every time a vCPU is bound to a (potentially new) host thread. Each thread that ever hosts a vCPU therefore has PAC disabled before it can trigger the bluepill SIGILL path. The prctl is per-thread and idempotent at the kernel level, so calling it again on a thread that already has PAC disabled is harmless. Fixes #12917. Co-authored-by: Konstantin Bogomolov <[email protected]> PiperOrigin-RevId: 926345394
169 lines
3.8 KiB
ArmAsm
169 lines
3.8 KiB
ArmAsm
// 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 "textflag.h"
|
|
|
|
// VCPU_CPU is the location of the CPU in the vCPU struct.
|
|
//
|
|
// This is guaranteed to be zero.
|
|
#define VCPU_CPU 0x0
|
|
|
|
// CPU_SELF is the self reference in ring0's percpu.
|
|
//
|
|
// This is guaranteed to be zero.
|
|
#define CPU_SELF 0x0
|
|
|
|
// Context offsets.
|
|
//
|
|
// Only limited use of the context is done in the assembly stub below, most is
|
|
// done in the Go handlers.
|
|
#define SIGINFO_SIGNO 0x0
|
|
#define SIGINFO_CODE 0x8
|
|
#define CONTEXT_PC 0x1B8
|
|
#define CONTEXT_R0 0xB8
|
|
|
|
#define SYS_MMAP 222
|
|
|
|
// getTLS returns the value of TPIDR_EL0 register.
|
|
TEXT ·getTLS(SB),NOSPLIT,$0-8
|
|
MRS TPIDR_EL0, R1
|
|
MOVD R1, value+0(FP)
|
|
RET
|
|
|
|
// setTLS writes the TPIDR_EL0 value.
|
|
TEXT ·setTLS(SB),NOSPLIT,$0-8
|
|
MOVD value+0(FP), R1
|
|
MSR R1, TPIDR_EL0
|
|
RET
|
|
|
|
// See bluepill.go.
|
|
TEXT ·bluepill(SB),NOSPLIT,$0
|
|
begin:
|
|
MOVD c+0(FP), R8
|
|
MOVD $VCPU_CPU(R8), R9
|
|
ORR $0xffff000000000000, R9, R9
|
|
// Trigger sigill.
|
|
// In ring0.Start(), the value of R8 will be stored into tpidr_el1.
|
|
// When the context was loaded into vcpu successfully,
|
|
// we will check if the value of R10 and R9 are the same.
|
|
WORD $0xd538d08a // MRS TPIDR_EL1, R10
|
|
check_vcpu:
|
|
CMP R10, R9
|
|
BEQ right_vCPU
|
|
wrong_vcpu:
|
|
CALL ·redpill(SB)
|
|
B begin
|
|
right_vCPU:
|
|
RET
|
|
|
|
// sighandler: see bluepill.go for documentation.
|
|
//
|
|
// The arguments are the following:
|
|
//
|
|
// R0 - The signal number.
|
|
// R1 - Pointer to siginfo_t structure.
|
|
// R2 - Pointer to ucontext structure.
|
|
//
|
|
TEXT ·sighandler(SB),NOSPLIT,$0
|
|
// si_signo should be sigill.
|
|
MOVD SIGINFO_SIGNO(R1), R7
|
|
CMPW $4, R7
|
|
BNE fallback
|
|
|
|
MOVD CONTEXT_PC(R2), R7
|
|
CMPW $0, R7
|
|
BEQ fallback
|
|
|
|
MOVD R2, 8(RSP)
|
|
BL ·bluepillHandler(SB) // Call the handler.
|
|
|
|
RET
|
|
|
|
fallback:
|
|
// Jump to the previous signal handler.
|
|
MOVD ·savedHandler(SB), R7
|
|
B (R7)
|
|
|
|
// func addrOfSighandler() uintptr
|
|
TEXT ·addrOfSighandler(SB), $0-8
|
|
MOVD $·sighandler(SB), R0
|
|
MOVD R0, ret+0(FP)
|
|
RET
|
|
|
|
// The arguments are the following:
|
|
//
|
|
// R0 - The signal number.
|
|
// R1 - Pointer to siginfo_t structure.
|
|
// R2 - Pointer to ucontext structure.
|
|
//
|
|
TEXT ·sigsysHandler(SB),NOSPLIT,$0
|
|
// si_code should be SYS_SECCOMP.
|
|
MOVD SIGINFO_CODE(R1), R7
|
|
CMPW $1, R7
|
|
BNE fallback
|
|
|
|
CMPW $SYS_MMAP, R8
|
|
BNE fallback
|
|
|
|
MOVD R2, 8(RSP)
|
|
BL ·seccompMmapHandler(SB) // Call the handler.
|
|
|
|
RET
|
|
|
|
fallback:
|
|
// Jump to the previous signal handler.
|
|
MOVD ·savedHandler(SB), R7
|
|
B (R7)
|
|
|
|
// func addrOfSighandler() uintptr
|
|
TEXT ·addrOfSigsysHandler(SB), $0-8
|
|
MOVD $·sigsysHandler(SB), R0
|
|
MOVD R0, ret+0(FP)
|
|
RET
|
|
|
|
// dieTrampoline: see bluepill.go, bluepill_arm64_unsafe.go for documentation.
|
|
TEXT ·dieTrampoline(SB),NOSPLIT,$0
|
|
// R0: Fake the old PC as caller
|
|
// R1: First argument (vCPU)
|
|
MOVD.P R1, 8(RSP) // R1: First argument (vCPU)
|
|
MOVD.P R0, 8(RSP) // R0: Fake the old PC as caller
|
|
B ·dieHandler(SB)
|
|
|
|
// func addrOfDieTrampoline() uintptr
|
|
TEXT ·addrOfDieTrampoline(SB), $0-8
|
|
MOVD $·dieTrampoline(SB), R0
|
|
MOVD R0, ret+0(FP)
|
|
RET
|
|
|
|
// currentEL returns the current exception level.
|
|
TEXT ·currentEL(SB),NOSPLIT,$0-8
|
|
MRS CurrentEL, R1
|
|
UBFX $2, R1, $2, R1
|
|
MOVD R1, ret+0(FP)
|
|
RET
|
|
|
|
// func checkPAC() bool
|
|
TEXT ·checkPAC(SB),NOSPLIT,$0-1
|
|
MOVD R30, R0 // Save LR
|
|
WORD $0xd503233f // PACIASP (signs R30)
|
|
CMP R0, R30
|
|
MOVD $0, R1
|
|
BEQ same
|
|
MOVD $1, R1
|
|
same:
|
|
MOVD R0, R30 // Restore LR
|
|
MOVB R1, ret+0(FP)
|
|
RET
|
|
|