// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause //go:build amd64 || arm64 || riscv64 || loong64 package verify import ( "encoding/binary" "fmt" "syscall" "unsafe" ) // CallChecked invokes the function at fnAddr with ABI sentinels and a // canary below SP, returning both the argument block (with results) and an // ABIReport. // // The architecture-specific parts live in abi_.s: enterJITChecked // plants sentinels in the registers the Go ABI fixes across calls (the // frame pointer and the goroutine pointer) before switching to the // prepared stack, and the raw return trampoline leaveJITCheckedRaw // compares them and records violations in abiResult. func CallChecked(fnAddr uintptr, args []byte) ([]byte, ABIReport, error) { report := ABIReport{} // Reset the global result. abiResult = 0 // Prepare the stack: [canary][padding][leaveJITCheckedRaw][args...] // The canary sits below the initial SP, so the function would have to // write below SP to corrupt it. totalSize := redZoneSize + stackPad + 8 + len(args) + 64 stackMem, err := syscall.Mmap(-1, 0, totalSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_PRIVATE|syscall.MAP_ANON) if err != nil { return nil, report, fmt.Errorf("verify: stack mmap: %w", err) } defer func() { _ = syscall.Munmap(stackMem) }() // Fill the canary window with the detection pattern. for i := range redZoneSize { stackMem[i] = redZoneFill } // Return address and args after the canary and padding. retOff := redZoneSize + stackPad binary.LittleEndian.PutUint64(stackMem[retOff:retOff+8], uint64(leaveCheckedPtr)) copy(stackMem[retOff+8:], args) stackBase := uintptr(unsafe.Pointer(&stackMem[retOff])) enterJITChecked(fnAddr, stackBase) // Read the register-clobber result. res := abiResult report.FPClobbered = res&1 != 0 report.GClobbered = res&2 != 0 // Check the canary window. for i := range redZoneSize { if stackMem[i] != redZoneFill { report.RedZoneHit = true break } } // Copy out the argument area. out := make([]byte, len(args)) copy(out, stackMem[retOff+8:retOff+8+len(args)]) return out, report, nil }