Copyright (c) 2024 Huawei Technologies Co., Ltd.
openFuyao is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/
package checker
import (
"bufio"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"openfuyao.com/oscheck/internal/conf"
"openfuyao.com/oscheck/internal/utils"
)
const (
procModulesPath = "/proc/modules"
sysModulePath = "/sys/module"
moduleStatusIndex = 5
)
type KernelModuleSubItemParam struct {
Detail []SubItemParamBase `json:"detail"`
Doc string `json:"doc"`
Name string `json:"name"`
ModuleName string `json:"moduleName"`
}
type kernelModuleChecker struct {
ctx Context
subCheckers []KernelModuleSubChecker
conf conf.CheckItem
logger *log.Logger
}
type KernelModuleSubChecker struct {
param KernelModuleSubItemParam
detailCheckers []kernelModuleDetailSubChecker
}
type kernelModuleDetailSubChecker struct {
detailPath string
expect interface{}
valueChecker ValueChecker
}
func (c *kernelModuleChecker) Init(ctx Context, config conf.CheckItem) *ParamCheckError {
c.ctx = ctx
c.conf = config
c.logger = utils.GetLogger("KernelModuleChecker")
paramCheckErr := ParamCheckError{
ErrField: []string{},
Msg: "",
FormatErr: false,
}
subCheckerParams := make([]KernelModuleSubItemParam, 0)
err := convertSpec2Special(config, &subCheckerParams)
if err != nil {
paramCheckErr.FormatErr = true
paramCheckErr.Msg = "Format Error"
c.logger.Printf("Failed to Init KenerlModuleChecker, config file: <%s>, because <%s>",
config.FilePath, err.Error())
return ¶mCheckErr
}
for _, subCheckerParam := range subCheckerParams {
if len(subCheckerParam.Name) == 0 {
errField := fmt.Sprintf("spec.[name=%s].name", subCheckerParam.Name)
paramCheckErr.ErrField = append(paramCheckErr.ErrField, errField)
c.logger.Printf("Failed to Init KernelModuleChecker, config file: <%s>, field: <%s>",
c.conf.FilePath, errField)
continue
}
detailSubCheckers := make([]kernelModuleDetailSubChecker, 0, len(subCheckerParam.Detail))
for _, detailItem := range subCheckerParam.Detail {
detailSubChecker := c.getDetailSubChecker(detailItem, subCheckerParam, ¶mCheckErr)
if detailSubChecker != nil {
detailSubCheckers = append(detailSubCheckers, kernelModuleDetailSubChecker{
detailPath: detailItem.Name,
expect: detailItem.Expect,
valueChecker: detailSubChecker,
})
}
}
kmSubChecker := KernelModuleSubChecker{
param: subCheckerParam,
detailCheckers: detailSubCheckers,
}
c.subCheckers = append(c.subCheckers, kmSubChecker)
}
if len(paramCheckErr.ErrField) > 0 {
return ¶mCheckErr
}
return nil
}
func (c *kernelModuleChecker) getDetailSubChecker(detailItem SubItemParamBase,
subCheckerParam KernelModuleSubItemParam, paramCheckErr *ParamCheckError) ValueChecker {
if !checkParamFormat(detailItem) {
errField := fmt.Sprintf("spec.[name=%s].detail.[name=%s]", subCheckerParam.Name, detailItem.Name)
paramCheckErr.ErrField = append(paramCheckErr.ErrField, errField)
c.logger.Printf("Failed to Init KernelModuleChecker, config file: <%s>, field: <%s>",
c.conf.FilePath, errField)
return nil
}
vc, err := GetValueChecker(detailItem.Type, detailItem.Expect)
if err != nil {
errField := fmt.Sprintf("spec.[name=%s].detail.[name=%s]", subCheckerParam.Name, detailItem.Name)
paramCheckErr.ErrField = append(paramCheckErr.ErrField, errField)
c.logger.Printf("Failed to Init KernelModuleChecker, config file: <%s>, field: <%s>, because: %s",
c.conf.FilePath, errField, err.Error())
return nil
}
return vc
}
func (c *kernelModuleChecker) Check() ItemRslt {
rslt := ItemRslt{
Key: c.conf.Name,
Result: ResultValid,
Doc: c.conf.Doc,
SubItems: make([]SubItemRslt, 0, len(c.subCheckers)),
}
kms, err := c.getKernelModulesLoaded(c.ctx.RootPath)
if err != nil {
rslt.Result = ResultError
return rslt
}
for _, subChecker := range c.subCheckers {
subItemRslt := c.execSubChecker(subChecker, kms)
if subItemRslt.Result == ResultInvalid {
rslt.Result = ResultInvalid
}
rslt.SubItems = append(rslt.SubItems, subItemRslt)
}
return rslt
}
func (c *kernelModuleChecker) execSubChecker(subChecker KernelModuleSubChecker,
kernelModuleLoaded map[string]interface{}) SubItemRslt {
realMsg := make([]string, 0)
expectMsg := []string{"Loaded"}
subItemRslt := SubItemRslt{
Key: subChecker.param.Name,
Result: ResultValid,
Doc: subChecker.param.Doc,
}
name := subChecker.param.ModuleName
if len(name) == 0 {
name = subChecker.param.Name
}
_, ok := kernelModuleLoaded[name]
if !ok {
subItemRslt.Expect = "Loaded"
subItemRslt.Real = "Not Loaded"
subItemRslt.Result = ResultInvalid
return subItemRslt
}
realMsg = append(realMsg, "Loaded")
for _, detailChecker := range subChecker.detailCheckers {
expectMsg = append(expectMsg, fmt.Sprintf("%s=%v", detailChecker.detailPath, detailChecker.expect))
content, err := c.getKernelModuleDetail(c.ctx.RootPath, name, detailChecker.detailPath)
if err != nil {
realMsg = append(realMsg, fmt.Sprintf("%s=Read Failed", detailChecker.detailPath))
subItemRslt.Result = ResultInvalid
continue
}
checkRslt := detailChecker.valueChecker.Check(content)
realMsg = append(realMsg, fmt.Sprintf("%s=%v", detailChecker.detailPath, content))
if !checkRslt {
subItemRslt.Result = ResultInvalid
}
}
subItemRslt.Expect = strings.Join(expectMsg, "\n")
subItemRslt.Real = strings.Join(realMsg, "\n")
return subItemRslt
}
func (c *kernelModuleChecker) getKernelModulesLoaded(rootPath string) (map[string]interface{}, error) {
rslt := make(map[string]interface{})
moduleFile, err := os.Open(filepath.Join(rootPath, procModulesPath))
if err != nil {
c.logger.Printf("Failed to read /proc/modules, because: <%s> ", err.Error())
return rslt, fmt.Errorf("failed to read module from /proc/modules")
}
defer utils.CloseAll(c.logger, moduleFile)
scanner := bufio.NewScanner(moduleFile)
for scanner.Scan() {
if fields := strings.Fields(scanner.Text()); len(fields) >= moduleStatusIndex+1 {
moduleName := fields[0]
status := fields[moduleStatusIndex]
if status == "Live" {
rslt[moduleName] = struct{}{}
}
}
}
moduleEntries, err := os.ReadDir(filepath.Join(rootPath, sysModulePath))
if err != nil {
c.logger.Printf("Failed to read /sys/module, because: <%s>", err.Error())
return rslt, fmt.Errorf("failed to read module from /sys/module/")
}
for _, moduleEntry := range moduleEntries {
if moduleEntry.IsDir() {
rslt[moduleEntry.Name()] = struct{}{}
}
}
return rslt, nil
}
func (c *kernelModuleChecker) getKernelModuleDetail(rootPath, module, detailPath string) (string, error) {
fullPath := filepath.Join(rootPath, "/sys/module", module, detailPath)
bytes, err := os.ReadFile(fullPath)
if err != nil {
c.logger.Printf("Failed to read file: <%s>, because: %s", fullPath, err.Error())
return "", fmt.Errorf("failed read file: <%s>", detailPath)
}
return string(bytes), nil
}
func init() {
RegisterCheckerFactory(
"kernel-module-checker",
func() Checker {
return &kernelModuleChecker{}
},
)
RegisterCheckerFactory(
"kernel-module",
func() Checker {
return &kernelModuleChecker{}
},
)
}