mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
a1be003b6f
commit
56be4a9921
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user