fix(verify): arm64 stack save, adaptive canary and host gating
Assisted-by: GLM 5.3
This commit is contained in:
+80
-2
@@ -4,8 +4,8 @@
|
||||
package verify
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
func loadBasicKernel(t *testing.T) *Kernel {
|
||||
t.Helper()
|
||||
requireHost(t, "amd64")
|
||||
k, err := Load("../testdata/verify/basic_amd64.s")
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
@@ -71,7 +72,7 @@ func TestGenDualArgsEntryReplayable(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("parseFuncSig failed")
|
||||
}
|
||||
_, _, bufs, entry := genDualArgs(rand.New(rand.NewSource(1)), sig, 24)
|
||||
_, _, bufs, entry := genDualArgs(newRNG(1), sig, 24)
|
||||
if len(entry.Args) != 2 || entry.Args[0].Kind != "int" {
|
||||
t.Fatalf("unexpected entry: %+v", entry)
|
||||
}
|
||||
@@ -96,11 +97,88 @@ func entryInt(t *testing.T, a CorpusArg) int64 {
|
||||
return int64(v)
|
||||
}
|
||||
|
||||
// strProbeSrc is a kernel that consumes an ABI0 string header: it
|
||||
// dereferences the data pointer (proving it points at live memory) and
|
||||
// returns len(s) + s[0] when the string is non-empty, len(s) otherwise.
|
||||
const strProbeSrc = `#include "textflag.h"
|
||||
|
||||
// func strProbe(s string) int64
|
||||
TEXT ·strProbe(SB), NOSPLIT, $0-24
|
||||
MOVQ s_base+0(FP), SI
|
||||
MOVQ s_len+8(FP), CX
|
||||
XORQ AX, AX
|
||||
TESTQ CX, CX
|
||||
JZ probe_done
|
||||
MOVB (SI), AL
|
||||
ADDQ AX, CX
|
||||
|
||||
probe_done:
|
||||
MOVQ CX, ret+16(FP)
|
||||
RET
|
||||
`
|
||||
|
||||
func loadStrProbeKernel(t *testing.T) *Kernel {
|
||||
t.Helper()
|
||||
requireHost(t, "amd64")
|
||||
file := t.TempDir() + "/strprobe_amd64.s"
|
||||
if err := os.WriteFile(file, []byte(strProbeSrc), 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
|
||||
}
|
||||
|
||||
// TestStringParamRoundTrip pins the ABI0 string marshalling end to end:
|
||||
// genDualArgs lays a string parameter out as a two-word header pointing at
|
||||
// a live buffer, the corpus entry records it, and ReplayEntry rebuilds an
|
||||
// equivalent header.
|
||||
func TestStringParamRoundTrip(t *testing.T) {
|
||||
k := loadStrProbeKernel(t)
|
||||
|
||||
sig, ok := parseFuncSig("// func strProbe(s string) int64")
|
||||
if !ok {
|
||||
t.Fatal("parseFuncSig failed")
|
||||
}
|
||||
args, _, bufs, entry := genDualArgs(newRNG(7), sig, 24)
|
||||
if len(entry.Args) != 1 || entry.Args[0].Kind != "string" {
|
||||
t.Fatalf("unexpected entry: %+v", entry)
|
||||
}
|
||||
|
||||
out, err := k.CallFunc("strProbe", args)
|
||||
if err != nil {
|
||||
t.Fatalf("CallFunc: %v", err)
|
||||
}
|
||||
live := int64(GetUint64(out, 16))
|
||||
want := int64(entry.Args[0].Len)
|
||||
if d, err := hex.DecodeString(entry.Args[0].Data); err != nil {
|
||||
t.Fatalf("entry data: %v", err)
|
||||
} else if len(d) > 0 {
|
||||
want += int64(d[0])
|
||||
}
|
||||
if live != want {
|
||||
t.Errorf("live call = %d, want %d (len + first byte)", live, want)
|
||||
}
|
||||
|
||||
replayed, err := k.ReplayEntry("strProbe", entry)
|
||||
if err != nil {
|
||||
t.Fatalf("ReplayEntry: %v", err)
|
||||
}
|
||||
if got := int64(GetUint64(replayed, 16)); got != want {
|
||||
t.Errorf("replayed call = %d, want %d", got, want)
|
||||
}
|
||||
runtime.KeepAlive(bufs)
|
||||
}
|
||||
|
||||
// TestFuzzHookSavesFailures fuzzes add against the go-tool-asm build of a
|
||||
// sub kernel with the same signature, so every iteration mismatches (safely:
|
||||
// both kernels read only their own arguments) and the hook must record
|
||||
// replayable entries.
|
||||
func TestFuzzHookSavesFailures(t *testing.T) {
|
||||
requireHost(t, "amd64")
|
||||
src := `#include "textflag.h"
|
||||
|
||||
// func add(a, b int) int
|
||||
|
||||
Reference in New Issue
Block a user