mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 22:12:28 +01:00
Add Linux Kernel cataloger (#1694)
* add kernel handler Signed-off-by: Avi Deitcher <avi@deitcher.net> * [wip] combine kernel and kernel module cataloging Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * [wip] combine kernel and kernel module cataloging Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Signed-off-by: Avi Deitcher <avi@deitcher.net> * rename Kernel package to LinuxKernel package Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * split kernel and module packages within cataloger Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * wire up application configuration with kernel cataloger options Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * dont use references for packages on relationships Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * fix linting and tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * kernel cataloger should be resistent to partial failure Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * log upon kernel module metadata missing Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add tests for linux kernel cataloger Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * update integration tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * update cli package test counts Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add evidence annotations for kernel packages Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * reduce noise in cli test output Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * missed cli test to reduce noise for Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * fix package counts Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * update docs with linux kernel cataloging refs Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * bump json schema with new metadata fields Signed-off-by: Alex Goodman <alex.goodman@anchore.com> --------- Signed-off-by: Avi Deitcher <avi@deitcher.net> Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Signed-off-by: <> Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
co-authored by
Alex Goodman
parent
5d156b8241
commit
cc731c7b19
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
func SourceInfo(p pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
@@ -45,6 +46,10 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
case pkg.LinuxKernelPkg:
|
||||
answer = "acquired package info from linux kernel archive"
|
||||
case pkg.LinuxKernelModulePkg:
|
||||
answer = "acquired package info from linux kernel module files"
|
||||
case pkg.NixPkg:
|
||||
answer = "acquired package info from nix store path"
|
||||
default:
|
||||
|
||||
@@ -199,6 +199,22 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from linux kernel archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from linux kernel module files",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NixPkg,
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "7.1.2",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json"
|
||||
"version": "7.1.3",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "7.1.2",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json"
|
||||
"version": "7.1.3",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "7.1.2",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json"
|
||||
"version": "7.1.3",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/haskell"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/nix"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
||||
@@ -55,6 +56,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
||||
nix.NewStoreCataloger(),
|
||||
sbom.NewSBOMCataloger(),
|
||||
binary.NewCataloger(),
|
||||
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
@@ -88,6 +90,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
@@ -126,6 +129,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ package cataloger
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
)
|
||||
|
||||
// TODO: these field naming vs helper function naming schemes are inconsistent.
|
||||
|
||||
type Config struct {
|
||||
Search SearchConfig
|
||||
Golang golang.GoCatalogerOpts
|
||||
LinuxKernel kernel.LinuxCatalogerConfig
|
||||
Catalogers []string
|
||||
Parallelism int
|
||||
}
|
||||
@@ -16,6 +20,7 @@ func DefaultConfig() Config {
|
||||
return Config{
|
||||
Search: DefaultSearchConfig(),
|
||||
Parallelism: 1,
|
||||
LinuxKernel: kernel.DefaultLinuxCatalogerConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,3 +34,7 @@ func (c Config) Java() java.Config {
|
||||
func (c Config) Go() golang.GoCatalogerOpts {
|
||||
return c.Golang
|
||||
}
|
||||
|
||||
func (c Config) Kernel() kernel.LinuxCatalogerConfig {
|
||||
return c.LinuxKernel
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Package kernel provides a concrete Cataloger implementation for linux kernel and module files.
|
||||
*/
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var _ pkg.Cataloger = (*LinuxKernelCataloger)(nil)
|
||||
|
||||
type LinuxCatalogerConfig struct {
|
||||
CatalogModules bool
|
||||
}
|
||||
|
||||
type LinuxKernelCataloger struct {
|
||||
cfg LinuxCatalogerConfig
|
||||
}
|
||||
|
||||
func DefaultLinuxCatalogerConfig() LinuxCatalogerConfig {
|
||||
return LinuxCatalogerConfig{
|
||||
CatalogModules: true,
|
||||
}
|
||||
}
|
||||
|
||||
var kernelArchiveGlobs = []string{
|
||||
"**/kernel",
|
||||
"**/kernel-*",
|
||||
"**/vmlinux",
|
||||
"**/vmlinux-*",
|
||||
"**/vmlinuz",
|
||||
"**/vmlinuz-*",
|
||||
}
|
||||
|
||||
var kernelModuleGlobs = []string{
|
||||
"**/lib/modules/**/*.ko",
|
||||
}
|
||||
|
||||
// NewLinuxKernelCataloger returns a new kernel files cataloger object.
|
||||
func NewLinuxKernelCataloger(cfg LinuxCatalogerConfig) *LinuxKernelCataloger {
|
||||
return &LinuxKernelCataloger{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (l LinuxKernelCataloger) Name() string {
|
||||
return "linux-kernel-cataloger"
|
||||
}
|
||||
|
||||
func (l LinuxKernelCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var allPackages []pkg.Package
|
||||
var allRelationships []artifact.Relationship
|
||||
var errs error
|
||||
|
||||
kernelPackages, kernelRelationships, err := generic.NewCataloger(l.Name()).WithParserByGlobs(parseLinuxKernelFile, kernelArchiveGlobs...).Catalog(resolver)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
allRelationships = append(allRelationships, kernelRelationships...)
|
||||
allPackages = append(allPackages, kernelPackages...)
|
||||
|
||||
if l.cfg.CatalogModules {
|
||||
modulePackages, moduleRelationships, err := generic.NewCataloger(l.Name()).WithParserByGlobs(parseLinuxKernelModuleFile, kernelModuleGlobs...).Catalog(resolver)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
allPackages = append(allPackages, modulePackages...)
|
||||
|
||||
moduleToKernelRelationships := createKernelToModuleRelationships(kernelPackages, modulePackages)
|
||||
allRelationships = append(allRelationships, moduleRelationships...)
|
||||
allRelationships = append(allRelationships, moduleToKernelRelationships...)
|
||||
}
|
||||
|
||||
return allPackages, allRelationships, errs
|
||||
}
|
||||
|
||||
func createKernelToModuleRelationships(kernelPackages, modulePackages []pkg.Package) []artifact.Relationship {
|
||||
// organize kernel and module packages by kernel version
|
||||
kernelPackagesByVersion := make(map[string][]*pkg.Package)
|
||||
for idx, p := range kernelPackages {
|
||||
kernelPackagesByVersion[p.Version] = append(kernelPackagesByVersion[p.Version], &kernelPackages[idx])
|
||||
}
|
||||
|
||||
modulesByKernelVersion := make(map[string][]*pkg.Package)
|
||||
for idx, p := range modulePackages {
|
||||
m, ok := p.Metadata.(pkg.LinuxKernelModuleMetadata)
|
||||
if !ok {
|
||||
log.Debug("linux-kernel-module package found without metadata: %s@%s", p.Name, p.Version)
|
||||
continue
|
||||
}
|
||||
modulesByKernelVersion[m.KernelVersion] = append(modulesByKernelVersion[m.KernelVersion], &modulePackages[idx])
|
||||
}
|
||||
|
||||
// create relationships between kernel and modules: [module] --(depends on)--> [kernel]
|
||||
// since we try to use singular directions for relationships, we'll use "dependency of" here instead:
|
||||
// [kernel] --(dependency of)--> [module]
|
||||
var moduleToKernelRelationships []artifact.Relationship
|
||||
for kernelVersion, modules := range modulesByKernelVersion {
|
||||
kps, ok := kernelPackagesByVersion[kernelVersion]
|
||||
if !ok {
|
||||
// it's ok if there is a module that has no installed kernel...
|
||||
continue
|
||||
}
|
||||
|
||||
// we don't know which kernel is the "right" one, so we'll create a relationship for each one
|
||||
for _, kp := range kps {
|
||||
for _, mp := range modules {
|
||||
moduleToKernelRelationships = append(moduleToKernelRelationships, artifact.Relationship{
|
||||
// note: relationships should have Package objects, not pointers
|
||||
From: *kp,
|
||||
To: *mp,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moduleToKernelRelationships
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_JavascriptCataloger(t *testing.T) {
|
||||
kernelPkg := pkg.Package{
|
||||
Name: "linux-kernel",
|
||||
Version: "6.2.9-200.fc37.x86_64",
|
||||
FoundBy: "linux-kernel-cataloger",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewVirtualLocation(
|
||||
"/lib/modules/6.2.9-200.fc37.x86_64/vmlinuz",
|
||||
"/lib/modules/6.2.9-200.fc37.x86_64/vmlinuz",
|
||||
),
|
||||
),
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
PURL: "pkg:generic/linux-kernel@6.2.9-200.fc37.x86_64",
|
||||
MetadataType: pkg.LinuxKernelMetadataType,
|
||||
Metadata: pkg.LinuxKernelMetadata{
|
||||
Name: "",
|
||||
Architecture: "x86",
|
||||
Version: "6.2.9-200.fc37.x86_64",
|
||||
ExtendedVersion: "6.2.9-200.fc37.x86_64 (mockbuild@bkernel02.iad2.fedoraproject.org) #1 SMP PREEMPT_DYNAMIC Thu Mar 30 22:31:57 UTC 2023",
|
||||
BuildTime: "",
|
||||
Author: "",
|
||||
Format: "bzImage",
|
||||
RWRootFS: false,
|
||||
SwapDevice: 0,
|
||||
RootDevice: 0,
|
||||
VideoMode: "Video mode 65535",
|
||||
},
|
||||
}
|
||||
|
||||
kernelModulePkg := pkg.Package{
|
||||
Name: "fsa4480",
|
||||
Version: "",
|
||||
FoundBy: "linux-kernel-cataloger",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewVirtualLocation("/lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko",
|
||||
"/lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko",
|
||||
),
|
||||
),
|
||||
Licenses: []string{
|
||||
"GPL v2",
|
||||
},
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
PURL: "pkg:generic/fsa4480",
|
||||
MetadataType: pkg.LinuxKernelModuleMetadataType,
|
||||
Metadata: pkg.LinuxKernelModuleMetadata{
|
||||
Name: "fsa4480",
|
||||
Version: "",
|
||||
SourceVersion: "",
|
||||
License: "GPL v2",
|
||||
Path: "/lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko",
|
||||
Description: "ON Semiconductor FSA4480 driver",
|
||||
KernelVersion: "6.2.9-200.fc37.x86_64",
|
||||
VersionMagic: "6.2.9-200.fc37.x86_64 SMP preempt mod_unload ",
|
||||
Parameters: map[string]pkg.LinuxKernelModuleParameter{},
|
||||
},
|
||||
}
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
kernelPkg,
|
||||
kernelModulePkg,
|
||||
}
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: kernelPkg,
|
||||
To: kernelModulePkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, "image-kernel-and-modules").
|
||||
IgnoreLocationLayer().
|
||||
Expects(expectedPkgs, expectedRelationships).
|
||||
TestCataloger(t,
|
||||
NewLinuxKernelCataloger(
|
||||
LinuxCatalogerConfig{
|
||||
CatalogModules: true,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const linuxKernelPackageName = "linux-kernel"
|
||||
|
||||
func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, locations ...source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: linuxKernelPackageName,
|
||||
Version: metadata.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
PURL: packageURL(linuxKernelPackageName, metadata.Version),
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
MetadataType: pkg.LinuxKernelMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, locations ...source.Location) pkg.Package {
|
||||
var licenses []string
|
||||
if metadata.License != "" {
|
||||
licenses = []string{metadata.License}
|
||||
} else {
|
||||
licenses = []string{}
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Licenses: licenses,
|
||||
PURL: packageURL(metadata.Name, metadata.Version),
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
MetadataType: pkg.LinuxKernelModuleMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// packageURL returns the PURL for the specific Kernel package (see https://github.com/package-url/purl-spec)
|
||||
func packageURL(name, version string) string {
|
||||
var namespace string
|
||||
|
||||
fields := strings.SplitN(name, "/", 2)
|
||||
if len(fields) > 1 {
|
||||
namespace = fields[0]
|
||||
name = fields[1]
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeGeneric,
|
||||
namespace,
|
||||
name,
|
||||
version,
|
||||
nil,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/deitch/magic/pkg/magic"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const linuxKernelMagicName = "Linux kernel"
|
||||
|
||||
func parseLinuxKernelFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
unionReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
|
||||
}
|
||||
magicType, err := magic.GetType(unionReader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get magic type for file: %w", err)
|
||||
}
|
||||
if len(magicType) < 1 || magicType[0] != linuxKernelMagicName {
|
||||
return nil, nil, nil
|
||||
}
|
||||
metadata := parseLinuxKernelMetadata(magicType)
|
||||
if metadata.Version == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
newLinuxKernelPackage(
|
||||
metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseLinuxKernelMetadata(magicType []string) (p pkg.LinuxKernelMetadata) {
|
||||
// Linux kernel x86 boot executable bzImage,
|
||||
// version 5.10.121-linuxkit (root@buildkitsandbox) #1 SMP Fri Dec 2 10:35:42 UTC 2022,
|
||||
// RO-rootFS,
|
||||
// swap_dev 0XA,
|
||||
// Normal VGA
|
||||
for _, t := range magicType {
|
||||
switch {
|
||||
case strings.HasPrefix(t, "x86 "):
|
||||
p.Architecture = "x86"
|
||||
case strings.Contains(t, "ARM64 "):
|
||||
p.Architecture = "arm64"
|
||||
case strings.Contains(t, "ARM "):
|
||||
p.Architecture = "arm"
|
||||
case t == "bzImage":
|
||||
p.Format = "bzImage"
|
||||
case t == "zImage":
|
||||
p.Format = "zImage"
|
||||
case strings.HasPrefix(t, "version "):
|
||||
p.ExtendedVersion = strings.TrimPrefix(t, "version ")
|
||||
fields := strings.Fields(p.ExtendedVersion)
|
||||
if len(fields) > 0 {
|
||||
p.Version = fields[0]
|
||||
}
|
||||
case strings.Contains(t, "rootFS") && strings.HasPrefix(t, "RW-"):
|
||||
p.RWRootFS = true
|
||||
case strings.HasPrefix(t, "swap_dev "):
|
||||
swapDevStr := strings.TrimPrefix(t, "swap_dev ")
|
||||
swapDev, err := strconv.ParseInt(swapDevStr, 16, 32)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse swap device: %s", err)
|
||||
continue
|
||||
}
|
||||
p.SwapDevice = int(swapDev)
|
||||
case strings.HasPrefix(t, "root_dev "):
|
||||
rootDevStr := strings.TrimPrefix(t, "root_dev ")
|
||||
rootDev, err := strconv.ParseInt(rootDevStr, 16, 32)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse root device: %s", err)
|
||||
continue
|
||||
}
|
||||
p.SwapDevice = int(rootDev)
|
||||
case strings.Contains(t, "VGA") || strings.Contains(t, "Video"):
|
||||
p.VideoMode = t
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const modinfoName = ".modinfo"
|
||||
|
||||
func parseLinuxKernelModuleFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
unionReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
|
||||
}
|
||||
metadata, err := parseLinuxKernelModuleMetadata(unionReader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse kernel module metadata: %w", err)
|
||||
}
|
||||
if metadata == nil || metadata.KernelVersion == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
metadata.Path = reader.Location.RealPath
|
||||
|
||||
return []pkg.Package{
|
||||
newLinuxKernelModulePackage(
|
||||
*metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseLinuxKernelModuleMetadata(r unionreader.UnionReader) (p *pkg.LinuxKernelModuleMetadata, err error) {
|
||||
// filename: /lib/modules/5.15.0-1031-aws/kernel/zfs/zzstd.ko
|
||||
// version: 1.4.5a
|
||||
// license: Dual BSD/GPL
|
||||
// description: ZSTD Compression for ZFS
|
||||
// srcversion: F1F818A6E016499AB7F826E
|
||||
// depends: spl
|
||||
// retpoline: Y
|
||||
// name: zzstd
|
||||
// vermagic: 5.15.0-1031-aws SMP mod_unload modversions
|
||||
// sig_id: PKCS#7
|
||||
// signer: Build time autogenerated kernel key
|
||||
// sig_key: 49:A9:55:87:90:5B:33:41:AF:C0:A7:BE:2A:71:6C:D2:CA:34:E0:AE
|
||||
// sig_hashalgo: sha512
|
||||
//
|
||||
// OR
|
||||
//
|
||||
// filename: /home/ubuntu/eve/rootfs/lib/modules/5.10.121-linuxkit/kernel/drivers/net/wireless/realtek/rtl8821cu/8821cu.ko
|
||||
// version: v5.4.1_28754.20180921_COEX20180712-3232
|
||||
// author: Realtek Semiconductor Corp.
|
||||
// description: Realtek Wireless Lan Driver
|
||||
// license: GPL
|
||||
// srcversion: 960CCC648A0E0369171A2C9
|
||||
// depends: cfg80211
|
||||
// retpoline: Y
|
||||
// name: 8821cu
|
||||
// vermagic: 5.10.121-linuxkit SMP mod_unload
|
||||
p = &pkg.LinuxKernelModuleMetadata{
|
||||
Parameters: make(map[string]pkg.LinuxKernelModuleParameter),
|
||||
}
|
||||
f, err := elf.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
modinfo := f.Section(modinfoName)
|
||||
if modinfo == nil {
|
||||
return nil, fmt.Errorf("no section %s", modinfoName)
|
||||
}
|
||||
b, err := modinfo.Data()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading secion %s: %w", modinfoName, err)
|
||||
}
|
||||
var (
|
||||
entry []byte
|
||||
)
|
||||
for _, b2 := range b {
|
||||
if b2 == 0 {
|
||||
if err := addLinuxKernelModuleEntry(p, entry); err != nil {
|
||||
return nil, fmt.Errorf("error parsing entry %s: %w", string(entry), err)
|
||||
}
|
||||
entry = []byte{}
|
||||
continue
|
||||
}
|
||||
entry = append(entry, b2)
|
||||
}
|
||||
if err := addLinuxKernelModuleEntry(p, entry); err != nil {
|
||||
return nil, fmt.Errorf("error parsing entry %s: %w", string(entry), err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func addLinuxKernelModuleEntry(k *pkg.LinuxKernelModuleMetadata, entry []byte) error {
|
||||
if len(entry) == 0 {
|
||||
return nil
|
||||
}
|
||||
var key, value string
|
||||
parts := strings.SplitN(string(entry), "=", 2)
|
||||
if len(parts) > 0 {
|
||||
key = parts[0]
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "version":
|
||||
k.Version = value
|
||||
case "license":
|
||||
k.License = value
|
||||
case "author":
|
||||
k.Author = value
|
||||
case "name":
|
||||
k.Name = value
|
||||
case "vermagic":
|
||||
k.VersionMagic = value
|
||||
fields := strings.Fields(value)
|
||||
if len(fields) > 0 {
|
||||
k.KernelVersion = fields[0]
|
||||
}
|
||||
case "srcversion":
|
||||
k.SourceVersion = value
|
||||
case "description":
|
||||
k.Description = value
|
||||
case "parm":
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid parm entry: %s", value)
|
||||
}
|
||||
if m, ok := k.Parameters[parts[0]]; !ok {
|
||||
k.Parameters[parts[0]] = pkg.LinuxKernelModuleParameter{Description: parts[1]}
|
||||
} else {
|
||||
m.Description = parts[1]
|
||||
}
|
||||
case "parmtype":
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid parmtype entry: %s", value)
|
||||
}
|
||||
if m, ok := k.Parameters[parts[0]]; !ok {
|
||||
k.Parameters[parts[0]] = pkg.LinuxKernelModuleParameter{Type: parts[1]}
|
||||
} else {
|
||||
m.Type = parts[1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM fedora:37@sha256:3f987b7657e944cf87a129cc262982d4f80e38bd98f7db313ccaf90ca7069dd2
|
||||
|
||||
RUN dnf install 'dnf-command(download)' cpio xz -y
|
||||
RUN dnf download kernel-core kernel-modules-core -y
|
||||
|
||||
RUN rpm2cpio kernel-core-*.rpm | cpio -t && \
|
||||
rpm2cpio kernel-core-*.rpm | cpio -idmv ./lib/modules/6.2.9-200.fc37.x86_64/vmlinuz
|
||||
|
||||
RUN rpm2cpio kernel-modules-core-*.rpm | cpio -t && \
|
||||
rpm2cpio kernel-modules-core-*.rpm | cpio -idmv ./lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko.xz
|
||||
|
||||
RUN unxz /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko.xz
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=0 /lib/modules/6.2.9-200.fc37.x86_64/vmlinuz /lib/modules/6.2.9-200.fc37.x86_64/vmlinuz
|
||||
COPY --from=0 /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko
|
||||
@@ -0,0 +1,34 @@
|
||||
package pkg
|
||||
|
||||
// LinuxKernelMetadata represents all captured data for a Linux kernel
|
||||
type LinuxKernelMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name" cyclonedx:"name"`
|
||||
Architecture string `mapstructure:"architecture" json:"architecture" cyclonedx:"architecture"`
|
||||
Version string `mapstructure:"version" json:"version" cyclonedx:"version"`
|
||||
ExtendedVersion string `mapstructure:"extendedVersion" json:"extendedVersion,omitempty" cyclonedx:"extendedVersion"`
|
||||
BuildTime string `mapstructure:"buildTime" json:"buildTime,omitempty" cyclonedx:"buildTime"`
|
||||
Author string `mapstructure:"author" json:"author,omitempty" cyclonedx:"author"`
|
||||
Format string `mapstructure:"format" json:"format,omitempty" cyclonedx:"format"`
|
||||
RWRootFS bool `mapstructure:"rwRootFS" json:"rwRootFS,omitempty" cyclonedx:"rwRootFS"`
|
||||
SwapDevice int `mapstructure:"swapDevice" json:"swapDevice,omitempty" cyclonedx:"swapDevice"`
|
||||
RootDevice int `mapstructure:"rootDevice" json:"rootDevice,omitempty" cyclonedx:"rootDevice"`
|
||||
VideoMode string `mapstructure:"videoMode" json:"videoMode,omitempty" cyclonedx:"videoMode"`
|
||||
}
|
||||
|
||||
type LinuxKernelModuleMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name,omitempty" cyclonedx:"name"`
|
||||
Version string `mapstructure:"version" json:"version,omitempty" cyclonedx:"version"`
|
||||
SourceVersion string `mapstructure:"sourceVersion" json:"sourceVersion,omitempty" cyclonedx:"sourceVersion"`
|
||||
Path string `mapstructure:"path" json:"path,omitempty" cyclonedx:"path"`
|
||||
Description string `mapstructure:"description" json:"description,omitempty" cyclonedx:"description"`
|
||||
Author string `mapstructure:"author" json:"author,omitempty" cyclonedx:"author"`
|
||||
License string `mapstructure:"license" json:"license,omitempty" cyclonedx:"license"`
|
||||
KernelVersion string `mapstructure:"kernelVersion" json:"kernelVersion,omitempty" cyclonedx:"kernelVersion"`
|
||||
VersionMagic string `mapstructure:"versionMagic" json:"versionMagic,omitempty" cyclonedx:"versionMagic"`
|
||||
Parameters map[string]LinuxKernelModuleParameter `mapstructure:"parameters" json:"parameters,omitempty" cyclonedx:"parameters"`
|
||||
}
|
||||
|
||||
type LinuxKernelModuleParameter struct {
|
||||
Type string `mapstructure:"type" json:"type,omitempty" cyclonedx:"type"`
|
||||
Description string `mapstructure:"description" json:"description,omitempty" cyclonedx:"description"`
|
||||
}
|
||||
@@ -26,6 +26,8 @@ const (
|
||||
HackageMetadataType MetadataType = "HackageMetadataType"
|
||||
JavaMetadataType MetadataType = "JavaMetadata"
|
||||
KbPackageMetadataType MetadataType = "KbPackageMetadata"
|
||||
LinuxKernelMetadataType MetadataType = "LinuxKernelMetadata"
|
||||
LinuxKernelModuleMetadataType MetadataType = "LinuxKernelModuleMetadata"
|
||||
MixLockMetadataType MetadataType = "MixLockMetadataType"
|
||||
NixStoreMetadataType MetadataType = "NixStoreMetadata"
|
||||
NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata"
|
||||
@@ -55,6 +57,8 @@ var AllMetadataTypes = []MetadataType{
|
||||
HackageMetadataType,
|
||||
JavaMetadataType,
|
||||
KbPackageMetadataType,
|
||||
LinuxKernelMetadataType,
|
||||
LinuxKernelModuleMetadataType,
|
||||
MixLockMetadataType,
|
||||
NixStoreMetadataType,
|
||||
NpmPackageJSONMetadataType,
|
||||
@@ -84,6 +88,8 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
|
||||
HackageMetadataType: reflect.TypeOf(HackageMetadata{}),
|
||||
JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
|
||||
KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}),
|
||||
LinuxKernelMetadataType: reflect.TypeOf(LinuxKernelMetadata{}),
|
||||
LinuxKernelModuleMetadataType: reflect.TypeOf(LinuxKernelModuleMetadata{}),
|
||||
MixLockMetadataType: reflect.TypeOf(MixLockMetadata{}),
|
||||
NixStoreMetadataType: reflect.TypeOf(NixStoreMetadata{}),
|
||||
NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}),
|
||||
|
||||
+17
-1
@@ -26,6 +26,8 @@ const (
|
||||
JavaPkg Type = "java-archive"
|
||||
JenkinsPluginPkg Type = "jenkins-plugin"
|
||||
KbPkg Type = "msrc-kb"
|
||||
LinuxKernelPkg Type = "linux-kernel"
|
||||
LinuxKernelModulePkg Type = "linux-kernel-module"
|
||||
NixPkg Type = "nix"
|
||||
NpmPkg Type = "npm"
|
||||
PhpComposerPkg Type = "php-composer"
|
||||
@@ -52,6 +54,8 @@ var AllPkgs = []Type{
|
||||
JavaPkg,
|
||||
JenkinsPluginPkg,
|
||||
KbPkg,
|
||||
LinuxKernelPkg,
|
||||
LinuxKernelModulePkg,
|
||||
NixPkg,
|
||||
NpmPkg,
|
||||
PhpComposerPkg,
|
||||
@@ -88,6 +92,10 @@ func (t Type) PackageURLType() string {
|
||||
return packageurl.TypeHackage
|
||||
case JavaPkg, JenkinsPluginPkg:
|
||||
return packageurl.TypeMaven
|
||||
case LinuxKernelPkg:
|
||||
return "generic/linux-kernel"
|
||||
case LinuxKernelModulePkg:
|
||||
return packageurl.TypeGeneric
|
||||
case PhpComposerPkg:
|
||||
return packageurl.TypeComposer
|
||||
case PythonPkg:
|
||||
@@ -114,7 +122,11 @@ func TypeFromPURL(p string) Type {
|
||||
return UnknownPkg
|
||||
}
|
||||
|
||||
return TypeByName(purl.Type)
|
||||
ptype := purl.Type
|
||||
if ptype == "generic" {
|
||||
ptype = purl.Name
|
||||
}
|
||||
return TypeByName(ptype)
|
||||
}
|
||||
|
||||
func TypeByName(name string) Type {
|
||||
@@ -155,6 +167,10 @@ func TypeByName(name string) Type {
|
||||
return PortagePkg
|
||||
case packageurl.TypeHex:
|
||||
return HexPkg
|
||||
case "linux-kernel":
|
||||
return LinuxKernelPkg
|
||||
case "linux-kernel-module":
|
||||
return LinuxKernelModulePkg
|
||||
case "nix":
|
||||
return NixPkg
|
||||
default:
|
||||
|
||||
@@ -83,6 +83,10 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
purl: "pkg:hex/hpax/hpax@0.1.1",
|
||||
expected: HexPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:generic/linux-kernel@5.10.15",
|
||||
expected: LinuxKernelPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
expected: NixPkg,
|
||||
@@ -101,6 +105,7 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
expectedTypes.Remove(string(JenkinsPluginPkg))
|
||||
expectedTypes.Remove(string(PortagePkg))
|
||||
expectedTypes.Remove(string(BinaryPkg))
|
||||
expectedTypes.Remove(string(LinuxKernelModulePkg))
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(string(test.expected), func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user