Skip to content

Commit

Permalink
syz-manager: pkg: skip seeds with mismatching requirements in fuzzing…
Browse files Browse the repository at this point in the history
… mode

When running in the test mode, syz-manager already ignores tests that have
arch requirements mismatching the target arch.
Because the same tests are also used as seeds in the fuzzing mode, skip them
likewise, instead of reporting errors if they contain arch-specific syscalls.

The code and tests for parsing the requirements is moved from pkg/runtest to
pkg/manager.
  • Loading branch information
ramosian-glider committed Dec 2, 2024
1 parent bb326ff commit b74e713
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 68 deletions.
75 changes: 65 additions & 10 deletions pkg/manager/seeds.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
package manager

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

Expand Down Expand Up @@ -45,6 +48,7 @@ func LoadSeeds(cfg *mgrconfig.Config, immutable bool) Seeds {
Path string
Data []byte
Prog *prog.Prog
Props map[string]bool
Err error
}
procs := runtime.GOMAXPROCS(0)
Expand All @@ -56,7 +60,7 @@ func LoadSeeds(cfg *mgrconfig.Config, immutable bool) Seeds {
go func() {
defer wg.Done()
for inp := range inputs {
inp.Prog, inp.Err = ParseSeed(cfg.Target, inp.Data)
inp.Prog, inp.Props, inp.Err = ParseSeed(cfg.Target, inp.Data)
outputs <- inp
}
}()
Expand Down Expand Up @@ -94,13 +98,23 @@ 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 inp.Props != nil {
skippedSeeds++
var pairs []string
for k, v := range inp.Props {
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
}
log.Logf(0, "seed %s is skipped because of constraints: %s", inp.Path, strings.Join(pairs, ", "))
} else {
brokenSeeds++
log.Logf(0, "seed %s is broken: %s", inp.Path, inp.Err)
}
} else {
brokenCorpus = append(brokenCorpus, inp.Key)
}
Expand All @@ -123,6 +137,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 @@ -178,29 +195,67 @@ func versionToFlags(version uint64) fuzzer.ProgFlags {
return corpusFlags
}

func ParseSeed(target *prog.Target, data []byte) (*prog.Prog, error) {
func ParseSeed(target *prog.Target, data []byte) (*prog.Prog, map[string]bool, error) {
return parseProg(target, data, prog.NonStrict)
}

func ParseSeedStrict(target *prog.Target, data []byte) (*prog.Prog, error) {
func ParseSeedStrict(target *prog.Target, data []byte) (*prog.Prog, map[string]bool, error) {
return parseProg(target, data, prog.Strict)
}

func parseProg(target *prog.Target, data []byte, mode prog.DeserializeMode) (*prog.Prog, error) {
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, map[string]bool, error) {
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) {
// Return non-empty properties to let the caller know the test was skipped.
return nil, properties, nil
}
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")
}
}
}
38 changes: 2 additions & 36 deletions pkg/runtest/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,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.ParseSeedStrict(target, data)
if !match(properties, requires) {
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,37 +363,6 @@ 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
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")
}
}
}
2 changes: 1 addition & 1 deletion syz-manager/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func (hc *HubConnector) processRepros(repros [][]byte) int {
}

func (hc *HubConnector) parseProgram(data []byte) (*prog.Prog, error) {
p, err := manager.ParseSeed(hc.target, data)
p, _, err := manager.ParseSeed(hc.target, data)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit b74e713

Please sign in to comment.