Skip to content

Commit

Permalink
kernel_features: fix tests for new KernelConfig
Browse files Browse the repository at this point in the history
This is a preparation for a fix for: aquasecurity/tracee#851
  • Loading branch information
rafaeldtinoco authored and Rafael David Tinoco committed Aug 29, 2021
1 parent c3b9bd6 commit 7b38633
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 125 deletions.
104 changes: 54 additions & 50 deletions helpers/kernel_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package helpers

import (
"bufio"
"bytes"
"compress/gzip"
"fmt"
"io"
Expand Down Expand Up @@ -178,64 +179,77 @@ type KernelConfig struct {
}

// InitKernelConfig inits external KernelConfig object
func InitKernelConfig() *KernelConfig {
config := KernelConfig{
configs: make(map[KernelConfigOption]interface{}),
needed: make(map[KernelConfigOption]interface{}),
func InitKernelConfig() (*KernelConfig, error) {
x := unix.Utsname{}
if err := unix.Uname(&x); err != nil {
return nil, fmt.Errorf("could not get utsname")
}
if err := config.initKernelConfig(); err != nil {
return nil

releaseVersion := bytes.TrimRight(x.Release[:], "\x00")
releaseFilePath := fmt.Sprintf("/boot/config-%s", releaseVersion)

config := KernelConfig{}

if err1 := config.initKernelConfig(releaseFilePath); err1 != nil {
if err2 := config.initKernelConfig("/proc/config.gz"); err2 != nil {
return nil, err2
}

return nil, err1
}

return &config
return &config, nil
}

// initKernelConfig inits internal KernelConfig data by calling appropriate readConfigFromXXX function
func (k *KernelConfig) initKernelConfig() error {
k.configs = make(map[KernelConfigOption]interface{})
func (k *KernelConfig) initKernelConfig(configFilePath string) error {
var err error
var file *os.File

x := unix.Utsname{}
if err := unix.Uname(&x); err != nil {
return fmt.Errorf("could not determine uname release: %w", err)
if file, err = os.Open(configFilePath); err != nil {
return fmt.Errorf("could not open %v: %w", configFilePath, err)
}
defer file.Close()

if err := k.readConfigFromBootConfigRelease(string(x.Release[:])); err != nil {
if err2 := k.readConfigFromProcConfigGZ(); err != nil {
return err2
}

head := make([]byte, 2)
if _, err = file.Read(head); err != nil {
return err
}

return nil
}

// readConfigFromProcConfigGZ prepares io.Reader for readConfigFromGZScanner (/proc/config.gz)
func (k *KernelConfig) readConfigFromProcConfigGZ() error {
configFile, err := os.Open("/proc/config.gz")
if err != nil {
return fmt.Errorf("could not open /proc/config.gz: %w", err)
// check if its a gziped file
if head[0] == 0x1f && head[1] == 0x8b {
return k.readConfigFromProcConfigGZ(configFilePath)
}

return k.readConfigFromGZScanner(configFile)
// assume it is a text file
return k.readConfigFromBootConfigRelease(configFilePath)
}

// readConfigFromScanner prepares io.Reader for readConfigFromScanner (/boot/config-$(uname -r))
func (k *KernelConfig) readConfigFromBootConfigRelease(release string) error {
path := fmt.Sprintf("/boot/config-%s", strings.TrimRight(release, "\x00"))
// readConfigFromBootConfigRelease prepares io.Reader (/boot/config-$(uname -r)) for readConfigFromScanner
func (k *KernelConfig) readConfigFromBootConfigRelease(filePath string) error {
file, _ := os.Open(filePath) // already checked
k.readConfigFromScanner(file)
file.Close()

configFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("could not open %s: %w", path, err)
}
return nil
}

k.readConfigFromScanner(configFile)
// readConfigFromProcConfigGZ prepares gziped io.Reader (/proc/config.gz) for readConfigFromScanner
func (k *KernelConfig) readConfigFromProcConfigGZ(filePath string) error {
file, _ := os.Open(filePath) // already checked
zreader, _ := gzip.NewReader(file)
k.readConfigFromScanner(zreader)
zreader.Close()
file.Close()

return nil
}

// readConfigFromScanner reads all existing KernelConfigOption's and KernelConfigOptionValue's from given io.Reader
func (k *KernelConfig) readConfigFromScanner(reader io.Reader) {
k.configs = make(map[KernelConfigOption]interface{})
k.needed = make(map[KernelConfigOption]interface{})

scanner := bufio.NewScanner(reader)
for scanner.Scan() {
kv := strings.Split(scanner.Text(), "=")
Expand All @@ -257,26 +271,14 @@ func (k *KernelConfig) readConfigFromScanner(reader io.Reader) {
}
}

// readConfigFromGZScanner reads all existing KernelConfigOption's and KernelConfigOptionValue's from a gzip io.Reader
func (k *KernelConfig) readConfigFromGZScanner(reader io.Reader) error {
zreader, err := gzip.NewReader(reader)
if err != nil {
return err
}

k.readConfigFromScanner(zreader)

return nil
}

// GetValue will return a KernelConfigOptionValue for a given KernelConfigOption when this is a BUILTIN or a MODULE
func (k *KernelConfig) GetValue(option KernelConfigOption) (KernelConfigOptionValue, error) {
value, ok := k.configs[option].(KernelConfigOptionValue)
if ok {
return value, nil
}

return UNDEFINED, fmt.Errorf("given option's value (%s) is a string", option)
return UNDEFINED, fmt.Errorf("given option's value (%s) is undefined", option)
}

// GetValueString will return a KernelConfigOptionValue for a given KernelConfigOption when this is actually a string
Expand Down Expand Up @@ -308,8 +310,8 @@ func (k *KernelConfig) Exists(option KernelConfigOption) bool {
// ExistsValue will return true if a given KernelConfigOption was found in provided KernelConfig
// AND its value is the same as the one provided by KernelConfigOptionValue
func (k *KernelConfig) ExistsValue(option KernelConfigOption, value interface{}) bool {
if _, ok := k.configs[option]; ok {
switch k.configs[option].(type) {
if cfg, ok := k.configs[option]; ok {
switch cfg.(type) {
case KernelConfigOptionValue:
if value == ANY {
return true
Expand Down Expand Up @@ -348,5 +350,7 @@ func (k *KernelConfig) CheckMissing() []KernelConfigOption {
// kernelConfig.AddNeeded(helpers.CONFIG_HZ, "250")
//
func (k *KernelConfig) AddNeeded(option KernelConfigOption, value interface{}) {
k.needed[option] = value
if _, ok := KernelConfigKeyIDToString[option]; ok {
k.needed[option] = value
}
}
126 changes: 51 additions & 75 deletions helpers/kernel_features_test.go
Original file line number Diff line number Diff line change
@@ -1,98 +1,74 @@
package helpers

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"testing"
)

func TestGetProcGZConfigByPath(t *testing.T) {
testCases := []struct {
name string
goldenFilePath string
expectedMap KernelConfig
expectedError error
}{
{
name: "non-existent",
goldenFilePath: "foobarblahblahblah",
expectedMap: KernelConfig{},
expectedError: errors.New("could not open foobarblahblahblah: open foobarblahblahblah: no such file or directory"),
},
{
name: "invalid zip format",
goldenFilePath: "testdata/tarred_config.tar",
expectedMap: KernelConfig{},
expectedError: errors.New("gzip: invalid header"),
},
{
name: "standard config",
goldenFilePath: "testdata/config_standard.gz",
expectedMap: KernelConfig{CONFIG_BPF: "y", CONFIG_BPF_JIT_ALWAYS_ON: "y", CONFIG_BPF_LSM: "y", CONFIG_BPF_PRELOAD: "y", CONFIG_BPF_PRELOAD_UMD: "m", CONFIG_BPF_SYSCALL: "y", CONFIG_IPV6_SEG6_BPF: "y", CONFIG_NETFILTER_XT_MATCH_BPF: "m"},
expectedError: nil,
},
{
name: "config with comments in it",
goldenFilePath: "testdata/comments_config.gz",
expectedMap: KernelConfig{CONFIG_BPF: "y", CONFIG_BPF_SYSCALL: "y", CONFIG_BPF_PRELOAD_UMD: "m"},
expectedError: nil,
},
}
func TestGetKernelConfigValue(t *testing.T) {

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
var kconfig KernelConfig = make(map[uint32]string)
err := kconfig.getProcGZConfigByPath(tt.goldenFilePath)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedMap, kconfig)
})
}
}
allConfigFiles := []string{"testdata/config_standard.gz", "testdata/config_comments.gz", "testdata/config_comments"}

func TestGetKernelConfigValue(t *testing.T) {
testCases := []struct {
name string
key uint32
conf KernelConfig
expectedError error
expectedValue string
testName string
givenOptions []KernelConfigOption
givenValues []interface{} // might either be KernelConfigOptionValue or String
missingOptions []KernelConfigOption // options that will be missing from given config files
}{
{
name: "Value present",
key: CONFIG_BPF,
conf: KernelConfig{CONFIG_BPF: "y"},
expectedError: nil,
expectedValue: "y",
testName: "option ok",
givenOptions: []KernelConfigOption{CONFIG_BPF},
givenValues: []interface{}{BUILTIN},
missingOptions: []KernelConfigOption{},
},
{
name: "Value present",
key: CONFIG_BPF,
conf: KernelConfig{CONFIG_BPFILTER: "foo", CONFIG_BPF: "y"},
expectedError: nil,
expectedValue: "y",
testName: "multiple options ok",
givenOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_TEST_BPF, CONFIG_HZ},
givenValues: []interface{}{BUILTIN, BUILTIN, MODULE, "250"},
missingOptions: []KernelConfigOption{},
},
{
name: "nil conf",
key: CONFIG_BPF,
conf: nil,
expectedError: errors.New("kernel config value does not exist, it's possible this option is not present in your kernel version or the KernelConfig has not been initialized"),
expectedValue: "",
testName: "multiple options ok with single not ok",
givenOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_TEST_BPF, CONFIG_HZ},
givenValues: []interface{}{MODULE, BUILTIN, MODULE, "250"},
missingOptions: []KernelConfigOption{CONFIG_BPF},
},
{
name: "Value not present",
key: CONFIG_BPF_JIT,
conf: KernelConfig{CONFIG_BPF: "y"},
expectedError: errors.New("kernel config value does not exist, it's possible this option is not present in your kernel version or the KernelConfig has not been initialized"),
expectedValue: "",
testName: "multiple options ok with multiple not ok",
givenOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_TEST_BPF, CONFIG_HZ, CONFIG_HZ},
givenValues: []interface{}{MODULE, BUILTIN, MODULE, "250", "500"},
missingOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_HZ},
},
{
testName: "undefined value",
givenOptions: []KernelConfigOption{0xFFFFFFFF},
givenValues: []interface{}{UNDEFINED}, // non-existing values will be ignored
missingOptions: []KernelConfigOption{},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
v, err := tt.conf.GetKernelConfigValue(tt.key)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedValue, v)
for _, tt := range testCases { // for each of the test cases run:
t.Run(tt.testName, func(test *testing.T) { // a test named testName with the following func():
for _, configFile := range allConfigFiles {
var err error

// initialize the KernelConfig object
config := KernelConfig{}
err = config.initKernelConfig(configFile)
assert.Equal(test, err, nil)

// add needed KernelConfigOptions
for pos, option := range tt.givenOptions {
config.AddNeeded(option, tt.givenValues[pos])
}

// check amount of missing KernelConfigOptions first
missing := config.CheckMissing()
assert.Equal(test, len(tt.missingOptions), len(missing))

// check if missing KernelConfigOptions are the correct ones
assert.ElementsMatch(test, tt.missingOptions, missing)
}
})
}
}
Binary file removed helpers/testdata/comments_config.gz
Binary file not shown.
17 changes: 17 additions & 0 deletions helpers/testdata/config_comments
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# this is a comment in the beginning
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
# this is comment in the middle
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_BPF_PRELOAD=y
CONFIG_BPF_PRELOAD_UMD=m
# CONFIG_BLAH is not set
CONFIG_IPV6_SEG6_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_HZ_250=y
CONFIG_HZ=250
CONFIG_TEST_BPF=m
# a comment at the end
Binary file added helpers/testdata/config_comments.gz
Binary file not shown.
Binary file modified helpers/testdata/config_standard.gz
Binary file not shown.
Binary file removed helpers/testdata/tarred_config.tar
Binary file not shown.

0 comments on commit 7b38633

Please sign in to comment.