seccomp: Add method to ValueMatcher to generate fuzz test cases.

This adds a method called `InterestingValues` to `ValueMatcher` and
`halfValueMatcher` that lists the values worth testing for this argument.

Then, the new `SyscallRules.UsefulTestCases` function aggregates all of
this into a set of useful test cases that can be used to seed the corpus
of a seccomp fuzz test.

PiperOrigin-RevId: 581148821
This commit is contained in:
Etienne Perot
2023-11-10 00:35:41 -08:00
committed by gVisor bot
parent a1be003b6f
commit 56be4a9921
4 changed files with 208 additions and 1 deletions
+1
View File
@@ -11,6 +11,7 @@ go_library(
"seccomp.go",
"seccomp_amd64.go",
"seccomp_arm64.go",
"seccomp_fuzz_helpers.go",
"seccomp_optimizer.go",
"seccomp_rules.go",
"seccomp_unsafe.go",
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package seccomp
// This file contains helpers to generate fuzz tests for seccomp rules.
// It contains the `InterestingValues` implementations for all matchers,
// and a helper function to generate test cases based on `RuleSet`s.
import (
"sort"
"gvisor.dev/gvisor/pkg/abi/linux"
)
// UsefulTestCases returns a best-effort list of test cases that may be
// useful in fuzzing this set of rules.
func (sr SyscallRules) UsefulTestCases() []linux.SeccompData {
var testCases []linux.SeccompData
for sysno, r := range sr.rules {
// valueMatchers maps argument indexes to value matchers
// seen for that argument index.
// valueMatchersRepr tracks the `Repr()` of those
// `ValueMatcher`s in order to avoid inserting duplicates.
valueMatchers := make(map[int][]ValueMatcher)
valueMatchersRepr := make(map[int]map[string]struct{})
// Find all unique `ValueMatcher`s for each argument.
var processRule func(SyscallRule) SyscallRule
processRule = func(r SyscallRule) SyscallRule {
r.Recurse(processRule)
pa, isPerArg := r.(PerArg)
if !isPerArg {
return r
}
for argNum, arg := range pa {
if arg == nil {
arg = AnyValue{}
}
valueMatchersReprMap, ok := valueMatchersRepr[argNum]
if !ok {
valueMatchersReprMap = make(map[string]struct{})
valueMatchersRepr[argNum] = valueMatchersReprMap
}
repr := arg.Repr()
if _, seen := valueMatchersReprMap[repr]; seen {
continue
}
valueMatchersReprMap[repr] = struct{}{}
valueMatchers[argNum] = append(valueMatchers[argNum], arg)
}
return r
}
processRule(r)
// Now compute the combination of all interesting values for them.
sysnoCases := []linux.SeccompData{{
Nr: int32(sysno),
Arch: LINUX_AUDIT_ARCH,
}}
for argNum, vms := range valueMatchers {
// Deduplicate interesting values across value matchers.
interestingValuesMap := make(map[uint64]struct{})
interestingValuesMap[0] = struct{}{} // The zero value is always interesting.
for _, vm := range vms {
for _, interestingValue := range vm.InterestingValues() {
interestingValuesMap[interestingValue] = struct{}{}
}
}
// Convert to sorted slice of integers.
interestingValues := make([]uint64, 0, len(interestingValuesMap))
for interestingValue := range interestingValuesMap {
interestingValues = append(interestingValues, interestingValue)
}
sort.Slice(interestingValues, func(i, j int) bool {
return interestingValues[i] < interestingValues[j]
})
// Generate test cases.
newSysnoCases := make([]linux.SeccompData, 0, len(sysnoCases)*len(interestingValues))
for _, sysnoCase := range sysnoCases {
for _, interestingValue := range interestingValues {
if argNum == RuleIP {
sysnoCase.InstructionPointer = interestingValue
} else {
sysnoCase.Args[argNum] = interestingValue
}
newSysnoCases = append(newSysnoCases, sysnoCase)
}
}
sysnoCases = newSysnoCases
}
testCases = append(testCases, sysnoCases...)
}
return testCases
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (halfAnyValue) InterestingValues() []uint32 {
return []uint32{0}
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (heq halfEqualTo) InterestingValues() []uint32 {
return []uint32{uint32(heq), uint32(heq + 1)}
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (hns halfNotSet) InterestingValues() []uint32 {
return []uint32{uint32(hns), uint32(hns + 1)}
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (hmeq halfMaskedEqual) InterestingValues() []uint32 {
return []uint32{uint32(hmeq.mask), uint32(hmeq.mask + 1)}
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (sm splitMatcher) InterestingValues() []uint64 {
interestingHigh := sm.highMatcher.InterestingValues()
interestingLow := sm.lowMatcher.InterestingValues()
interesting := make([]uint64, 0, len(interestingHigh)*len(interestingLow))
for _, high := range interestingHigh {
for _, low := range interestingLow {
interesting = append(interesting, (uint64(high)<<32)|uint64(low))
}
}
return interesting
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (av AnyValue) InterestingValues() []uint64 {
return []uint64{0}
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (eq EqualTo) InterestingValues() []uint64 {
return eq.split().InterestingValues()
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (ne NotEqual) InterestingValues() []uint64 {
return EqualTo(ne).InterestingValues()
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (gt GreaterThan) InterestingValues() []uint64 {
return []uint64{
uint64(high32Bits(uintptr(gt))+1) << 32,
uint64(high32Bits(uintptr(gt))-1) << 32,
uint64(high32Bits(uintptr(gt))) << 32,
(uint64(high32Bits(uintptr(gt))) << 32) + uint64(low32Bits(uintptr(gt))),
(uint64(high32Bits(uintptr(gt))) << 32) + uint64(low32Bits(uintptr(gt))) + 1,
(uint64(high32Bits(uintptr(gt))) << 32) + uint64(low32Bits(uintptr(gt))) - 1,
}
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (ge GreaterThanOrEqual) InterestingValues() []uint64 {
return GreaterThan(ge).InterestingValues()
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (lt LessThan) InterestingValues() []uint64 {
return GreaterThan(lt).InterestingValues()
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (le LessThanOrEqual) InterestingValues() []uint64 {
return GreaterThan(le).InterestingValues()
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (nnfd NonNegativeFD) InterestingValues() []uint64 {
return nnfd.split().InterestingValues()
}
// InterestingValues implements `halfValueMatcher.InterestingValues`.
func (me maskedEqual) InterestingValues() []uint64 {
return me.split().InterestingValues()
}
+8
View File
@@ -63,6 +63,10 @@ type ValueMatcher interface {
// The rules should indicate this by either jumping to `labelSet.Matched()`
// or `labelSet.Mismatched()`. They may not fall through.
Render(program *syscallProgram, labelSet *labelSet, value matchedValue)
// InterestingValues returns a list of values that may be interesting to
// test this `ValueMatcher` against.
InterestingValues() []uint64
}
// halfValueMatcher verifies a 32-bit value.
@@ -78,6 +82,10 @@ type halfValueMatcher interface {
// The rules should indicate this by either jumping to `labelSet.Matched()`
// or `labelSet.Mismatched()`. They may not fall through.
HalfRender(program *syscallProgram, labelSet *labelSet)
// InterestingValues returns a list of values that may be interesting to
// test this `halfValueMatcher` against.
InterestingValues() []uint32
}
// halfAnyValue implements `halfValueMatcher` and matches any value.
+5 -1
View File
@@ -142,7 +142,11 @@ func (df *DiffFuzzer) defaultSeedCorpus() {
// DeriveCorpusFromRuleSets attempts to extract useful seed corpus rules
// out of the given `RuleSet`s.
func (df *DiffFuzzer) DeriveCorpusFromRuleSets(ruleSets []seccomp.RuleSet) {
// TODO(b/298726675): Not implemented yet.
for _, ruleSet := range ruleSets {
for _, tc := range ruleSet.Rules.UsefulTestCases() {
df.AddSeed(tc)
}
}
}
// NewDiffFuzzer creates a fuzzer that verifies that two seccomp-bpf programs