Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 91 additions & 10 deletions pkg/manager/seeds.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
package manager

import (
"bufio"
"bytes"
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -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)
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
29 changes: 29 additions & 0 deletions pkg/manager/seeds_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
62 changes: 4 additions & 58 deletions pkg/runtest/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -305,13 +306,10 @@ func parseProg(target *prog.Target, dir, filename string, requires map[string]bo
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to read %v: %w", filename, err)
}
properties := parseRequires(data)
// Need to check arch requirement early as some programs
// may fail to deserialize on some arches due to missing syscalls.
if !checkArch(properties, target.Arch) || !match(properties, requires) {
p, properties, err := manager.ParseSeedWithRequirements(target, data, requires)
if errors.Is(err, manager.ErrSkippedTest) {
return nil, nil, nil, nil
}
p, err := manager.ParseSeedStrict(target, data)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to deserialize %v: %w", filename, err)
}
Expand Down Expand Up @@ -366,42 +364,11 @@ func parseProg(target *prog.Target, dir, filename string, requires map[string]bo
return p, properties, info, nil
}

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 (ctx *Context) produceTest(req *runRequest, name string, properties,
requires map[string]bool, results *flatrpc.ProgInfo) {
req.name = name
req.results = results
if !match(properties, requires) {
if !manager.MatchRequirements(properties, requires) {
req.skip = "excluded by constraints"
}
ctx.createTest(req)
Expand Down Expand Up @@ -445,27 +412,6 @@ func (ctx *Context) submit(req *runRequest) {
req.executor.Submit(req.Request)
}

func match(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
}

func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bool) (*runRequest, error) {
var opts flatrpc.ExecOpts
sandboxFlags, err := flatrpc.SandboxToFlags(sandbox)
Expand Down
21 changes: 0 additions & 21 deletions pkg/runtest/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,24 +589,3 @@ func TestParsing(t *testing.T) {
}
}
}

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")
}
}
}
Loading