167 lines
4.3 KiB
Go
167 lines
4.3 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package verify
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestExtractSignatures(t *testing.T) {
|
|
src := `// func add(a int64, b int64) int64
|
|
TEXT ·add(SB), NOSPLIT, $0-24
|
|
RET
|
|
|
|
// func wideCopy(dst []byte, src []byte)
|
|
TEXT ·wideCopy(SB), NOSPLIT, $0-48
|
|
RET
|
|
`
|
|
sigs := ExtractSignatures(src)
|
|
if len(sigs) != 2 {
|
|
t.Fatalf("expected 2 signatures, got %d: %v", len(sigs), sigs)
|
|
}
|
|
add, ok := sigs["add"]
|
|
if !ok {
|
|
t.Fatal("add not found")
|
|
}
|
|
if len(add.params) != 2 {
|
|
t.Errorf("add params: got %d, want 2", len(add.params))
|
|
}
|
|
wc, ok := sigs["wideCopy"]
|
|
if !ok {
|
|
t.Fatal("wideCopy not found")
|
|
}
|
|
if len(wc.params) != 2 {
|
|
t.Errorf("wideCopy params: got %d, want 2", len(wc.params))
|
|
}
|
|
if wc.params[0].typ != "[]byte" {
|
|
t.Errorf("wideCopy param[0].typ = %q, want []byte", wc.params[0].typ)
|
|
}
|
|
}
|
|
|
|
func TestFuzzWideCopy(t *testing.T) {
|
|
k := loadBasic(t)
|
|
|
|
gt, err := GroundTruth("../testdata/verify/basic_amd64.s")
|
|
if err != nil {
|
|
t.Fatalf("GroundTruth: %v", err)
|
|
}
|
|
goCode, ok := gt["wideCopy"]
|
|
if !ok {
|
|
t.Skip("wideCopy not in ground truth")
|
|
}
|
|
|
|
sig := funcSig{
|
|
name: "wideCopy",
|
|
params: []param{
|
|
{name: "dst", typ: "[]byte"},
|
|
{name: "src", typ: "[]byte"},
|
|
},
|
|
}
|
|
|
|
res := k.FuzzFunc("wideCopy", sig, goCode, 200, 42)
|
|
if !res.OK() {
|
|
t.Errorf("wideCopy fuzz: %s", res)
|
|
}
|
|
}
|
|
|
|
// TestFuzzFuncSigWiderThanFrame pins the guard against a // func comment
|
|
// that declares more parameter bytes than the TEXT frame carries: before
|
|
// the guard, slicing the result area at paramsSize(sig) past the end of
|
|
// the argument block panicked the whole test binary.
|
|
func TestFuzzFuncSigWiderThanFrame(t *testing.T) {
|
|
k := loadBasic(t)
|
|
|
|
gt, err := GroundTruth("../testdata/verify/basic_amd64.s")
|
|
if err != nil {
|
|
t.Skipf("go tool asm unavailable: %v", err)
|
|
}
|
|
goCode, ok := gt["add"]
|
|
if !ok {
|
|
t.Skip("add not in ground truth")
|
|
}
|
|
|
|
// add carries $0-24; four int parameters declare 32 bytes.
|
|
sig := funcSig{
|
|
name: "add",
|
|
params: []param{
|
|
{name: "a", typ: "int"},
|
|
{name: "b", typ: "int"},
|
|
{name: "c", typ: "int"},
|
|
{name: "d", typ: "int"},
|
|
},
|
|
}
|
|
|
|
res := k.FuzzFunc("add", sig, goCode, 3, 42)
|
|
if res.OK() {
|
|
t.Fatal("expected the over-wide signature to fail the campaign")
|
|
}
|
|
if !strings.Contains(res.FirstFail, "parameter bytes") || !strings.Contains(res.FirstFail, "argument bytes") {
|
|
t.Errorf("FirstFail = %q, want a clear signature-versus-frame message", res.FirstFail)
|
|
}
|
|
}
|
|
|
|
// TestFuzzStringParam runs the differential fuzzer over a kernel whose
|
|
// only parameter is a string: the marshaller lays out a real two-word
|
|
// header (data pointer + length) and the comparison slices at
|
|
// paramsSize(sig) = 16, so the length word is input, not result.
|
|
func TestFuzzStringParam(t *testing.T) {
|
|
k := loadStrProbeKernel(t)
|
|
|
|
file := t.TempDir() + "/strprobe_amd64.s"
|
|
if err := os.WriteFile(file, []byte(strProbeSrc), 0o644); err != nil {
|
|
t.Fatalf("write kernel: %v", err)
|
|
}
|
|
gt, err := GroundTruth(file)
|
|
if err != nil {
|
|
t.Skipf("go tool asm unavailable: %v", err)
|
|
}
|
|
goCode, ok := gt["strProbe"]
|
|
if !ok {
|
|
t.Skip("strProbe not in ground truth")
|
|
}
|
|
|
|
sig, ok := parseFuncSig("// func strProbe(s string) int64")
|
|
if !ok {
|
|
t.Fatal("parseFuncSig failed")
|
|
}
|
|
res := k.FuzzFunc("strProbe", sig, goCode, 100, 42)
|
|
if !res.OK() {
|
|
t.Errorf("strProbe fuzz: %s", res)
|
|
}
|
|
}
|
|
|
|
func TestParseFuncSig(t *testing.T) {
|
|
tests := []struct {
|
|
comment string
|
|
name string
|
|
nParams int
|
|
}{
|
|
{"// func add(a int64, b int64) int64", "add", 2},
|
|
{"// func wideCopy(dst []byte, src []byte)", "wideCopy", 2},
|
|
{"// func analyzeO1RangeAVX2(swin []int32, dstP []uint32, hist *[32]uint16) (partSum uint64, overflow bool)", "analyzeO1RangeAVX2", 3},
|
|
{"// not a func", "", 0},
|
|
}
|
|
for _, tt := range tests {
|
|
sig, ok := parseFuncSig(tt.comment)
|
|
if tt.name == "" {
|
|
if ok {
|
|
t.Errorf("parseFuncSig(%q): expected not ok", tt.comment)
|
|
}
|
|
continue
|
|
}
|
|
if !ok {
|
|
t.Errorf("parseFuncSig(%q): expected ok", tt.comment)
|
|
continue
|
|
}
|
|
if sig.name != tt.name {
|
|
t.Errorf("parseFuncSig(%q).name = %q, want %q", tt.comment, sig.name, tt.name)
|
|
}
|
|
if len(sig.params) != tt.nParams {
|
|
t.Errorf("parseFuncSig(%q): %d params, want %d", tt.comment, len(sig.params), tt.nParams)
|
|
}
|
|
}
|
|
}
|