aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/manager
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/manager')
-rw-r--r--pkg/manager/seeds.go101
-rw-r--r--pkg/manager/seeds_test.go29
2 files changed, 120 insertions, 10 deletions
diff --git a/pkg/manager/seeds.go b/pkg/manager/seeds.go
index 3a83a0788..2d769d43b 100644
--- a/pkg/manager/seeds.go
+++ b/pkg/manager/seeds.go
@@ -4,11 +4,15 @@
package manager
import (
+ "bufio"
+ "bytes"
+ "errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"runtime"
+ "strings"
"sync"
"time"
@@ -94,13 +98,19 @@ func LoadSeeds(cfg *mgrconfig.Config, immutable bool) Seeds {
close(inputs)
}()
brokenSeeds := 0
+ skippedSeeds := 0
var brokenCorpus []string
var candidates []fuzzer.Candidate
for inp := range outputs {
if inp.Prog == nil {
if inp.IsSeed {
- brokenSeeds++
- log.Logf(0, "seed %s is broken: %s", inp.Path, inp.Err)
+ if errors.Is(inp.Err, ErrSkippedTest) {
+ skippedSeeds++
+ log.Logf(2, "seed %s is skipped: %s", inp.Path, inp.Err)
+ } else {
+ brokenSeeds++
+ log.Logf(0, "seed %s is broken: %s", inp.Path, inp.Err)
+ }
} else {
brokenCorpus = append(brokenCorpus, inp.Key)
}
@@ -123,6 +133,9 @@ func LoadSeeds(cfg *mgrconfig.Config, immutable bool) Seeds {
if len(brokenCorpus)+brokenSeeds != 0 {
log.Logf(0, "broken programs in the corpus: %v, broken seeds: %v", len(brokenCorpus), brokenSeeds)
}
+ if skippedSeeds != 0 {
+ log.Logf(0, "skipped %v seeds", skippedSeeds)
+ }
if !immutable {
// This needs to be done outside of the loop above to not race with corpusDB reads.
for _, sig := range brokenCorpus {
@@ -179,28 +192,96 @@ func versionToFlags(version uint64) fuzzer.ProgFlags {
}
func ParseSeed(target *prog.Target, data []byte) (*prog.Prog, error) {
- return parseProg(target, data, prog.NonStrict)
+ p, _, err := parseProg(target, data, prog.NonStrict, nil)
+ return p, err
+}
+
+func ParseSeedWithRequirements(target *prog.Target, data []byte, reqs map[string]bool) (
+ *prog.Prog, map[string]bool, error) {
+ return parseProg(target, data, prog.Strict, reqs)
}
-func ParseSeedStrict(target *prog.Target, data []byte) (*prog.Prog, error) {
- return parseProg(target, data, prog.Strict)
+func parseRequires(data []byte) map[string]bool {
+ requires := make(map[string]bool)
+ for s := bufio.NewScanner(bytes.NewReader(data)); s.Scan(); {
+ const prefix = "# requires:"
+ line := s.Text()
+ if !strings.HasPrefix(line, prefix) {
+ continue
+ }
+ for _, req := range strings.Fields(line[len(prefix):]) {
+ positive := true
+ if req[0] == '-' {
+ positive = false
+ req = req[1:]
+ }
+ requires[req] = positive
+ }
+ }
+ return requires
+}
+
+func checkArch(requires map[string]bool, arch string) bool {
+ for req, positive := range requires {
+ const prefix = "arch="
+ if strings.HasPrefix(req, prefix) &&
+ arch != req[len(prefix):] == positive {
+ return false
+ }
+ }
+ return true
}
-func parseProg(target *prog.Target, data []byte, mode prog.DeserializeMode) (*prog.Prog, error) {
+func MatchRequirements(props, requires map[string]bool) bool {
+ for req, positive := range requires {
+ if positive {
+ if !props[req] {
+ return false
+ }
+ continue
+ }
+ matched := true
+ for _, req1 := range strings.Split(req, ",") {
+ if !props[req1] {
+ matched = false
+ }
+ }
+ if matched {
+ return false
+ }
+ }
+ return true
+}
+
+var ErrSkippedTest = errors.New("skipped test based on constraints")
+
+func parseProg(target *prog.Target, data []byte, mode prog.DeserializeMode, reqs map[string]bool) (
+ *prog.Prog, map[string]bool, error) {
+ properties := parseRequires(data)
+ // Need to check requirements early, as some programs may fail to deserialize
+ // on some arches due to missing syscalls. We also do not want to parse tests
+ // that are marked as 'manual'.
+ if !checkArch(properties, target.Arch) || !MatchRequirements(properties, reqs) {
+ var pairs []string
+ for k, v := range properties {
+ pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
+ }
+ return nil, properties, fmt.Errorf("%w: %s", ErrSkippedTest, strings.Join(pairs, ", "))
+ }
p, err := target.Deserialize(data, mode)
if err != nil {
- return nil, err
+ return nil, nil, err
}
if len(p.Calls) > prog.MaxCalls {
- return nil, fmt.Errorf("longer than %d calls (%d)", prog.MaxCalls, len(p.Calls))
+ return nil, nil, fmt.Errorf("longer than %d calls (%d)", prog.MaxCalls, len(p.Calls))
}
// For some yet unknown reasons, programs with fail_nth > 0 may sneak in. Ignore them.
for _, call := range p.Calls {
if call.Props.FailNth > 0 {
- return nil, fmt.Errorf("input has fail_nth > 0")
+ return nil, nil, fmt.Errorf("input has fail_nth > 0")
}
}
- return p, nil
+ return p, properties, nil
}
type FilteredCandidates struct {
diff --git a/pkg/manager/seeds_test.go b/pkg/manager/seeds_test.go
new file mode 100644
index 000000000..d683c4a50
--- /dev/null
+++ b/pkg/manager/seeds_test.go
@@ -0,0 +1,29 @@
+// Copyright 2024 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package manager
+
+import (
+ "testing"
+)
+
+func TestRequires(t *testing.T) {
+ {
+ requires := parseRequires([]byte("# requires: manual arch=amd64"))
+ if !checkArch(requires, "amd64") {
+ t.Fatalf("amd64 does not pass check")
+ }
+ if checkArch(requires, "riscv64") {
+ t.Fatalf("riscv64 passes check")
+ }
+ }
+ {
+ requires := parseRequires([]byte("# requires: -arch=arm64 manual -arch=riscv64"))
+ if !checkArch(requires, "amd64") {
+ t.Fatalf("amd64 does not pass check")
+ }
+ if checkArch(requires, "riscv64") {
+ t.Fatalf("riscv64 passes check")
+ }
+ }
+}