fix(verify): arm64 stack save, adaptive canary and host gating
Assisted-by: GLM 5.3
This commit is contained in:
@@ -4,11 +4,13 @@
|
||||
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)
|
||||
@@ -93,3 +95,85 @@ func TestCallFuncCheckedErrors(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user