fix(verify): isolate smoke and abi sweeps in a child process
Assisted-by: GLM 5.3
This commit is contained in:
+119
-43
@@ -1051,7 +1051,8 @@ decoders) that crash on random input but should succeed on valid data.
|
||||
groundTruth := fs.Bool("ground-truth", false, "compare machine code byte-for-byte against go tool asm")
|
||||
fuzz := fs.Bool("fuzz", false, "differential fuzz: JIT both gasm and go-tool-asm versions, compare outputs")
|
||||
fuzzN := fs.Int("n", 1000, "number of fuzz iterations per function")
|
||||
fuzzOne := fs.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode)
|
||||
fuzzOne := fs.String("fuzz-one", "", "") // hidden: fuzz 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")
|
||||
bufSpec := fs.String("buf", "", "buffer spec for -call: name:size:pattern[,name:size:pattern] (zero, ones, seq, or hex)")
|
||||
repeat := fs.Int("repeat", 1, "number of times to repeat a -call invocation")
|
||||
@@ -1126,6 +1127,25 @@ decoders) that crash on random input but should succeed on valid data.
|
||||
return 0
|
||||
}
|
||||
|
||||
// Subprocess mode: run the smoke/abi checks for a single function and
|
||||
// exit with the accumulated status. The parent interprets a clean exit
|
||||
// as success, a non-zero exit as failure and death-by-signal as a crash.
|
||||
if *sweepOne != "" {
|
||||
fl, err := k.Func(*sweepOne)
|
||||
if err != nil || !fl.NoSplit {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: %s: %v\n", *sweepOne, err)
|
||||
return 1
|
||||
}
|
||||
msgs, failed := runSweepChecks(k, path, *sweepOne, fl, *smoke, *abi, *abiN)
|
||||
for _, m := range msgs {
|
||||
fmt.Println(m)
|
||||
}
|
||||
if failed {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Ground-truth comparison: assemble with go tool asm and compare bytes.
|
||||
if *groundTruth {
|
||||
gt, err := verify.GroundTruth(path)
|
||||
@@ -1241,7 +1261,10 @@ decoders) that crash on random input but should succeed on valid data.
|
||||
}
|
||||
}
|
||||
|
||||
// Run smoke and ABI checks in parallel.
|
||||
// Run smoke and ABI checks in parallel, each function in its own child
|
||||
// process: the JIT'd code runs with zeroed or fuzzed arguments, and a
|
||||
// function that dereferences them faults — the crash is reported as a
|
||||
// CRASH line instead of killing this process (mirrors fuzzInSubprocess).
|
||||
if *smoke || *abi {
|
||||
type checkResult struct {
|
||||
name string
|
||||
@@ -1259,53 +1282,16 @@ decoders) that crash on random input but should succeed on valid data.
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(name string, fl asm.FuncLayout) {
|
||||
go func(name string) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
var msgs []string
|
||||
failed := false
|
||||
|
||||
if *smoke {
|
||||
args := make([]byte, fl.Args)
|
||||
_, err := k.CallFunc(name, args)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf(" smoke: FAIL — %v", err))
|
||||
failed = true
|
||||
} else {
|
||||
msgs = append(msgs, " smoke: OK")
|
||||
}
|
||||
}
|
||||
|
||||
if *abi {
|
||||
if src, err := readSource(path); err == nil {
|
||||
result := k.FuzzFuncCheckedByName(name, src, *abiN, int64(*abiN))
|
||||
if result.Mismatches > 0 {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: %s", result))
|
||||
failed = true
|
||||
} else {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: clean (%d varied inputs)", result.Matches))
|
||||
}
|
||||
} else {
|
||||
args := make([]byte, fl.Args)
|
||||
_, report, err := k.CallFuncChecked(name, args)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: FAIL — %v", err))
|
||||
failed = true
|
||||
} else if !report.OK() {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: %s", report))
|
||||
failed = true
|
||||
} else {
|
||||
msgs = append(msgs, " abi: clean")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg, fail := sweepInSubprocess(path, name, *smoke, *abi, *abiN)
|
||||
mu.Lock()
|
||||
results = append(results, checkResult{name: name, msg: strings.Join(msgs, "\n"), fail: failed})
|
||||
results = append(results, checkResult{name: name, msg: msg, fail: fail})
|
||||
mu.Unlock()
|
||||
}(name, fl)
|
||||
}(name)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -1363,6 +1349,96 @@ func fuzzInSubprocess(path, funcName string, n int) string {
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
// sweepInSubprocess runs the smoke/abi checks for a single function in a
|
||||
// child process. If the child is killed by a signal (e.g. SIGSEGV from a
|
||||
// function that dereferences its zeroed or fuzzed arguments), it returns a
|
||||
// CRASH report instead of dying — the same isolation fuzzInSubprocess
|
||||
// provides for the fuzz sweep.
|
||||
func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string, bool) {
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Sprintf(" smoke/abi: FAIL — cannot find self: %v", err), true
|
||||
}
|
||||
args := []string{"verify", "--sweep-one=" + funcName}
|
||||
if smoke {
|
||||
args = append(args, "-smoke")
|
||||
}
|
||||
if abi {
|
||||
args = append(args, "-abi", "-abi-n", strconv.Itoa(abiN))
|
||||
}
|
||||
args = append(args, path)
|
||||
cmd := exec.Command(self, args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
ws, ok := exitErr.Sys().(syscall.WaitStatus)
|
||||
if ok && ws.Signaled() {
|
||||
return fmt.Sprintf(" smoke/abi: CRASH (%v — the function faults on zeroed or fuzzed\n arguments; verify it with -call and valid buffers)", ws.Signal()), true
|
||||
}
|
||||
}
|
||||
// Non-zero exit without a signal: the checks themselves failed and
|
||||
// the child already printed the diagnostic lines.
|
||||
return sweepCheckLines(out), true
|
||||
}
|
||||
return sweepCheckLines(out), false
|
||||
}
|
||||
|
||||
// sweepCheckLines extracts the check-result lines from child output,
|
||||
// dropping the child's own file/function banners (the parent prints those).
|
||||
func sweepCheckLines(out []byte) string {
|
||||
var lines []string
|
||||
for _, l := range strings.Split(string(out), "\n") {
|
||||
t := strings.TrimSpace(l)
|
||||
if strings.HasPrefix(t, "smoke:") || strings.HasPrefix(t, "abi:") {
|
||||
lines = append(lines, " "+t)
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// runSweepChecks performs the in-process smoke and ABI checks for one
|
||||
// function — the child half of sweepInSubprocess.
|
||||
func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smoke, abi bool, abiN int) ([]string, bool) {
|
||||
var msgs []string
|
||||
failed := false
|
||||
|
||||
if smoke {
|
||||
args := make([]byte, fl.Args)
|
||||
_, err := k.CallFunc(name, args)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf(" smoke: FAIL — %v", err))
|
||||
failed = true
|
||||
} else {
|
||||
msgs = append(msgs, " smoke: OK")
|
||||
}
|
||||
}
|
||||
|
||||
if abi {
|
||||
if src, err := readSource(path); err == nil {
|
||||
result := k.FuzzFuncCheckedByName(name, src, abiN, int64(abiN))
|
||||
if result.Mismatches > 0 {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: %s", result))
|
||||
failed = true
|
||||
} else {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: clean (%d varied inputs)", result.Matches))
|
||||
}
|
||||
} else {
|
||||
args := make([]byte, fl.Args)
|
||||
_, report, err := k.CallFuncChecked(name, args)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: FAIL — %v", err))
|
||||
failed = true
|
||||
} else if !report.OK() {
|
||||
msgs = append(msgs, fmt.Sprintf(" abi: %s", report))
|
||||
failed = true
|
||||
} else {
|
||||
msgs = append(msgs, " abi: clean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgs, failed
|
||||
}
|
||||
|
||||
// cmdVerifyCall implements `gasm verify --call <func> [--buf spec] [--repeat n]`.
|
||||
// It invokes a single function with user-supplied buffers and prints the arg
|
||||
// block before and after the call, so the user can inspect return values and
|
||||
|
||||
Reference in New Issue
Block a user