github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/platform/ptrace/subprocess_arm64.go (about) 1 // Copyright 2019 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build arm64 16 // +build arm64 17 18 package ptrace 19 20 import ( 21 "fmt" 22 "strings" 23 24 "golang.org/x/sys/unix" 25 "github.com/nicocha30/gvisor-ligolo/pkg/abi/linux" 26 "github.com/nicocha30/gvisor-ligolo/pkg/seccomp" 27 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch" 28 ) 29 30 const ( 31 // initRegsRipAdjustment is the size of the svc instruction. 32 initRegsRipAdjustment = 4 33 ) 34 35 // resetSysemuRegs sets up emulation registers. 36 // 37 // This should be called prior to calling sysemu. 38 func (t *thread) resetSysemuRegs(regs *arch.Registers) { 39 } 40 41 // createSyscallRegs sets up syscall registers. 42 // 43 // This should be called to generate registers for a system call. 44 func createSyscallRegs(initRegs *arch.Registers, sysno uintptr, args ...arch.SyscallArgument) arch.Registers { 45 // Copy initial registers (Pc, Sp, etc.). 46 regs := *initRegs 47 48 // Set our syscall number. 49 // r8 for the syscall number. 50 // r0-r6 is used to store the parameters. 51 regs.Regs[8] = uint64(sysno) 52 if len(args) >= 1 { 53 regs.Regs[0] = args[0].Uint64() 54 } 55 if len(args) >= 2 { 56 regs.Regs[1] = args[1].Uint64() 57 } 58 if len(args) >= 3 { 59 regs.Regs[2] = args[2].Uint64() 60 } 61 if len(args) >= 4 { 62 regs.Regs[3] = args[3].Uint64() 63 } 64 if len(args) >= 5 { 65 regs.Regs[4] = args[4].Uint64() 66 } 67 if len(args) >= 6 { 68 regs.Regs[5] = args[5].Uint64() 69 } 70 71 return regs 72 } 73 74 // isSingleStepping determines if the registers indicate single-stepping. 75 func isSingleStepping(regs *arch.Registers) bool { 76 // Refer to the ARM SDM D2.12.3: software step state machine 77 // return (regs.Pstate.SS == 1) && (MDSCR_EL1.SS == 1). 78 // 79 // Since the host Linux kernel will set MDSCR_EL1.SS on our behalf 80 // when we call a single-step ptrace command, we only need to check 81 // the Pstate.SS bit here. 82 return (regs.Pstate & arch.ARMTrapFlag) != 0 83 } 84 85 // updateSyscallRegs updates registers after finishing sysemu. 86 func updateSyscallRegs(regs *arch.Registers) { 87 // No special work is necessary. 88 return 89 } 90 91 // syscallReturnValue extracts a sensible return from registers. 92 func syscallReturnValue(regs *arch.Registers) (uintptr, error) { 93 rval := int64(regs.Regs[0]) 94 if rval < 0 { 95 return 0, unix.Errno(-rval) 96 } 97 return uintptr(rval), nil 98 } 99 100 func dumpRegs(regs *arch.Registers) string { 101 var m strings.Builder 102 103 fmt.Fprintf(&m, "Registers:\n") 104 105 for i := 0; i < 31; i++ { 106 fmt.Fprintf(&m, "\tRegs[%d]\t = %016x\n", i, regs.Regs[i]) 107 } 108 fmt.Fprintf(&m, "\tSp\t = %016x\n", regs.Sp) 109 fmt.Fprintf(&m, "\tPc\t = %016x\n", regs.Pc) 110 fmt.Fprintf(&m, "\tPstate\t = %016x\n", regs.Pstate) 111 112 return m.String() 113 } 114 115 // adjustInitregsRip adjust the current register RIP value to 116 // be just before the system call instruction excution 117 func (t *thread) adjustInitRegsRip() { 118 t.initRegs.Pc -= initRegsRipAdjustment 119 } 120 121 // Pass the expected PPID to the child via X7 when creating stub process 122 func initChildProcessPPID(initregs *arch.Registers, ppid int32) { 123 initregs.Regs[7] = uint64(ppid) 124 // R9 has to be set to 1 when creating stub process. 125 initregs.Regs[9] = 1 126 } 127 128 // patchSignalInfo patches the signal info to account for hitting the seccomp 129 // filters from vsyscall emulation, specified below. We allow for SIGSYS as a 130 // synchronous trap, but patch the structure to appear like a SIGSEGV with the 131 // Rip as the faulting address. 132 // 133 // Note that this should only be called after verifying that the signalInfo has 134 // been generated by the kernel. 135 func patchSignalInfo(regs *arch.Registers, signalInfo *linux.SignalInfo) { 136 if linux.Signal(signalInfo.Signo) == linux.SIGSYS { 137 signalInfo.Signo = int32(linux.SIGSEGV) 138 139 // Unwind the kernel emulation, if any has occurred. A SIGSYS is delivered 140 // with the si_call_addr field pointing to the current RIP. This field 141 // aligns with the si_addr field for a SIGSEGV, so we don't need to touch 142 // anything there. We do need to unwind emulation however, so we set the 143 // instruction pointer to the faulting value, and "unpop" the stack. 144 regs.Pc = signalInfo.Addr() 145 regs.Sp -= 8 146 } 147 } 148 149 // Noop on arm64. 150 // 151 //go:nosplit 152 func enableCpuidFault() { 153 } 154 155 // appendArchSeccompRules append architecture specific seccomp rules when creating BPF program. 156 // Ref attachedThread() for more detail. 157 func appendArchSeccompRules(rules []seccomp.RuleSet, defaultAction linux.BPFAction) []seccomp.RuleSet { 158 return rules 159 } 160 161 // probeSeccomp returns true if seccomp is run after ptrace notifications, 162 // which is generally the case for kernel version >= 4.8. 163 // 164 // On arm64, the support of PTRACE_SYSEMU was added in the 5.3 kernel, so 165 // probeSeccomp can always return true. 166 func probeSeccomp() bool { 167 return true 168 } 169 170 func (s *subprocess) arm64SyscallWorkaround(t *thread, regs *arch.Registers) { 171 // On ARM64, when ptrace stops on a system call, it uses the x7 172 // register to indicate whether the stop has been signalled from 173 // syscall entry or syscall exit. This means that we can't get a value 174 // of this register and we can't change it. More details are in the 175 // comment for tracehook_report_syscall in arch/arm64/kernel/ptrace.c. 176 // 177 // This happens only if we stop on a system call, so let's queue a 178 // signal, resume a stub thread and catch it on a signal handling. 179 t.NotifyInterrupt() 180 for { 181 if _, _, errno := unix.RawSyscall6( 182 unix.SYS_PTRACE, 183 unix.PTRACE_SYSEMU, 184 uintptr(t.tid), 0, 0, 0, 0); errno != 0 { 185 panic(fmt.Sprintf("ptrace sysemu failed: %v", errno)) 186 } 187 188 // Wait for the syscall-enter stop. 189 sig := t.wait(stopped) 190 if sig == unix.SIGSTOP { 191 // SIGSTOP was delivered to another thread in the same thread 192 // group, which initiated another group stop. Just ignore it. 193 continue 194 } 195 if sig == (syscallEvent | unix.SIGTRAP) { 196 t.dumpAndPanic(fmt.Sprintf("unexpected syscall event")) 197 } 198 break 199 } 200 if err := t.getRegs(regs); err != nil { 201 panic(fmt.Sprintf("ptrace get regs failed: %v", err)) 202 } 203 }