mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
@@ -9,12 +9,12 @@ go_library(
|
||||
"bluepill.go",
|
||||
"bluepill_allocator.go",
|
||||
"bluepill_amd64.go",
|
||||
"bluepill_amd64.s",
|
||||
"bluepill_amd64_unsafe.go",
|
||||
"bluepill_arm64.go",
|
||||
"bluepill_arm64.s",
|
||||
"bluepill_arm64_unsafe.go",
|
||||
"bluepill_fault.go",
|
||||
"bluepill_impl_amd64.s",
|
||||
"bluepill_unsafe.go",
|
||||
"context.go",
|
||||
"filters_amd64.go",
|
||||
@@ -81,3 +81,11 @@ go_test(
|
||||
"//pkg/usermem",
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "bluepill_impl_amd64",
|
||||
srcs = ["bluepill_amd64.s"],
|
||||
outs = ["bluepill_impl_amd64.s"],
|
||||
cmd = "(echo -e '// build +amd64\\n' && $(location //pkg/sentry/platform/ring0/gen_offsets) && cat $(SRCS)) > $@",
|
||||
tools = ["//pkg/sentry/platform/ring0/gen_offsets"],
|
||||
)
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
// 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
|
||||
@@ -44,7 +39,7 @@ begin:
|
||||
LEAQ VCPU_CPU(AX), BX
|
||||
BYTE CLI;
|
||||
check_vcpu:
|
||||
MOVQ CPU_SELF(GS), CX
|
||||
MOVQ ENTRY_CPU_SELF(GS), CX
|
||||
CMPQ BX, CX
|
||||
JE right_vCPU
|
||||
wrong_vcpu:
|
||||
|
||||
@@ -156,15 +156,7 @@ func (*KVM) MaxUserAddress() usermem.Addr {
|
||||
func (k *KVM) NewAddressSpace(_ interface{}) (platform.AddressSpace, <-chan struct{}, error) {
|
||||
// Allocate page tables and install system mappings.
|
||||
pageTables := pagetables.New(newAllocator())
|
||||
applyPhysicalRegions(func(pr physicalRegion) bool {
|
||||
// Map the kernel in the upper half.
|
||||
pageTables.Map(
|
||||
usermem.Addr(ring0.KernelStartAddress|pr.virtual),
|
||||
pr.length,
|
||||
pagetables.MapOpts{AccessType: usermem.AnyAccess},
|
||||
pr.physical)
|
||||
return true // Keep iterating.
|
||||
})
|
||||
k.machine.mapUpperHalf(pageTables)
|
||||
|
||||
// Return the new address space.
|
||||
return &addressSpace{
|
||||
|
||||
@@ -155,7 +155,7 @@ func (m *machine) newVCPU() *vCPU {
|
||||
fd: int(fd),
|
||||
machine: m,
|
||||
}
|
||||
c.CPU.Init(&m.kernel, c)
|
||||
c.CPU.Init(&m.kernel, c.id, c)
|
||||
m.vCPUsByID[c.id] = c
|
||||
|
||||
// Ensure the signal mask is correct.
|
||||
@@ -183,9 +183,6 @@ func newMachine(vm int) (*machine, error) {
|
||||
// Create the machine.
|
||||
m := &machine{fd: vm}
|
||||
m.available.L = &m.mu
|
||||
m.kernel.Init(ring0.KernelOpts{
|
||||
PageTables: pagetables.New(newAllocator()),
|
||||
})
|
||||
|
||||
// Pull the maximum vCPUs.
|
||||
maxVCPUs, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(m.fd), _KVM_CHECK_EXTENSION, _KVM_CAP_MAX_VCPUS)
|
||||
@@ -197,6 +194,9 @@ func newMachine(vm int) (*machine, error) {
|
||||
log.Debugf("The maximum number of vCPUs is %d.", m.maxVCPUs)
|
||||
m.vCPUsByTID = make(map[uint64]*vCPU)
|
||||
m.vCPUsByID = make([]*vCPU, m.maxVCPUs)
|
||||
m.kernel.Init(ring0.KernelOpts{
|
||||
PageTables: pagetables.New(newAllocator()),
|
||||
}, m.maxVCPUs)
|
||||
|
||||
// Pull the maximum slots.
|
||||
maxSlots, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(m.fd), _KVM_CHECK_EXTENSION, _KVM_CAP_MAX_MEMSLOTS)
|
||||
@@ -219,15 +219,9 @@ func newMachine(vm int) (*machine, error) {
|
||||
pagetables.MapOpts{AccessType: usermem.AnyAccess},
|
||||
pr.physical)
|
||||
|
||||
// And keep everything in the upper half.
|
||||
m.kernel.PageTables.Map(
|
||||
usermem.Addr(ring0.KernelStartAddress|pr.virtual),
|
||||
pr.length,
|
||||
pagetables.MapOpts{AccessType: usermem.AnyAccess},
|
||||
pr.physical)
|
||||
|
||||
return true // Keep iterating.
|
||||
})
|
||||
m.mapUpperHalf(m.kernel.PageTables)
|
||||
|
||||
var physicalRegionsReadOnly []physicalRegion
|
||||
var physicalRegionsAvailable []physicalRegion
|
||||
@@ -365,6 +359,11 @@ func (m *machine) Destroy() {
|
||||
// Get gets an available vCPU.
|
||||
//
|
||||
// This will return with the OS thread locked.
|
||||
//
|
||||
// It is guaranteed that if any OS thread TID is in guest, m.vCPUs[TID] points
|
||||
// to the vCPU in which the OS thread TID is running. So if Get() returns with
|
||||
// the corrent context in guest, the vCPU of it must be the same as what
|
||||
// Get() returns.
|
||||
func (m *machine) Get() *vCPU {
|
||||
m.mu.RLock()
|
||||
runtime.LockOSThread()
|
||||
|
||||
@@ -144,6 +144,7 @@ func (c *vCPU) initArchState() error {
|
||||
// Set the entrypoint for the kernel.
|
||||
kernelUserRegs.RIP = uint64(reflect.ValueOf(ring0.Start).Pointer())
|
||||
kernelUserRegs.RAX = uint64(reflect.ValueOf(&c.CPU).Pointer())
|
||||
kernelUserRegs.RSP = c.StackTop()
|
||||
kernelUserRegs.RFLAGS = ring0.KernelFlagsSet
|
||||
|
||||
// Set the system registers.
|
||||
@@ -345,3 +346,43 @@ func rdonlyRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
func availableRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
return physicalRegions
|
||||
}
|
||||
|
||||
var execRegions []region
|
||||
|
||||
func init() {
|
||||
applyVirtualRegions(func(vr virtualRegion) {
|
||||
if excludeVirtualRegion(vr) || vr.filename == "[vsyscall]" {
|
||||
return
|
||||
}
|
||||
|
||||
if vr.accessType.Execute {
|
||||
execRegions = append(execRegions, vr.region)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (m *machine) mapUpperHalf(pageTable *pagetables.PageTables) {
|
||||
for _, r := range execRegions {
|
||||
physical, length, ok := translateToPhysical(r.virtual)
|
||||
if !ok || length < r.length {
|
||||
panic("impossilbe translation")
|
||||
}
|
||||
pageTable.Map(
|
||||
usermem.Addr(ring0.KernelStartAddress|r.virtual),
|
||||
r.length,
|
||||
pagetables.MapOpts{AccessType: usermem.Execute},
|
||||
physical)
|
||||
}
|
||||
for start, end := range m.kernel.EntryRegions() {
|
||||
regionLen := end - start
|
||||
physical, length, ok := translateToPhysical(start)
|
||||
if !ok || length < regionLen {
|
||||
panic("impossible translation")
|
||||
}
|
||||
pageTable.Map(
|
||||
usermem.Addr(ring0.KernelStartAddress|start),
|
||||
regionLen,
|
||||
pagetables.MapOpts{AccessType: usermem.ReadWrite},
|
||||
physical)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package kvm
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/platform"
|
||||
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
|
||||
"gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -48,6 +49,18 @@ const (
|
||||
poolPCIDs = 8
|
||||
)
|
||||
|
||||
func (m *machine) mapUpperHalf(pageTable *pagetables.PageTables) {
|
||||
applyPhysicalRegions(func(pr physicalRegion) bool {
|
||||
pageTable.Map(
|
||||
usermem.Addr(ring0.KernelStartAddress|pr.virtual),
|
||||
pr.length,
|
||||
pagetables.MapOpts{AccessType: usermem.AnyAccess},
|
||||
pr.physical)
|
||||
|
||||
return true // Keep iterating.
|
||||
})
|
||||
}
|
||||
|
||||
// Get all read-only physicalRegions.
|
||||
func rdonlyRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
var rdonlyRegions []region
|
||||
|
||||
@@ -76,15 +76,42 @@ type KernelOpts struct {
|
||||
type KernelArchState struct {
|
||||
KernelOpts
|
||||
|
||||
// cpuEntries is array of kernelEntry for all cpus
|
||||
cpuEntries []kernelEntry
|
||||
|
||||
// globalIDT is our set of interrupt gates.
|
||||
globalIDT idt64
|
||||
globalIDT *idt64
|
||||
}
|
||||
|
||||
// kernelEntry contains minimal CPU-specific arch state
|
||||
// that can be mapped at the upper of the address space.
|
||||
// Malicious APP might steal info from it via CPU bugs.
|
||||
type kernelEntry struct {
|
||||
// stack is the stack used for interrupts on this CPU.
|
||||
stack [256]byte
|
||||
|
||||
// scratch space for temporary usage.
|
||||
scratch0 uint64
|
||||
scratch1 uint64
|
||||
|
||||
// stackTop is the top of the stack.
|
||||
stackTop uint64
|
||||
|
||||
// cpuSelf is back reference to CPU.
|
||||
cpuSelf *CPU
|
||||
|
||||
// kernelCR3 is the cr3 used for sentry kernel.
|
||||
kernelCR3 uintptr
|
||||
|
||||
// gdt is the CPU's descriptor table.
|
||||
gdt descriptorTable
|
||||
|
||||
// tss is the CPU's task state.
|
||||
tss TaskState64
|
||||
}
|
||||
|
||||
// CPUArchState contains CPU-specific arch state.
|
||||
type CPUArchState struct {
|
||||
// stack is the stack used for interrupts on this CPU.
|
||||
stack [256]byte
|
||||
|
||||
// errorCode is the error code from the last exception.
|
||||
errorCode uintptr
|
||||
|
||||
@@ -97,11 +124,7 @@ type CPUArchState struct {
|
||||
// exception.
|
||||
errorType uintptr
|
||||
|
||||
// gdt is the CPU's descriptor table.
|
||||
gdt descriptorTable
|
||||
|
||||
// tss is the CPU's task state.
|
||||
tss TaskState64
|
||||
*kernelEntry
|
||||
}
|
||||
|
||||
// ErrorCode returns the last error code.
|
||||
|
||||
@@ -36,12 +36,15 @@ func sysenter()
|
||||
// This must be called prior to sysret/iret.
|
||||
func swapgs()
|
||||
|
||||
// jumpToKernel jumps to the kernel version of the current RIP.
|
||||
func jumpToKernel()
|
||||
|
||||
// sysret returns to userspace from a system call.
|
||||
//
|
||||
// The return code is the vector that interrupted execution.
|
||||
//
|
||||
// See stubs.go for a note regarding the frame size of this function.
|
||||
func sysret(*CPU, *arch.Registers) Vector
|
||||
func sysret(cpu *CPU, regs *arch.Registers, userCR3 uintptr) Vector
|
||||
|
||||
// "iret is the cadillac of CPL switching."
|
||||
//
|
||||
@@ -50,7 +53,7 @@ func sysret(*CPU, *arch.Registers) Vector
|
||||
// iret is nearly identical to sysret, except an iret is used to fully restore
|
||||
// all user state. This must be called in cases where all registers need to be
|
||||
// restored.
|
||||
func iret(*CPU, *arch.Registers) Vector
|
||||
func iret(cpu *CPU, regs *arch.Registers, userCR3 uintptr) Vector
|
||||
|
||||
// exception is the generic exception entry.
|
||||
//
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
MOVQ offset+PTRACE_RSI(reg), SI; \
|
||||
MOVQ offset+PTRACE_RDI(reg), DI;
|
||||
|
||||
// WRITE_CR3() writes the given CR3 value.
|
||||
//
|
||||
// The code corresponds to:
|
||||
//
|
||||
// mov %rax, %cr3
|
||||
//
|
||||
#define WRITE_CR3() \
|
||||
BYTE $0x0f; BYTE $0x22; BYTE $0xd8;
|
||||
|
||||
// SWAP_GS swaps the kernel GS (CPU).
|
||||
#define SWAP_GS() \
|
||||
BYTE $0x0F; BYTE $0x01; BYTE $0xf8;
|
||||
@@ -75,15 +84,9 @@
|
||||
#define SYSRET64() \
|
||||
BYTE $0x48; BYTE $0x0f; BYTE $0x07;
|
||||
|
||||
// LOAD_KERNEL_ADDRESS loads a kernel address.
|
||||
#define LOAD_KERNEL_ADDRESS(from, to) \
|
||||
MOVQ from, to; \
|
||||
ORQ ·KernelStartAddress(SB), to;
|
||||
|
||||
// LOAD_KERNEL_STACK loads the kernel stack.
|
||||
#define LOAD_KERNEL_STACK(from) \
|
||||
LOAD_KERNEL_ADDRESS(CPU_SELF(from), SP); \
|
||||
LEAQ CPU_STACK_TOP(SP), SP;
|
||||
#define LOAD_KERNEL_STACK(entry) \
|
||||
MOVQ ENTRY_STACK_TOP(entry), SP;
|
||||
|
||||
// See kernel.go.
|
||||
TEXT ·Halt(SB),NOSPLIT,$0
|
||||
@@ -95,58 +98,93 @@ TEXT ·swapgs(SB),NOSPLIT,$0
|
||||
SWAP_GS()
|
||||
RET
|
||||
|
||||
// jumpToKernel changes execution to the kernel address space.
|
||||
//
|
||||
// This works by changing the return value to the kernel version.
|
||||
TEXT ·jumpToKernel(SB),NOSPLIT,$0
|
||||
MOVQ 0(SP), AX
|
||||
ORQ ·KernelStartAddress(SB), AX // Future return value.
|
||||
MOVQ AX, 0(SP)
|
||||
RET
|
||||
|
||||
// See entry_amd64.go.
|
||||
TEXT ·sysret(SB),NOSPLIT,$0-24
|
||||
// Save original state.
|
||||
LOAD_KERNEL_ADDRESS(cpu+0(FP), BX)
|
||||
LOAD_KERNEL_ADDRESS(regs+8(FP), AX)
|
||||
CALL ·jumpToKernel(SB)
|
||||
// Save original state and stack. sysenter() or exception()
|
||||
// from APP(gr3) will switch to this stack, set the return
|
||||
// value (vector: 32(SP)) and then do RET, which will also
|
||||
// automatically return to the lower half.
|
||||
MOVQ cpu+0(FP), BX
|
||||
MOVQ regs+8(FP), AX
|
||||
MOVQ userCR3+16(FP), CX
|
||||
MOVQ SP, CPU_REGISTERS+PTRACE_RSP(BX)
|
||||
MOVQ BP, CPU_REGISTERS+PTRACE_RBP(BX)
|
||||
MOVQ AX, CPU_REGISTERS+PTRACE_RAX(BX)
|
||||
|
||||
// save SP AX userCR3 on the kernel stack.
|
||||
MOVQ CPU_ENTRY(BX), BX
|
||||
LOAD_KERNEL_STACK(BX)
|
||||
PUSHQ PTRACE_RSP(AX)
|
||||
PUSHQ PTRACE_RAX(AX)
|
||||
PUSHQ CX
|
||||
|
||||
// Restore user register state.
|
||||
REGISTERS_LOAD(AX, 0)
|
||||
MOVQ PTRACE_RIP(AX), CX // Needed for SYSRET.
|
||||
MOVQ PTRACE_FLAGS(AX), R11 // Needed for SYSRET.
|
||||
MOVQ PTRACE_RSP(AX), SP // Restore the stack directly.
|
||||
MOVQ PTRACE_RAX(AX), AX // Restore AX (scratch).
|
||||
|
||||
// restore userCR3, AX, SP.
|
||||
POPQ AX // Get userCR3.
|
||||
WRITE_CR3() // Switch to userCR3.
|
||||
POPQ AX // Restore AX.
|
||||
POPQ SP // Restore SP.
|
||||
SYSRET64()
|
||||
|
||||
// See entry_amd64.go.
|
||||
TEXT ·iret(SB),NOSPLIT,$0-24
|
||||
// Save original state.
|
||||
LOAD_KERNEL_ADDRESS(cpu+0(FP), BX)
|
||||
LOAD_KERNEL_ADDRESS(regs+8(FP), AX)
|
||||
CALL ·jumpToKernel(SB)
|
||||
// Save original state and stack. sysenter() or exception()
|
||||
// from APP(gr3) will switch to this stack, set the return
|
||||
// value (vector: 32(SP)) and then do RET, which will also
|
||||
// automatically return to the lower half.
|
||||
MOVQ cpu+0(FP), BX
|
||||
MOVQ regs+8(FP), AX
|
||||
MOVQ userCR3+16(FP), CX
|
||||
MOVQ SP, CPU_REGISTERS+PTRACE_RSP(BX)
|
||||
MOVQ BP, CPU_REGISTERS+PTRACE_RBP(BX)
|
||||
MOVQ AX, CPU_REGISTERS+PTRACE_RAX(BX)
|
||||
|
||||
// Build an IRET frame & restore state.
|
||||
MOVQ CPU_ENTRY(BX), BX
|
||||
LOAD_KERNEL_STACK(BX)
|
||||
MOVQ PTRACE_SS(AX), BX; PUSHQ BX
|
||||
MOVQ PTRACE_RSP(AX), CX; PUSHQ CX
|
||||
MOVQ PTRACE_FLAGS(AX), DX; PUSHQ DX
|
||||
MOVQ PTRACE_CS(AX), DI; PUSHQ DI
|
||||
MOVQ PTRACE_RIP(AX), SI; PUSHQ SI
|
||||
REGISTERS_LOAD(AX, 0) // Restore most registers.
|
||||
MOVQ PTRACE_RAX(AX), AX // Restore AX (scratch).
|
||||
PUSHQ PTRACE_SS(AX)
|
||||
PUSHQ PTRACE_RSP(AX)
|
||||
PUSHQ PTRACE_FLAGS(AX)
|
||||
PUSHQ PTRACE_CS(AX)
|
||||
PUSHQ PTRACE_RIP(AX)
|
||||
PUSHQ PTRACE_RAX(AX) // Save AX on kernel stack.
|
||||
PUSHQ CX // Save userCR3 on kernel stack.
|
||||
REGISTERS_LOAD(AX, 0) // Restore most registers.
|
||||
POPQ AX // Get userCR3.
|
||||
WRITE_CR3() // Switch to userCR3.
|
||||
POPQ AX // Restore AX.
|
||||
IRET()
|
||||
|
||||
// See entry_amd64.go.
|
||||
TEXT ·resume(SB),NOSPLIT,$0
|
||||
// See iret, above.
|
||||
MOVQ CPU_REGISTERS+PTRACE_SS(GS), BX; PUSHQ BX
|
||||
MOVQ CPU_REGISTERS+PTRACE_RSP(GS), CX; PUSHQ CX
|
||||
MOVQ CPU_REGISTERS+PTRACE_FLAGS(GS), DX; PUSHQ DX
|
||||
MOVQ CPU_REGISTERS+PTRACE_CS(GS), DI; PUSHQ DI
|
||||
MOVQ CPU_REGISTERS+PTRACE_RIP(GS), SI; PUSHQ SI
|
||||
REGISTERS_LOAD(GS, CPU_REGISTERS)
|
||||
MOVQ CPU_REGISTERS+PTRACE_RAX(GS), AX
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
PUSHQ CPU_REGISTERS+PTRACE_SS(AX)
|
||||
PUSHQ CPU_REGISTERS+PTRACE_RSP(AX)
|
||||
PUSHQ CPU_REGISTERS+PTRACE_FLAGS(AX)
|
||||
PUSHQ CPU_REGISTERS+PTRACE_CS(AX)
|
||||
PUSHQ CPU_REGISTERS+PTRACE_RIP(AX)
|
||||
REGISTERS_LOAD(AX, CPU_REGISTERS)
|
||||
MOVQ CPU_REGISTERS+PTRACE_RAX(AX), AX
|
||||
IRET()
|
||||
|
||||
// See entry_amd64.go.
|
||||
TEXT ·Start(SB),NOSPLIT,$0
|
||||
LOAD_KERNEL_STACK(AX) // Set the stack.
|
||||
PUSHQ $0x0 // Previous frame pointer.
|
||||
MOVQ SP, BP // Set frame pointer.
|
||||
PUSHQ AX // First argument (CPU).
|
||||
@@ -164,44 +202,54 @@ TEXT ·sysenter(SB),NOSPLIT,$0
|
||||
|
||||
user:
|
||||
SWAP_GS()
|
||||
XCHGQ CPU_REGISTERS+PTRACE_RSP(GS), SP // Swap stacks.
|
||||
XCHGQ CPU_REGISTERS+PTRACE_RAX(GS), AX // Swap for AX (regs).
|
||||
MOVQ AX, ENTRY_SCRATCH0(GS) // Save user AX on scratch.
|
||||
MOVQ ENTRY_KERNEL_CR3(GS), AX // Get kernel cr3 on AX.
|
||||
WRITE_CR3() // Switch to kernel cr3.
|
||||
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RAX(AX), AX // Get user regs.
|
||||
REGISTERS_SAVE(AX, 0) // Save all except IP, FLAGS, SP, AX.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RAX(GS), BX // Load saved AX value.
|
||||
MOVQ BX, PTRACE_RAX(AX) // Save everything else.
|
||||
MOVQ BX, PTRACE_ORIGRAX(AX)
|
||||
MOVQ CX, PTRACE_RIP(AX)
|
||||
MOVQ R11, PTRACE_FLAGS(AX)
|
||||
MOVQ CPU_REGISTERS+PTRACE_RSP(GS), BX; MOVQ BX, PTRACE_RSP(AX)
|
||||
MOVQ $0, CPU_ERROR_CODE(GS) // Clear error code.
|
||||
MOVQ $1, CPU_ERROR_TYPE(GS) // Set error type to user.
|
||||
MOVQ SP, PTRACE_RSP(AX)
|
||||
MOVQ ENTRY_SCRATCH0(GS), CX // Load saved user AX value.
|
||||
MOVQ CX, PTRACE_RAX(AX) // Save everything else.
|
||||
MOVQ CX, PTRACE_ORIGRAX(AX)
|
||||
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RSP(AX), SP // Get stacks.
|
||||
MOVQ $0, CPU_ERROR_CODE(AX) // Clear error code.
|
||||
MOVQ $1, CPU_ERROR_TYPE(AX) // Set error type to user.
|
||||
|
||||
// Return to the kernel, where the frame is:
|
||||
//
|
||||
// vector (sp+24)
|
||||
// vector (sp+32)
|
||||
// userCR3 (sp+24)
|
||||
// regs (sp+16)
|
||||
// cpu (sp+8)
|
||||
// vcpu.Switch (sp+0)
|
||||
//
|
||||
MOVQ CPU_REGISTERS+PTRACE_RBP(GS), BP // Original base pointer.
|
||||
MOVQ $Syscall, 24(SP) // Output vector.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RBP(AX), BP // Original base pointer.
|
||||
MOVQ $Syscall, 32(SP) // Output vector.
|
||||
RET
|
||||
|
||||
kernel:
|
||||
// We can't restore the original stack, but we can access the registers
|
||||
// in the CPU state directly. No need for temporary juggling.
|
||||
MOVQ AX, CPU_REGISTERS+PTRACE_ORIGRAX(GS)
|
||||
MOVQ AX, CPU_REGISTERS+PTRACE_RAX(GS)
|
||||
REGISTERS_SAVE(GS, CPU_REGISTERS)
|
||||
MOVQ CX, CPU_REGISTERS+PTRACE_RIP(GS)
|
||||
MOVQ R11, CPU_REGISTERS+PTRACE_FLAGS(GS)
|
||||
MOVQ SP, CPU_REGISTERS+PTRACE_RSP(GS)
|
||||
MOVQ $0, CPU_ERROR_CODE(GS) // Clear error code.
|
||||
MOVQ $0, CPU_ERROR_TYPE(GS) // Set error type to kernel.
|
||||
MOVQ AX, ENTRY_SCRATCH0(GS)
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
REGISTERS_SAVE(AX, CPU_REGISTERS)
|
||||
MOVQ CX, CPU_REGISTERS+PTRACE_RIP(AX)
|
||||
MOVQ R11, CPU_REGISTERS+PTRACE_FLAGS(AX)
|
||||
MOVQ SP, CPU_REGISTERS+PTRACE_RSP(AX)
|
||||
MOVQ ENTRY_SCRATCH0(GS), BX
|
||||
MOVQ BX, CPU_REGISTERS+PTRACE_ORIGRAX(AX)
|
||||
MOVQ BX, CPU_REGISTERS+PTRACE_RAX(AX)
|
||||
MOVQ $0, CPU_ERROR_CODE(AX) // Clear error code.
|
||||
MOVQ $0, CPU_ERROR_TYPE(AX) // Set error type to kernel.
|
||||
|
||||
// Call the syscall trampoline.
|
||||
LOAD_KERNEL_STACK(GS)
|
||||
MOVQ CPU_SELF(GS), AX // Load vCPU.
|
||||
PUSHQ AX // First argument (vCPU).
|
||||
CALL ·kernelSyscall(SB) // Call the trampoline.
|
||||
POPQ AX // Pop vCPU.
|
||||
@@ -237,9 +285,14 @@ user:
|
||||
SWAP_GS()
|
||||
ADDQ $-8, SP // Adjust for flags.
|
||||
MOVQ $_KERNEL_FLAGS, 0(SP); BYTE $0x9d; // Reset flags (POPFQ).
|
||||
XCHGQ CPU_REGISTERS+PTRACE_RAX(GS), AX // Swap for user regs.
|
||||
PUSHQ AX // Save user AX on stack.
|
||||
MOVQ ENTRY_KERNEL_CR3(GS), AX // Get kernel cr3 on AX.
|
||||
WRITE_CR3() // Switch to kernel cr3.
|
||||
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RAX(AX), AX // Get user regs.
|
||||
REGISTERS_SAVE(AX, 0) // Save all except IP, FLAGS, SP, AX.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RAX(GS), BX // Restore original AX.
|
||||
POPQ BX // Restore original AX.
|
||||
MOVQ BX, PTRACE_RAX(AX) // Save it.
|
||||
MOVQ BX, PTRACE_ORIGRAX(AX)
|
||||
MOVQ 16(SP), BX; MOVQ BX, PTRACE_RIP(AX)
|
||||
@@ -249,34 +302,36 @@ user:
|
||||
MOVQ 48(SP), SI; MOVQ SI, PTRACE_SS(AX)
|
||||
|
||||
// Copy out and return.
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
MOVQ 0(SP), BX // Load vector.
|
||||
MOVQ 8(SP), CX // Load error code.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RSP(GS), SP // Original stack (kernel version).
|
||||
MOVQ CPU_REGISTERS+PTRACE_RBP(GS), BP // Original base pointer.
|
||||
MOVQ CX, CPU_ERROR_CODE(GS) // Set error code.
|
||||
MOVQ $1, CPU_ERROR_TYPE(GS) // Set error type to user.
|
||||
MOVQ BX, 24(SP) // Output vector.
|
||||
MOVQ CPU_REGISTERS+PTRACE_RSP(AX), SP // Original stack (kernel version).
|
||||
MOVQ CPU_REGISTERS+PTRACE_RBP(AX), BP // Original base pointer.
|
||||
MOVQ CX, CPU_ERROR_CODE(AX) // Set error code.
|
||||
MOVQ $1, CPU_ERROR_TYPE(AX) // Set error type to user.
|
||||
MOVQ BX, 32(SP) // Output vector.
|
||||
RET
|
||||
|
||||
kernel:
|
||||
// As per above, we can save directly.
|
||||
MOVQ AX, CPU_REGISTERS+PTRACE_RAX(GS)
|
||||
MOVQ AX, CPU_REGISTERS+PTRACE_ORIGRAX(GS)
|
||||
REGISTERS_SAVE(GS, CPU_REGISTERS)
|
||||
MOVQ 16(SP), AX; MOVQ AX, CPU_REGISTERS+PTRACE_RIP(GS)
|
||||
MOVQ 32(SP), BX; MOVQ BX, CPU_REGISTERS+PTRACE_FLAGS(GS)
|
||||
MOVQ 40(SP), CX; MOVQ CX, CPU_REGISTERS+PTRACE_RSP(GS)
|
||||
PUSHQ AX
|
||||
MOVQ ENTRY_CPU_SELF(GS), AX // Load vCPU.
|
||||
REGISTERS_SAVE(AX, CPU_REGISTERS)
|
||||
POPQ BX
|
||||
MOVQ BX, CPU_REGISTERS+PTRACE_RAX(AX)
|
||||
MOVQ BX, CPU_REGISTERS+PTRACE_ORIGRAX(AX)
|
||||
MOVQ 16(SP), BX; MOVQ BX, CPU_REGISTERS+PTRACE_RIP(AX)
|
||||
MOVQ 32(SP), BX; MOVQ BX, CPU_REGISTERS+PTRACE_FLAGS(AX)
|
||||
MOVQ 40(SP), BX; MOVQ BX, CPU_REGISTERS+PTRACE_RSP(AX)
|
||||
|
||||
// Set the error code and adjust the stack.
|
||||
MOVQ 8(SP), AX // Load the error code.
|
||||
MOVQ AX, CPU_ERROR_CODE(GS) // Copy out to the CPU.
|
||||
MOVQ $0, CPU_ERROR_TYPE(GS) // Set error type to kernel.
|
||||
MOVQ 8(SP), BX // Load the error code.
|
||||
MOVQ BX, CPU_ERROR_CODE(AX) // Copy out to the CPU.
|
||||
MOVQ $0, CPU_ERROR_TYPE(AX) // Set error type to kernel.
|
||||
MOVQ 0(SP), BX // BX contains the vector.
|
||||
ADDQ $48, SP // Drop the exception frame.
|
||||
|
||||
// Call the exception trampoline.
|
||||
LOAD_KERNEL_STACK(GS)
|
||||
MOVQ CPU_SELF(GS), AX // Load vCPU.
|
||||
PUSHQ BX // Second argument (vector).
|
||||
PUSHQ AX // First argument (vCPU).
|
||||
CALL ·kernelException(SB) // Call the trampoline.
|
||||
|
||||
@@ -24,7 +24,10 @@ go_binary(
|
||||
"defs_impl_arm64.go",
|
||||
"main.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry/platform/ring0:__pkg__"],
|
||||
visibility = [
|
||||
"//pkg/sentry/platform/kvm:__pkg__",
|
||||
"//pkg/sentry/platform/ring0:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/cpuid",
|
||||
"//pkg/sentry/arch",
|
||||
|
||||
@@ -19,8 +19,8 @@ package ring0
|
||||
// N.B. that constraints on KernelOpts must be satisfied.
|
||||
//
|
||||
//go:nosplit
|
||||
func (k *Kernel) Init(opts KernelOpts) {
|
||||
k.init(opts)
|
||||
func (k *Kernel) Init(opts KernelOpts, maxCPUs int) {
|
||||
k.init(opts, maxCPUs)
|
||||
}
|
||||
|
||||
// Halt halts execution.
|
||||
@@ -49,6 +49,11 @@ func (defaultHooks) KernelException(Vector) {
|
||||
|
||||
// kernelSyscall is a trampoline.
|
||||
//
|
||||
// When in amd64, it is called with %rip on the upper half, so it can
|
||||
// NOT access to any global data which is not mapped on upper and must
|
||||
// call to function pointers or interfaces to switch to the lower half
|
||||
// so that callee can access to global data.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
@@ -58,6 +63,11 @@ func kernelSyscall(c *CPU) {
|
||||
|
||||
// kernelException is a trampoline.
|
||||
//
|
||||
// When in amd64, it is called with %rip on the upper half, so it can
|
||||
// NOT access to any global data which is not mapped on upper and must
|
||||
// call to function pointers or interfaces to switch to the lower half
|
||||
// so that callee can access to global data.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
@@ -68,10 +78,10 @@ func kernelException(c *CPU, vector Vector) {
|
||||
// Init initializes a new CPU.
|
||||
//
|
||||
// Init allows embedding in other objects.
|
||||
func (c *CPU) Init(k *Kernel, hooks Hooks) {
|
||||
c.self = c // Set self reference.
|
||||
c.kernel = k // Set kernel reference.
|
||||
c.init() // Perform architectural init.
|
||||
func (c *CPU) Init(k *Kernel, cpuID int, hooks Hooks) {
|
||||
c.self = c // Set self reference.
|
||||
c.kernel = k // Set kernel reference.
|
||||
c.init(cpuID) // Perform architectural init.
|
||||
|
||||
// Require hooks.
|
||||
if hooks != nil {
|
||||
|
||||
@@ -18,13 +18,42 @@ package ring0
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// init initializes architecture-specific state.
|
||||
func (k *Kernel) init(opts KernelOpts) {
|
||||
func (k *Kernel) init(opts KernelOpts, maxCPUs int) {
|
||||
// Save the root page tables.
|
||||
k.PageTables = opts.PageTables
|
||||
|
||||
entrySize := reflect.TypeOf(kernelEntry{}).Size()
|
||||
var (
|
||||
entries []kernelEntry
|
||||
padding = 1
|
||||
)
|
||||
for {
|
||||
entries = make([]kernelEntry, maxCPUs+padding-1)
|
||||
totalSize := entrySize * uintptr(maxCPUs+padding-1)
|
||||
addr := reflect.ValueOf(&entries[0]).Pointer()
|
||||
if addr&(usermem.PageSize-1) == 0 && totalSize >= usermem.PageSize {
|
||||
// The runtime forces power-of-2 alignment for allocations, and we are therefore
|
||||
// safe once the first address is aligned and the chunk is at least a full page.
|
||||
break
|
||||
}
|
||||
padding = padding << 1
|
||||
}
|
||||
k.cpuEntries = entries
|
||||
|
||||
k.globalIDT = &idt64{}
|
||||
if reflect.TypeOf(idt64{}).Size() != usermem.PageSize {
|
||||
panic("Size of globalIDT should be PageSize")
|
||||
}
|
||||
if reflect.ValueOf(k.globalIDT).Pointer()&(usermem.PageSize-1) != 0 {
|
||||
panic("Allocated globalIDT should be page aligned")
|
||||
}
|
||||
|
||||
// Setup the IDT, which is uniform.
|
||||
for v, handler := range handlers {
|
||||
// Allow Breakpoint and Overflow to be called from all
|
||||
@@ -39,8 +68,26 @@ func (k *Kernel) init(opts KernelOpts) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kernel) EntryRegions() map[uintptr]uintptr {
|
||||
regions := make(map[uintptr]uintptr)
|
||||
|
||||
addr := reflect.ValueOf(&k.cpuEntries[0]).Pointer()
|
||||
size := reflect.TypeOf(kernelEntry{}).Size() * uintptr(len(k.cpuEntries))
|
||||
end, _ := usermem.Addr(addr + size).RoundUp()
|
||||
regions[uintptr(usermem.Addr(addr).RoundDown())] = uintptr(end)
|
||||
|
||||
addr = reflect.ValueOf(k.globalIDT).Pointer()
|
||||
size = reflect.TypeOf(idt64{}).Size()
|
||||
end, _ = usermem.Addr(addr + size).RoundUp()
|
||||
regions[uintptr(usermem.Addr(addr).RoundDown())] = uintptr(end)
|
||||
|
||||
return regions
|
||||
}
|
||||
|
||||
// init initializes architecture-specific state.
|
||||
func (c *CPU) init() {
|
||||
func (c *CPU) init(cpuID int) {
|
||||
c.kernelEntry = &c.kernel.cpuEntries[cpuID]
|
||||
c.cpuSelf = c
|
||||
// Null segment.
|
||||
c.gdt[0].setNull()
|
||||
|
||||
@@ -65,6 +112,7 @@ func (c *CPU) init() {
|
||||
|
||||
// Set the kernel stack pointer in the TSS (virtual address).
|
||||
stackAddr := c.StackTop()
|
||||
c.stackTop = stackAddr
|
||||
c.tss.rsp0Lo = uint32(stackAddr)
|
||||
c.tss.rsp0Hi = uint32(stackAddr >> 32)
|
||||
c.tss.ist1Lo = uint32(stackAddr)
|
||||
@@ -183,7 +231,7 @@ func IsCanonical(addr uint64) bool {
|
||||
//go:nosplit
|
||||
func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
|
||||
userCR3 := switchOpts.PageTables.CR3(!switchOpts.Flush, switchOpts.UserPCID)
|
||||
kernelCR3 := c.kernel.PageTables.CR3(true, switchOpts.KernelPCID)
|
||||
c.kernelCR3 = uintptr(c.kernel.PageTables.CR3(true, switchOpts.KernelPCID))
|
||||
|
||||
// Sanitize registers.
|
||||
regs := switchOpts.Registers
|
||||
@@ -197,15 +245,11 @@ func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
|
||||
WriteFS(uintptr(regs.Fs_base)) // escapes: no. Set application FS.
|
||||
WriteGS(uintptr(regs.Gs_base)) // escapes: no. Set application GS.
|
||||
LoadFloatingPoint(switchOpts.FloatingPointState) // escapes: no. Copy in floating point.
|
||||
jumpToKernel() // Switch to upper half.
|
||||
writeCR3(uintptr(userCR3)) // Change to user address space.
|
||||
if switchOpts.FullRestore {
|
||||
vector = iret(c, regs)
|
||||
vector = iret(c, regs, uintptr(userCR3))
|
||||
} else {
|
||||
vector = sysret(c, regs)
|
||||
vector = sysret(c, regs, uintptr(userCR3))
|
||||
}
|
||||
writeCR3(uintptr(kernelCR3)) // Return to kernel address space.
|
||||
jumpToUser() // Return to lower half.
|
||||
SaveFloatingPoint(switchOpts.FloatingPointState) // escapes: no. Copy out floating point.
|
||||
WriteFS(uintptr(c.registers.Fs_base)) // escapes: no. Restore kernel FS.
|
||||
return
|
||||
@@ -219,7 +263,7 @@ func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
|
||||
//go:nosplit
|
||||
func start(c *CPU) {
|
||||
// Save per-cpu & FS segment.
|
||||
WriteGS(kernelAddr(c))
|
||||
WriteGS(kernelAddr(c.kernelEntry))
|
||||
WriteFS(uintptr(c.registers.Fs_base))
|
||||
|
||||
// Initialize floating point.
|
||||
|
||||
@@ -25,13 +25,13 @@ func HaltAndResume()
|
||||
func HaltEl1SvcAndResume()
|
||||
|
||||
// init initializes architecture-specific state.
|
||||
func (k *Kernel) init(opts KernelOpts) {
|
||||
func (k *Kernel) init(opts KernelOpts, maxCPUs int) {
|
||||
// Save the root page tables.
|
||||
k.PageTables = opts.PageTables
|
||||
}
|
||||
|
||||
// init initializes architecture-specific state.
|
||||
func (c *CPU) init() {
|
||||
func (c *CPU) init(cpuID int) {
|
||||
// Set the kernel stack pointer(virtual address).
|
||||
c.registers.Sp = uint64(c.StackTop())
|
||||
|
||||
|
||||
@@ -61,21 +61,9 @@ func wrgsbase(addr uintptr)
|
||||
// wrgsmsr writes to the GS_BASE MSR.
|
||||
func wrgsmsr(addr uintptr)
|
||||
|
||||
// writeCR3 writes the CR3 value.
|
||||
func writeCR3(phys uintptr)
|
||||
|
||||
// readCR3 reads the current CR3 value.
|
||||
func readCR3() uintptr
|
||||
|
||||
// readCR2 reads the current CR2 value.
|
||||
func readCR2() uintptr
|
||||
|
||||
// jumpToKernel jumps to the kernel version of the current RIP.
|
||||
func jumpToKernel()
|
||||
|
||||
// jumpToUser jumps to the user version of the current RIP.
|
||||
func jumpToUser()
|
||||
|
||||
// fninit initializes the floating point unit.
|
||||
func fninit()
|
||||
|
||||
|
||||
@@ -127,53 +127,6 @@ TEXT ·wrgsmsr(SB),NOSPLIT,$0-8
|
||||
BYTE $0x0f; BYTE $0x30; // WRMSR
|
||||
RET
|
||||
|
||||
// jumpToUser changes execution to the user address.
|
||||
//
|
||||
// This works by changing the return value to the user version.
|
||||
TEXT ·jumpToUser(SB),NOSPLIT,$0
|
||||
MOVQ 0(SP), AX
|
||||
MOVQ ·KernelStartAddress(SB), BX
|
||||
NOTQ BX
|
||||
ANDQ BX, SP // Switch the stack.
|
||||
ANDQ BX, BP // Switch the frame pointer.
|
||||
ANDQ BX, AX // Future return value.
|
||||
MOVQ AX, 0(SP)
|
||||
RET
|
||||
|
||||
// jumpToKernel changes execution to the kernel address space.
|
||||
//
|
||||
// This works by changing the return value to the kernel version.
|
||||
TEXT ·jumpToKernel(SB),NOSPLIT,$0
|
||||
MOVQ 0(SP), AX
|
||||
MOVQ ·KernelStartAddress(SB), BX
|
||||
ORQ BX, SP // Switch the stack.
|
||||
ORQ BX, BP // Switch the frame pointer.
|
||||
ORQ BX, AX // Future return value.
|
||||
MOVQ AX, 0(SP)
|
||||
RET
|
||||
|
||||
// writeCR3 writes the given CR3 value.
|
||||
//
|
||||
// The code corresponds to:
|
||||
//
|
||||
// mov %rax, %cr3
|
||||
//
|
||||
TEXT ·writeCR3(SB),NOSPLIT,$0-8
|
||||
MOVQ cr3+0(FP), AX
|
||||
BYTE $0x0f; BYTE $0x22; BYTE $0xd8;
|
||||
RET
|
||||
|
||||
// readCR3 reads the current CR3 value.
|
||||
//
|
||||
// The code corresponds to:
|
||||
//
|
||||
// mov %cr3, %rax
|
||||
//
|
||||
TEXT ·readCR3(SB),NOSPLIT,$0-8
|
||||
BYTE $0x0f; BYTE $0x20; BYTE $0xd8;
|
||||
MOVQ AX, ret+0(FP)
|
||||
RET
|
||||
|
||||
// readCR2 reads the current CR2 value.
|
||||
//
|
||||
// The code corresponds to:
|
||||
|
||||
@@ -30,11 +30,18 @@ func Emit(w io.Writer) {
|
||||
|
||||
c := &CPU{}
|
||||
fmt.Fprintf(w, "\n// CPU offsets.\n")
|
||||
fmt.Fprintf(w, "#define CPU_SELF 0x%02x\n", reflect.ValueOf(&c.self).Pointer()-reflect.ValueOf(c).Pointer())
|
||||
fmt.Fprintf(w, "#define CPU_REGISTERS 0x%02x\n", reflect.ValueOf(&c.registers).Pointer()-reflect.ValueOf(c).Pointer())
|
||||
fmt.Fprintf(w, "#define CPU_STACK_TOP 0x%02x\n", reflect.ValueOf(&c.stack[0]).Pointer()-reflect.ValueOf(c).Pointer()+uintptr(len(c.stack)))
|
||||
fmt.Fprintf(w, "#define CPU_ERROR_CODE 0x%02x\n", reflect.ValueOf(&c.errorCode).Pointer()-reflect.ValueOf(c).Pointer())
|
||||
fmt.Fprintf(w, "#define CPU_ERROR_TYPE 0x%02x\n", reflect.ValueOf(&c.errorType).Pointer()-reflect.ValueOf(c).Pointer())
|
||||
fmt.Fprintf(w, "#define CPU_ENTRY 0x%02x\n", reflect.ValueOf(&c.kernelEntry).Pointer()-reflect.ValueOf(c).Pointer())
|
||||
|
||||
e := &kernelEntry{}
|
||||
fmt.Fprintf(w, "\n// CPU entry offsets.\n")
|
||||
fmt.Fprintf(w, "#define ENTRY_SCRATCH0 0x%02x\n", reflect.ValueOf(&e.scratch0).Pointer()-reflect.ValueOf(e).Pointer())
|
||||
fmt.Fprintf(w, "#define ENTRY_SCRATCH1 0x%02x\n", reflect.ValueOf(&e.scratch1).Pointer()-reflect.ValueOf(e).Pointer())
|
||||
fmt.Fprintf(w, "#define ENTRY_STACK_TOP 0x%02x\n", reflect.ValueOf(&e.stackTop).Pointer()-reflect.ValueOf(e).Pointer())
|
||||
fmt.Fprintf(w, "#define ENTRY_CPU_SELF 0x%02x\n", reflect.ValueOf(&e.cpuSelf).Pointer()-reflect.ValueOf(e).Pointer())
|
||||
fmt.Fprintf(w, "#define ENTRY_KERNEL_CR3 0x%02x\n", reflect.ValueOf(&e.kernelCR3).Pointer()-reflect.ValueOf(e).Pointer())
|
||||
|
||||
fmt.Fprintf(w, "\n// Bits.\n")
|
||||
fmt.Fprintf(w, "#define _RFLAGS_IF 0x%02x\n", _RFLAGS_IF)
|
||||
|
||||
@@ -104,7 +104,7 @@ const (
|
||||
VirtualizationException
|
||||
SecurityException = 0x1e
|
||||
SyscallInt80 = 0x80
|
||||
_NR_INTERRUPTS = SyscallInt80 + 1
|
||||
_NR_INTERRUPTS = 0x100
|
||||
)
|
||||
|
||||
// System call vectors.
|
||||
|
||||
Reference in New Issue
Block a user