diff --git a/docs/syscall_descriptions_syntax.md b/docs/syscall_descriptions_syntax.md index aa03c48a4598..9dedd33768cb 100644 --- a/docs/syscall_descriptions_syntax.md +++ b/docs/syscall_descriptions_syntax.md @@ -110,9 +110,10 @@ Call attributes are: string argument is a fsck-like command that will be called to verify the filesystem. "remote_cover": wait longer to collect remote coverage for this call. "kfuzz_test": the call is a kfuzztest target. -"snapshot": the call is enabled by default only in snapshot fuzzing mode, - but "enable_syscalls" and "disable_syscalls" config parameters override this. - It is generally used to mark calls that are not safe to execute in non-snapshot mode +"snapshot": the call is enabled by default only in snapshot fuzzing mode, but can also be enabled in + the non-snasphot mode when listed in "enable_syscalls" with its full name (as opposed to a wildcard match). + It can also always be disabled via "disable_syscalls". + The attribute is generally used to mark calls that are not safe to execute in non-snapshot mode (can lead to false positives, or lost connections to test machines. ``` diff --git a/pkg/mgrconfig/load.go b/pkg/mgrconfig/load.go index 531dc26d897f..38f0d00627c0 100644 --- a/pkg/mgrconfig/load.go +++ b/pkg/mgrconfig/load.go @@ -427,7 +427,11 @@ func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string, for _, c := range enabled { n := 0 for _, call := range target.Syscalls { - if MatchSyscall(call.Name, c) { + if !MatchSyscall(call.Name, c) { + continue + } + // Skip snapshot attr check for the calls that match exactly. + if checkMode(call, descriptionsMode, call.Name != c) { syscalls[call.ID] = true n++ } @@ -438,18 +442,14 @@ func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string, } } else { for _, call := range target.Syscalls { - if call.Attrs.Snapshot && (descriptionsMode&SnapshotDescriptions) == 0 { + if !checkMode(call, descriptionsMode, true) { continue } syscalls[call.ID] = true } } - for call := range syscalls { - if target.Syscalls[call].Attrs.Disabled || - (descriptionsMode&AutoDescriptions) == 0 && target.Syscalls[call].Attrs.Automatic || - (descriptionsMode&ManualDescriptions) == 0 && - !target.Syscalls[call].Attrs.Automatic && !target.Syscalls[call].Attrs.AutomaticHelper { + if target.Syscalls[call].Attrs.Disabled { delete(syscalls, call) } } @@ -475,6 +475,24 @@ func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string, return arr, nil } +func checkMode(syscall *prog.Syscall, descriptionsMode DescriptionsMode, + checkSnapshot bool) bool { + if syscall.Attrs.Automatic && + (descriptionsMode&AutoDescriptions) == 0 { + return false + } + if !syscall.Attrs.Automatic && + !syscall.Attrs.AutomaticHelper && + (descriptionsMode&ManualDescriptions) == 0 { + return false + } + if checkSnapshot && syscall.Attrs.Snapshot && + (descriptionsMode&SnapshotDescriptions) == 0 { + return false + } + return true +} + func ParseNoMutateSyscalls(target *prog.Target, syscalls []string) (map[int]bool, error) { var result = make(map[int]bool) diff --git a/pkg/mgrconfig/load_test.go b/pkg/mgrconfig/load_test.go new file mode 100644 index 000000000000..38e102b72b0d --- /dev/null +++ b/pkg/mgrconfig/load_test.go @@ -0,0 +1,99 @@ +// Copyright 2026 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 mgrconfig + +import ( + "testing" + + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseEnabledSyscalls(t *testing.T) { + target, err := prog.GetTarget(targets.TestOS, targets.TestArch64) + require.NoError(t, err) + + tests := []struct { + name string + mode DescriptionsMode + enable []string + // TODO: add disable tests as well. + expectEnabled []string + expectDisabled []string + }{ + { + name: "wildcard, no snapshot", + mode: ManualDescriptions, + enable: []string{"test"}, + expectDisabled: []string{"test$snapshot_only"}, + }, + { + name: "wildcard, snapshot", + mode: ManualDescriptions | SnapshotDescriptions, + enable: []string{"test"}, + expectEnabled: []string{"test$snapshot_only"}, + }, + { + name: "no wildcard, no snapshot", + mode: ManualDescriptions, + enable: []string{"test$snapshot_only"}, + expectEnabled: []string{"test$snapshot_only"}, + }, + { + name: "no wildcard, snapshot", + mode: ManualDescriptions | SnapshotDescriptions, + enable: []string{"test$snapshot_only"}, + expectEnabled: []string{"test$snapshot_only"}, + }, + { + name: "automatic allowed", + mode: ManualDescriptions | AutoDescriptions, + enable: []string{"test"}, + expectEnabled: []string{ + "test$automatic", + "test$automatic_helper", + "test$manual", + }, + }, + { + name: "manual only", + mode: ManualDescriptions, + enable: []string{"test"}, + expectEnabled: []string{ + "test$automatic_helper", + "test$manual", + }, + expectDisabled: []string{ + "test$automatic", + }, + }, + { + name: "auto only", + mode: AutoDescriptions, + enable: []string{"test"}, + expectEnabled: []string{ + "test$automatic", + "test$automatic_helper", + }, + expectDisabled: []string{ + "test$manual", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ids, err := ParseEnabledSyscalls(target, test.enable, + nil, test.mode) + require.NoError(t, err) + for _, enabled := range test.expectEnabled { + assert.Contains(t, ids, target.SyscallMap[enabled].ID) + } + for _, disabled := range test.expectDisabled { + assert.NotContains(t, ids, target.SyscallMap[disabled].ID) + } + }) + } +} diff --git a/sys/test/test.txt b/sys/test/test.txt index 54cf96f9b268..515f1e6580fa 100644 --- a/sys/test/test.txt +++ b/sys/test/test.txt @@ -1002,3 +1002,8 @@ test$consume_common(val common) test$consume_subtype_of_common(val subtype_of_common) test$fsck_attr() (fsck["fsck.test -n"]) + +test$snapshot_only(a0 intptr) (snapshot) +test$automatic(a0 intptr) (automatic) +test$automatic_helper(a0 intptr) (automatic_helper) +test$manual(a0 intptr)