diff options
| author | Alexander Potapenko <glider@google.com> | 2025-09-09 12:09:02 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2025-09-09 18:27:31 +0000 |
| commit | 5ac84ab421465f8f15ac9350f9f33a4416b4b3b7 (patch) | |
| tree | 4bd25bbaef09bb2a4fa31877862e8d2f20b17d1f /prog | |
| parent | d291dd2d58a1885c00a60561048b6ceb1bf1206a (diff) | |
prog: pkg/compiler: docs: introduce the `no_squash` attribute
The `no_squash` per-syscall attribute prevents the fuzzer from generating
squashed arguments to a particular syscall.
This is particularly helpful for pseudo-syscalls with elaborate
arguments that are hard to reason about when they are squashed - e.g.
for syz_kvm_add_vcpu() that takes a SYZOS program as an input.
I've considered an alternative solution that prohibits ANY for all
pseudo-syscalls. But there is a bunch of existing programs (both
the tests and the repros) for syscalls like syz_mount_image() for which
the benefit of not passing ANY is not immediately obvious.
I therefore decided to go with an explicit attribute that can later
be enforced for every pseudo-syscall at compile time.
Diffstat (limited to 'prog')
| -rw-r--r-- | prog/mutation.go | 3 | ||||
| -rw-r--r-- | prog/mutation_test.go | 26 | ||||
| -rw-r--r-- | prog/types.go | 1 | ||||
| -rw-r--r-- | prog/validation.go | 16 |
4 files changed, 41 insertions, 5 deletions
diff --git a/prog/mutation.go b/prog/mutation.go index eb9e8285d..4d05e0a7d 100644 --- a/prog/mutation.go +++ b/prog/mutation.go @@ -145,6 +145,9 @@ func (ctx *mutator) squashAny() bool { if ctx.noMutate[ptr.call.Meta.ID] { return false } + if ptr.call.Meta.Attrs.NoSquash { + return false + } if !p.Target.isAnyPtr(ptr.arg.Type()) { p.Target.squashPtr(ptr.arg) } diff --git a/prog/mutation_test.go b/prog/mutation_test.go index 1cd18656e..d9734b482 100644 --- a/prog/mutation_test.go +++ b/prog/mutation_test.go @@ -165,6 +165,32 @@ func TestMutateArgument(t *testing.T) { } } +func TestMutateNoSquash(t *testing.T) { + target := initTargetTest(t, "test", "64") + p, err := target.Deserialize([]byte(`mutate_no_squash(&(0x7f0000000000)={0x1, 0x2, 0x3, 0x4, "5e9ce23b"})`), Strict) + if err != nil { + t.Fatal(err) + } + rs := rand.NewSource(0) + r := newRand(target, rs) + ctx := &mutator{ + p: p, + r: r, + ncalls: 1, + ct: target.DefaultChoiceTable(), + opts: DefaultMutateOpts, + } + + // squashAny should not mutate the program. + for i := 0; i < 100; i++ { + p1 := p.Clone() + ctx.p = p1 + if ctx.squashAny() { + t.Fatalf("squashAny mutated a no_squash call: %s", p1.Serialize()) + } + } +} + func TestSizeMutateArg(t *testing.T) { target, rs, iters := initRandomTargetTest(t, "test", "64") r := newRand(target, rs) diff --git a/prog/types.go b/prog/types.go index 3bf7d640e..5a170dad1 100644 --- a/prog/types.go +++ b/prog/types.go @@ -44,6 +44,7 @@ type SyscallAttrs struct { BreaksReturns bool NoGenerate bool NoMinimize bool + NoSquash bool RemoteCover bool Automatic bool AutomaticHelper bool diff --git a/prog/validation.go b/prog/validation.go index 48a6efade..3fec567b9 100644 --- a/prog/validation.go +++ b/prog/validation.go @@ -31,11 +31,12 @@ func (p *Prog) validate() error { } type validCtx struct { - target *Target - isUnsafe bool - opts validationOptions - args map[Arg]bool - uses map[Arg]Arg + target *Target + isUnsafe bool + opts validationOptions + args map[Arg]bool + uses map[Arg]Arg + currentCall *Call } type validationOptions struct { @@ -54,10 +55,12 @@ func (p *Prog) validateWithOpts(opts validationOptions) error { if c.Meta == nil { return fmt.Errorf("call does not have meta information") } + ctx.currentCall = c if err := ctx.validateCall(c); err != nil { return fmt.Errorf("call #%d %v: %w", i, c.Meta.Name, err) } } + ctx.currentCall = nil for u, orig := range ctx.uses { if !ctx.args[u] { return fmt.Errorf("use of %+v referes to an out-of-tree arg\narg: %#v", orig, u) @@ -128,6 +131,9 @@ func (ctx *validCtx) validateArg(arg Arg, typ Type, dir Dir) error { if !ctx.target.isAnyPtr(arg.Type()) && arg.Type() != typ { return fmt.Errorf("bad arg type %#v, expect %#v", arg.Type(), typ) } + if ctx.currentCall.Meta.Attrs.NoSquash && ctx.target.isAnyPtr(arg.Type()) { + return fmt.Errorf("ANY argument for no_squash call %v", ctx.currentCall.Meta.Name) + } ctx.args[arg] = true return arg.validate(ctx, dir) } |
