// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package verify import ( "os" "testing" ) func loadABIKernel(t *testing.T) *Kernel { t.Helper() requireHost(t, "amd64") k, err := Load("../testdata/verify/abi_amd64.s") if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) return k } func TestABIClean(t *testing.T) { k := loadABIKernel(t) args := make([]byte, 24) PutUint64(args, 0, 3) PutUint64(args, 8, 4) out, report, err := k.CallFuncChecked("cleanAdd", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 16)); got != 7 { t.Errorf("cleanAdd(3, 4) = %d, want 7", got) } if !report.OK() { t.Errorf("cleanAdd: %s", report) } } func TestABIBPClobbered(t *testing.T) { k := loadABIKernel(t) args := make([]byte, 16) PutUint64(args, 0, 42) out, report, err := k.CallFuncChecked("dirtyBP", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 8)); got != 42 { t.Errorf("dirtyBP(42) = %d, want 42", got) } if !report.FPClobbered { t.Error("dirtyBP: expected BP clobbered, but report says clean") } if report.GClobbered { t.Error("dirtyBP: R14 should not be clobbered") } } func TestABIR14Clobbered(t *testing.T) { k := loadABIKernel(t) args := make([]byte, 16) PutUint64(args, 0, 99) out, report, err := k.CallFuncChecked("dirtyR14", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 8)); got != 99 { t.Errorf("dirtyR14(99) = %d, want 99", got) } if !report.GClobbered { t.Error("dirtyR14: expected R14 clobbered, but report says clean") } if report.FPClobbered { t.Error("dirtyR14: BP should not be clobbered") } } func TestCallFuncCheckedErrors(t *testing.T) { k := loadABIKernel(t) // Nonexistent function. _, _, err := k.CallFuncChecked("nope", make([]byte, 8)) if err == nil { t.Fatal("expected error for nonexistent function") } // Arg block too small. _, _, err = k.CallFuncChecked("cleanAdd", make([]byte, 8)) if err == nil { t.Fatal("expected error for too-small arg block") } } // loadCanaryKernel writes an inline kernel pair that exercises the canary // geometry against declared frames: frameLocal owns a $96 local frame and // writes its lowest local (96 bytes below the entry stack pointer, well // past the 64-byte call margin a frame-0 kernel gets); belowFrame writes // 136 bytes below its own frame, deep into the canary window. func loadCanaryKernel(t *testing.T) *Kernel { t.Helper() requireHost(t, "amd64") src := `#include "textflag.h" // func frameLocal(x int64) int64 TEXT ·frameLocal(SB), NOSPLIT, $96-16 MOVQ x+0(FP), AX MOVQ AX, l-96(SP) MOVQ l-96(SP), AX MOVQ AX, ret+8(FP) RET // func belowFrame(x int64) int64 TEXT ·belowFrame(SB), NOSPLIT, $96-16 MOVQ $1, -136(SP) MOVQ x+0(FP), AX MOVQ AX, ret+8(FP) RET ` file := t.TempDir() + "/canary_amd64.s" if err := os.WriteFile(file, []byte(src), 0o644); err != nil { t.Fatalf("write kernel: %v", err) } k, err := Load(file) if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) return k } // TestCallCheckedFrameLegal checks that a kernel whose declared frame // extends below the fixed 64-byte margin does not trip the canary: the // protected gap must adapt to the frame the TEXT directive declares. func TestCallCheckedFrameLegal(t *testing.T) { k := loadCanaryKernel(t) args := make([]byte, 16) PutUint64(args, 0, 42) out, report, err := k.CallFuncChecked("frameLocal", args) if err != nil { t.Fatalf("CallFuncChecked(frameLocal): %v", err) } if got := int64(GetUint64(out, 8)); got != 42 { t.Errorf("frameLocal(42) = %d, want 42", got) } if report.RedZoneHit { t.Error("frameLocal: writing its own $96 frame must not count as a red-zone hit") } if !report.OK() { t.Errorf("frameLocal: %s", report) } } // TestCallCheckedBelowFrameCaught checks the other side of the adaptive // gap: a kernel that writes below its own frame by more than the call // margin must still be reported. func TestCallCheckedBelowFrameCaught(t *testing.T) { k := loadCanaryKernel(t) args := make([]byte, 16) PutUint64(args, 0, 42) out, report, err := k.CallFuncChecked("belowFrame", args) if err != nil { t.Fatalf("CallFuncChecked(belowFrame): %v", err) } if got := int64(GetUint64(out, 8)); got != 42 { t.Errorf("belowFrame(42) = %d, want 42", got) } if !report.RedZoneHit { t.Error("belowFrame: expected RedZoneHit for a write below the declared frame") } }