feat(verify): scalar arguments for -call invocations
This commit is contained in:
+28
-6
@@ -1067,6 +1067,7 @@ decoders) that crash on random input but should succeed on valid data.
|
|||||||
sweepOne := fs.String("sweep-one", "", "") // hidden: smoke/abi a single function (subprocess mode)
|
sweepOne := fs.String("sweep-one", "", "") // hidden: smoke/abi a single function (subprocess mode)
|
||||||
call := fs.String("call", "", "call a single function with -buf instead of the sweeps")
|
call := fs.String("call", "", "call a single function with -buf instead of the sweeps")
|
||||||
bufSpec := fs.String("buf", "", "buffer spec for -call: name:size:pattern[,name:size:pattern] (zero, ones, seq, or hex)")
|
bufSpec := fs.String("buf", "", "buffer spec for -call: name:size:pattern[,name:size:pattern] (zero, ones, seq, or hex)")
|
||||||
|
scalarSpec := fs.String("args", "", "scalar args for -call: name=value[,name=value] (decimal or 0x hex)")
|
||||||
repeat := fs.Int("repeat", 1, "number of times to repeat a -call invocation")
|
repeat := fs.Int("repeat", 1, "number of times to repeat a -call invocation")
|
||||||
fs.Parse(args)
|
fs.Parse(args)
|
||||||
if fs.NArg() != 1 {
|
if fs.NArg() != 1 {
|
||||||
@@ -1105,7 +1106,7 @@ decoders) that crash on random input but should succeed on valid data.
|
|||||||
|
|
||||||
// Single-function call mode: invoke one function with user-supplied buffers.
|
// Single-function call mode: invoke one function with user-supplied buffers.
|
||||||
if *call != "" {
|
if *call != "" {
|
||||||
return cmdVerifyCall(k, path, *call, *bufSpec, *repeat)
|
return cmdVerifyCall(k, path, *call, *bufSpec, *scalarSpec, *repeat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subprocess mode: fuzz a single function and exit.
|
// Subprocess mode: fuzz a single function and exit.
|
||||||
@@ -1451,11 +1452,11 @@ func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smok
|
|||||||
return msgs, failed
|
return msgs, failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdVerifyCall implements `gasm verify --call <func> [--buf spec] [--repeat n]`.
|
// cmdVerifyCall implements `gasm verify --call <func> [--buf spec] [--args spec] [--repeat n]`.
|
||||||
// It invokes a single function with user-supplied buffers and prints the arg
|
// It invokes a single function with user-supplied buffers and scalar
|
||||||
// block before and after the call, so the user can inspect return values and
|
// arguments and prints the arg block before and after the call, so the user
|
||||||
// any output written to the buffers.
|
// can inspect return values and any output written to the buffers.
|
||||||
func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec string, repeat int) int {
|
func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec, scalarSpec string, repeat int) int {
|
||||||
fl, err := k.Func(funcName)
|
fl, err := k.Func(funcName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
@@ -1493,6 +1494,16 @@ func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec string, repeat int)
|
|||||||
defer pool.Close()
|
defer pool.Close()
|
||||||
args := pool.BuildArgs(layout, fl.Args)
|
args := pool.BuildArgs(layout, fl.Args)
|
||||||
|
|
||||||
|
scalars, err := verify.ParseScalarArgs(scalarSpec)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := verify.ApplyScalarArgs(args, layout, scalars); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("%s: %d bytes, args=%d\n", funcName, fl.Size, fl.Args)
|
fmt.Printf("%s: %d bytes, args=%d\n", funcName, fl.Size, fl.Args)
|
||||||
fmt.Printf(" signature: func %s(%s) %s\n", sig.Name, formatParams(sig.Params), formatResults(sig.Results))
|
fmt.Printf(" signature: func %s(%s) %s\n", sig.Name, formatParams(sig.Params), formatResults(sig.Results))
|
||||||
if len(specs) > 0 {
|
if len(specs) > 0 {
|
||||||
@@ -1501,6 +1512,17 @@ func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec string, repeat int)
|
|||||||
fmt.Printf(" %s: %d bytes, pattern=%s\n", s.Name, s.Size, s.Pattern)
|
fmt.Printf(" %s: %d bytes, pattern=%s\n", s.Name, s.Size, s.Pattern)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(scalars) > 0 {
|
||||||
|
names := make([]string, 0, len(scalars))
|
||||||
|
for n := range scalars {
|
||||||
|
names = append(names, n)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
fmt.Printf(" scalars:\n")
|
||||||
|
for _, n := range names {
|
||||||
|
fmt.Printf(" %s = %d\n", n, scalars[n])
|
||||||
|
}
|
||||||
|
}
|
||||||
fmt.Printf(" args before: %s\n", hexDump(args))
|
fmt.Printf(" args before: %s\n", hexDump(args))
|
||||||
|
|
||||||
rc := 0
|
rc := 0
|
||||||
|
|||||||
@@ -148,3 +148,73 @@ func fillBuffer(buf []byte, pattern string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyScalarArgs writes user-supplied scalar argument values into an ABI0
|
||||||
|
// argument block after BuildArgs. Each name=value pair addresses the layout
|
||||||
|
// entry of that parameter name; only word-sized scalar parameters (int,
|
||||||
|
// int64, uint64, and their named spellings) accept values, so a typo'd name
|
||||||
|
// or a slice parameter fails loudly instead of silently corrupting the call.
|
||||||
|
func ApplyScalarArgs(args []byte, layout []ArgOffset, scalars map[string]uint64) error {
|
||||||
|
if len(scalars) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
byName := make(map[string]ArgOffset, len(layout))
|
||||||
|
for _, l := range layout {
|
||||||
|
byName[l.Name] = l
|
||||||
|
}
|
||||||
|
for name, val := range scalars {
|
||||||
|
l, ok := byName[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("scalar arg %q: no such parameter", name)
|
||||||
|
}
|
||||||
|
if l.IsPtr || l.Size != 8 {
|
||||||
|
return fmt.Errorf("scalar arg %q: parameter has type %s; only word-sized scalars accept values", name, l.Typ)
|
||||||
|
}
|
||||||
|
if l.Offset+8 > len(args) {
|
||||||
|
return fmt.Errorf("scalar arg %q: offset %d outside the %d-byte arg block", name, l.Offset, len(args))
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint64(args[l.Offset:], val)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseScalarArgs parses a "name=value[,name=value...]" spec into a map.
|
||||||
|
// Values are decimal or 0x-prefixed hex.
|
||||||
|
func ParseScalarArgs(spec string) (map[string]uint64, error) {
|
||||||
|
out := map[string]uint64{}
|
||||||
|
if strings.TrimSpace(spec) == "" {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
for _, part := range strings.Split(spec, ",") {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, val, err := splitScalar(part)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[name] = val
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitScalar(part string) (string, uint64, error) {
|
||||||
|
eq := strings.Index(part, "=")
|
||||||
|
if eq <= 0 {
|
||||||
|
return "", 0, fmt.Errorf("scalar arg spec %q: want name=value", part)
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(part[:eq])
|
||||||
|
valStr := strings.TrimSpace(part[eq+1:])
|
||||||
|
var val uint64
|
||||||
|
var err error
|
||||||
|
if strings.HasPrefix(valStr, "0x") || strings.HasPrefix(valStr, "0X") {
|
||||||
|
_, err = fmt.Sscanf(valStr, "0x%x", &val)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Sscanf(valStr, "%d", &val)
|
||||||
|
}
|
||||||
|
if err != nil || name == "" {
|
||||||
|
return "", 0, fmt.Errorf("scalar arg spec %q: bad value", part)
|
||||||
|
}
|
||||||
|
return name, val, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,3 +156,41 @@ func TestBufPoolBuildArgs(t *testing.T) {
|
|||||||
t.Errorf("src.len = %d, want 128", args[32])
|
t.Errorf("src.len = %d, want 128", args[32])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseScalarArgs(t *testing.T) {
|
||||||
|
m, err := ParseScalarArgs("n=57,base=0x1f,x= 12 ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if m["n"] != 57 || m["base"] != 0x1f || m["x"] != 12 {
|
||||||
|
t.Fatalf("parsed %+v", m)
|
||||||
|
}
|
||||||
|
if _, err := ParseScalarArgs("n"); err == nil {
|
||||||
|
t.Error("name-only spec must fail")
|
||||||
|
}
|
||||||
|
if _, err := ParseScalarArgs("n=zz"); err == nil {
|
||||||
|
t.Error("bad value must fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyScalarArgs(t *testing.T) {
|
||||||
|
layout := []ArgOffset{
|
||||||
|
{Name: "text", Typ: "*byte", Offset: 0, Size: 8, IsPtr: true},
|
||||||
|
{Name: "n", Typ: "int", Offset: 8, Size: 8, IsPtr: false},
|
||||||
|
}
|
||||||
|
args := make([]byte, 16)
|
||||||
|
if err := ApplyScalarArgs(args, layout, map[string]uint64{"n": 0x1f}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := args[8]; got != 0x1f {
|
||||||
|
t.Fatalf("args[8] = %d, want 31", got)
|
||||||
|
}
|
||||||
|
// Pointer parameters reject scalar writes.
|
||||||
|
if err := ApplyScalarArgs(args, layout, map[string]uint64{"text": 5}); err == nil {
|
||||||
|
t.Fatal("pointer param must reject a scalar write")
|
||||||
|
}
|
||||||
|
// Unknown names reject.
|
||||||
|
if err := ApplyScalarArgs(args, layout, map[string]uint64{"nope": 1}); err == nil {
|
||||||
|
t.Fatal("unknown param must reject")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user