Skip to content

Commit

Permalink
feat(suite/step/syscalls): add exactly-one-arg constraint support
Browse files Browse the repository at this point in the history
Introduce exactly-one-arg constraint allowing to specify that
exactly one argument, among a list of specified arguments, must be
directly set or bound (through field bindings) by the user. This
requires all the involved fields to have also a default value.

Signed-off-by: Leonardo Di Giovanna <leonardodigiovanna1@gmail.com>
  • Loading branch information
ekoops committed Mar 7, 2025
1 parent aaf411f commit 88e39ed
Show file tree
Hide file tree
Showing 19 changed files with 110 additions and 22 deletions.
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

0 comments on commit 88e39ed

Please sign in to comment.