Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(suite/step/syscalls): add exactly-one-arg constraint support #285

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 88 additions & 4 deletions pkg/test/step/syscall/base/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"fmt"
"reflect"
"slices"

"github.com/falcosecurity/event-generator/pkg/test/field"
"github.com/falcosecurity/event-generator/pkg/test/step"
Expand Down Expand Up @@ -48,28 +49,84 @@ type Syscall struct {
fieldBindings []*step.FieldBinding
}

var errOpenModeMustBePositive = fmt.Errorf("open mode must be a positive integer")
type config struct {
defaultedArgs []string
exactlyOneArgsLists [][]string
}

// Option for configuring a Syscall.
type Option interface {
apply(*config) error
}

// funcOption is an implementation of Option storing a function that implements the requested apply method behavior.
type funcOption struct {
f func(*config) error
}

func (cfo *funcOption) apply(c *config) error {
return cfo.f(c)
}

// newFuncOption is a helper function to create a new funcOption from a function.
func newFuncOption(f func(*config) error) *funcOption {
return &funcOption{f: f}
}

// WithDefaultedArgs allows to specify the list of defaulted arguments.
func WithDefaultedArgs(defaultedArgs []string) Option {
return newFuncOption(func(c *config) error {
c.defaultedArgs = defaultedArgs
return nil
})
}

// WithExactlyOneArgs puts a constraint on the provided arguments, mandating the user to specify a value for exactly one
// of them. This option can be provided multiple times to enforce exactly-one argument constraints upon different
// arguments set.
func WithExactlyOneArgs(exactlyOneArgs []string) Option {
return newFuncOption(func(c *config) error {
c.exactlyOneArgsLists = append(c.exactlyOneArgsLists, exactlyOneArgs)
return nil
})
}

// New creates a new system call test step common implementation layer.
func New(stepName string, rawArgs map[string]any, fieldBindings []*step.FieldBinding, argsContainer,
bindOnlyArgsContainer, retValueContainer reflect.Value, defaultedArgs []string) (*Syscall, error) {
bindOnlyArgsContainer, retValueContainer reflect.Value, options ...Option) (*Syscall, error) {
if err := checkContainersInvariants(argsContainer, bindOnlyArgsContainer, retValueContainer); err != nil {
return nil, err
}

unboundArgs := field.Paths(argsContainer.Type())
c := &config{}
for _, opt := range options {
if err := opt.apply(c); err != nil {
return nil, fmt.Errorf("error applying option: %w", err)
}
}

boundArgs, err := setArgFieldValues(argsContainer, rawArgs)
if err != nil {
return nil, fmt.Errorf("error setting argument field values: %w", err)
}

// Check exactly-one-argument constraints.
for _, exactlyOneArgs := range c.exactlyOneArgsLists {
if err := checkExactlyOneArgConstraint(exactlyOneArgs, boundArgs, fieldBindings); err != nil {
return nil, fmt.Errorf("error checking exactly-one-argument constraint for %v: %w", exactlyOneArgs, err)
}
}

// Evaluate unbound arguments sets.
unboundArgs := field.Paths(argsContainer.Type())
for _, boundArg := range boundArgs {
delete(unboundArgs, boundArg)
}

unboundBindOnlyArgs := field.Paths(bindOnlyArgsContainer.Type())

// Remove defaulted arguments from unbound arguments sets.
for _, arg := range defaultedArgs {
for _, arg := range c.defaultedArgs {
delete(unboundArgs, arg)
delete(unboundBindOnlyArgs, arg)
}
Expand Down Expand Up @@ -125,6 +182,8 @@ func setArgFieldValues(argFieldContainer reflect.Value, rawArgs map[string]any)
return boundArgs, nil
}

var errOpenModeMustBePositive = fmt.Errorf("open mode must be a positive integer")

// setArgFieldValue sets the value of the provided field and/or sub-fields to the provided value, parsing it differently
// depending on the field type.
//
Expand Down Expand Up @@ -280,6 +339,31 @@ func setSubArgFieldValues(argField *field.Field, value any) ([]string, error) {
return boundArgs, nil
}

// checkExactlyOneArgConstraint verifies that the provided bound arguments and the specified field bindings set/bind to
// exactly 1 argument among the specified arguments set. It returns an error if the condition is not met.
func checkExactlyOneArgConstraint(exactlyOneArgs []string, boundArgs []string,
fieldBindings []*step.FieldBinding) error {
var foundArgs, foundBindings []string
for _, boundArg := range boundArgs {
if slices.Contains(exactlyOneArgs, boundArg) {
foundArgs = append(foundArgs, boundArg)
}
}

for _, fieldBinding := range fieldBindings {
arg := fieldBinding.LocalField
if slices.Contains(exactlyOneArgs, arg) {
foundBindings = append(foundBindings, arg)
}
}

if len(foundArgs)+len(foundBindings) != 1 {
return fmt.Errorf("found %v arguments set and %v field bindings", foundArgs, foundBindings)
}

return nil
}

// Name implements step.Step.Name method.
func (s *Syscall) Name() string {
return s.stepName
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/connect/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&c.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(c).Elem()
var err error
c.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
c.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/dup/dup.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&d.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(d).Elem()
var err error
d.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
d.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/dup2/dup2.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&d.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(d).Elem()
var err error
d.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
d.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/dup3/dup3.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"flags"}
var err error
d.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/finitmodule/finitmodule.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"paramvalues", "flags"}
var err error
f.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/initmodule/initmodule.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"paramvalues"}
var err error
i.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/kill/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&k.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(k).Elem()
var err error
k.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
k.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/link/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&l.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(l).Elem()
var err error
l.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
l.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/linkat/linkat.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"olddirfd", "newdirfd", "flags"}
var err error
l.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/open/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"mode"}
var err error
o.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/openat/openat.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"dirfd", "mode"}
var err error
o.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/openat2/openat2.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"dirfd", "how", "how.flags", "how.mode", "how.resolve"}
var err error
o.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/test/step/syscall/read/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ type readSyscall struct {
// New creates a new read system call test step.
func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding) (syscall.Syscall, error) {
r := &readSyscall{}
// r.args.Buffer defaults to a buffer of length r.args.Len at run time, if unbound.
// r.args.Len defaults to the buffer length at run time, if unbound.
argsContainer := reflect.ValueOf(&r.args).Elem()
bindOnlyArgsContainer := reflect.ValueOf(&r.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(r).Elem()
defaultedArgs := []string{"buffer", "len"}
var err error
r.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
r.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
base.WithDefaultedArgs(defaultedArgs), base.WithExactlyOneArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/sendto/sendto.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"len"}
var err error
s.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/socket/socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&s.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(s).Elem()
var err error
s.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
s.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/symlink/symlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
bindOnlyArgsContainer := reflect.ValueOf(&s.bindOnlyArgs).Elem()
retValContainer := reflect.ValueOf(s).Elem()
var err error
s.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer, nil)
s.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/symlinkat/symlinkat.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"newdirfd"}
var err error
s.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/test/step/syscall/write/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func New(name string, rawArgs map[string]any, fieldBindings []*step.FieldBinding
defaultedArgs := []string{"len"}
var err error
w.Syscall, err = base.New(name, rawArgs, fieldBindings, argsContainer, bindOnlyArgsContainer, retValContainer,
defaultedArgs)
base.WithDefaultedArgs(defaultedArgs))
if err != nil {
return nil, err
}
Expand Down