Skip to content

Commit

Permalink
features: automatically wrap all feature probe errors
Browse files Browse the repository at this point in the history
Instead of relying on repeated calls to fmt.Errorf("...: %w", err) to ensure
we never return naked sentinels, add error wrappers at a few key locations.

This abstracts and centralizes the wrapping logic to make the probe logic
itself a bit leaner.

Signed-off-by: Timo Beckers <timo@isovalent.com>
  • Loading branch information
ti-mo committed Jul 29, 2022
1 parent 05d253d commit b3db817
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 28 deletions.
38 changes: 38 additions & 0 deletions features/errors.go
@@ -0,0 +1,38 @@
package features

import (
"errors"
"fmt"

"github.com/cilium/ebpf"
)

// wrapProbeErrors wraps err to prevent callers from directly comparing
// it to exported sentinels. Error rewriting in Go can be implemented by
// deferring a closure over a named error return variable. This gives the
// closure access to the stack space where the return value will be written,
// allowing it to intercept and rewrite all returns, regardless of whether
// or not the return statements use the named return variable.
//
// func foo() (err error) {
// defer func() {
// err = wrapProbeErrors(err)
// }
// return errors.New("this error will be wrapped")
// }
func wrapProbeErrors(err error) error {
if err == nil {
return nil
}

// Wrap all errors to prevent them from being compared directly
// to exported sentinels by the caller.
errStr := "%w"

if !errors.Is(err, ebpf.ErrNotSupported) {
// Wrap unexpected errors with an appropriate error string.
errStr = "unexpected error during feature probe: %w"
}

return fmt.Errorf(errStr, err)
}
16 changes: 8 additions & 8 deletions features/map.go
Expand Up @@ -2,7 +2,6 @@ package features

import (
"errors"
"fmt"
"os"
"sync"
"unsafe"
Expand Down Expand Up @@ -98,7 +97,12 @@ func createMapTypeAttr(mt ebpf.MapType) *sys.MapCreateAttr {
// HaveMapType probes the running kernel for the availability of the specified map type.
//
// See the package documentation for the meaning of the error return value.
func HaveMapType(mt ebpf.MapType) error {
func HaveMapType(mt ebpf.MapType) (err error) {
defer func() {
// This closure modifies a named return variable.
err = wrapProbeErrors(err)
}()

if err := validateMaptype(mt); err != nil {
return err
}
Expand All @@ -108,7 +112,7 @@ func HaveMapType(mt ebpf.MapType) error {

func validateMaptype(mt ebpf.MapType) error {
if mt > mt.Max() {
return fmt.Errorf("%w", os.ErrInvalid)
return os.ErrInvalid
}

if mt == ebpf.StructOpsMap {
Expand Down Expand Up @@ -146,11 +150,7 @@ func haveMapType(mt ebpf.MapType) error {
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given map type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = fmt.Errorf("%w", ebpf.ErrNotSupported)

// Wrap unexpected errors.
case err != nil:
err = fmt.Errorf("unexpected error during feature probe: %w", err)
err = ebpf.ErrNotSupported
}

mc.mapTypes[mt] = err
Expand Down
13 changes: 7 additions & 6 deletions features/misc.go
Expand Up @@ -78,7 +78,12 @@ func HaveV3ISA() error {

// probeMisc checks the kernel for a given supported misc by creating
// a specialized program probe and loading it.
func probeMisc(mt miscType) error {
func probeMisc(mt miscType) (err error) {
defer func() {
// This closure modifies a named return variable.
err = wrapProbeErrors(err)
}()

mc.Lock()
defer mc.Unlock()
err, ok := miscs.miscTypes[mt]
Expand All @@ -102,11 +107,7 @@ func probeMisc(mt miscType) error {
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given map type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = fmt.Errorf("%w", ebpf.ErrNotSupported)

// Wrap unexpected errors.
case err != nil:
err = fmt.Errorf("unexpected error during feature probe: %w", err)
err = ebpf.ErrNotSupported
}

miscs.miscTypes[mt] = err
Expand Down
30 changes: 16 additions & 14 deletions features/prog.go
Expand Up @@ -101,7 +101,12 @@ var HaveProgType = HaveProgramType
// HaveProgramType probes the running kernel for the availability of the specified program type.
//
// See the package documentation for the meaning of the error return value.
func HaveProgramType(pt ebpf.ProgramType) error {
func HaveProgramType(pt ebpf.ProgramType) (err error) {
defer func() {
// This closure modifies a named return variable.
err = wrapProbeErrors(err)
}()

if err := validateProgramType(pt); err != nil {
return err
}
Expand All @@ -112,7 +117,7 @@ func HaveProgramType(pt ebpf.ProgramType) error {

func validateProgramType(pt ebpf.ProgramType) error {
if pt > pt.Max() {
return fmt.Errorf("%w", os.ErrInvalid)
return os.ErrInvalid
}

if progLoadProbeNotImplemented(pt) {
Expand Down Expand Up @@ -148,11 +153,7 @@ func haveProgramType(pt ebpf.ProgramType) error {
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given prog type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = fmt.Errorf("%w", ebpf.ErrNotSupported)

// Wrap unexpected errors.
case err != nil:
err = fmt.Errorf("unexpected error during feature probe: %w", err)
err = ebpf.ErrNotSupported
}

pc.types[pt] = err
Expand All @@ -173,7 +174,12 @@ func haveProgramType(pt ebpf.ProgramType) error {
// Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) (err error) {
defer func() {
// This closure modifies a named return variable.
err = wrapProbeErrors(err)
}()

if err := validateProgramType(pt); err != nil {
return err
}
Expand All @@ -187,7 +193,7 @@ func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {

func validateProgramHelper(helper asm.BuiltinFunc) error {
if helper > helper.Max() {
return fmt.Errorf("%w", os.ErrInvalid)
return os.ErrInvalid
}

return nil
Expand Down Expand Up @@ -225,11 +231,7 @@ func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
// to support the given prog type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
// TODO: possibly we need to check verifier output here to be sure
err = fmt.Errorf("%w", ebpf.ErrNotSupported)

// Wrap unexpected errors.
case err != nil:
err = fmt.Errorf("unexpected error during feature probe: %w", err)
err = ebpf.ErrNotSupported
}

pc.helpers[pt][helper] = err
Expand Down

0 comments on commit b3db817

Please sign in to comment.