diff --git a/README.md b/README.md index 85fba926a..7dff47bcf 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ For commercial support options with Syft or Grype, please [contact Anchore](http - Java (jar, ear, war, par, sar, nar, native-image) - JavaScript (npm, yarn) - Jenkins Plugins (jpi, hpi) +- Linux kernel archives (vmlinz) +- Linux kernel modules (ko) - Nix (outputs in /nix/store) - PHP (composer) - Python (wheel, egg, poetry, requirements.txt) @@ -513,6 +515,11 @@ golang: # SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var local-mod-cache-dir: "" +linux-kernel: + # whether to catalog linux kernel modules found within lib/modules/** directories + # SYFT_LINUX_KERNEL_CATALOG_MODULES env var + catalog-modules: true + # cataloging file contents is exposed through the power-user subcommand file-contents: cataloger: diff --git a/go.mod b/go.mod index 277a57afe..b902f0bd7 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 github.com/anchore/stereoscope v0.0.0-20230406143206-e95d60a265e3 + github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da github.com/docker/docker v23.0.3+incompatible github.com/google/go-containerregistry v0.14.0 github.com/google/licensecheck v0.3.1 diff --git a/go.sum b/go.sum index 443725479..b2ce2a707 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= +github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= diff --git a/internal/config/application.go b/internal/config/application.go index 3785b8642..98dec82c8 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -19,6 +19,7 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/pkg/cataloger" golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang" + "github.com/anchore/syft/syft/pkg/cataloger/kernel" ) var ( @@ -50,6 +51,7 @@ type Application struct { Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"` Package pkg `yaml:"package" json:"package" mapstructure:"package"` Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"` + LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"` Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"` FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"` FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"` @@ -76,6 +78,9 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config { SearchLocalModCacheLicenses: cfg.Golang.SearchLocalModCacheLicenses, LocalModCacheDir: cfg.Golang.LocalModCacheDir, }, + LinuxKernel: kernel.LinuxCatalogerConfig{ + CatalogModules: cfg.LinuxKernel.CatalogModules, + }, } } diff --git a/internal/config/linux_kernel.go b/internal/config/linux_kernel.go new file mode 100644 index 000000000..1d0b38e96 --- /dev/null +++ b/internal/config/linux_kernel.go @@ -0,0 +1,11 @@ +package config + +import "github.com/spf13/viper" + +type linuxKernel struct { + CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"` +} + +func (cfg linuxKernel) loadDefaultValues(v *viper.Viper) { + v.SetDefault("linux-kernel.catalog-modules", true) +} diff --git a/internal/constants.go b/internal/constants.go index 127fb3924..c5dcea13a 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -6,5 +6,5 @@ const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "7.1.2" + JSONSchemaVersion = "7.1.3" ) diff --git a/schema/json/generate.go b/schema/json/generate.go index 3f4efaf4a..db5d1adbe 100644 --- a/schema/json/generate.go +++ b/schema/json/generate.go @@ -45,6 +45,8 @@ type artifactMetadataContainer struct { Hackage pkg.HackageMetadata Java pkg.JavaMetadata KbPackage pkg.KbPackageMetadata + LinuxKernel pkg.LinuxKernelMetadata + LinuxKernelModule pkg.LinuxKernelModuleMetadata Nix pkg.NixStoreMetadata NpmPackage pkg.NpmPackageJSONMetadata NpmPackageLock pkg.NpmPackageLockJSONMetadata diff --git a/schema/json/schema-7.1.3.json b/schema/json/schema-7.1.3.json new file mode 100644 index 000000000..dd50bd0d5 --- /dev/null +++ b/schema/json/schema-7.1.3.json @@ -0,0 +1,1768 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/anchore/syft/syft/formats/syftjson/model/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "AlpmMetadata": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "license": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "license", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "ApkMetadata": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "license", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "BinaryMetadata": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CargoPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoapodsMetadata": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "ConanLockMetadata": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "type": "string" + }, + "build_requires": { + "type": "string" + }, + "py_requires": { + "type": "string" + }, + "options": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "ConanMetadata": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "secrets": { + "items": { + "$ref": "#/$defs/Secrets" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "DpkgMetadata": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType" + ] + }, + "GemMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "GolangBinMetadata": { + "properties": { + "goBuildSettings": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GolangModMetadata": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaManifest": { + "properties": { + "main": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "namedSections": { + "patternProperties": { + ".*": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "JavaMetadata": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/PomProperties" + }, + "pomProject": { + "$ref": "#/$defs/PomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "KbPackageMetadata": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "LinuxKernelMetadata": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModuleMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "MixLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "NixStoreMetadata": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "NpmPackageJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "licenses", + "homepage", + "description", + "url", + "private" + ] + }, + "NpmPackageLockJSONMetadata": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmMetadata" + }, + { + "$ref": "#/$defs/ApkMetadata" + }, + { + "$ref": "#/$defs/BinaryMetadata" + }, + { + "$ref": "#/$defs/CargoPackageMetadata" + }, + { + "$ref": "#/$defs/CocoapodsMetadata" + }, + { + "$ref": "#/$defs/ConanLockMetadata" + }, + { + "$ref": "#/$defs/ConanMetadata" + }, + { + "$ref": "#/$defs/DartPubMetadata" + }, + { + "$ref": "#/$defs/DotnetDepsMetadata" + }, + { + "$ref": "#/$defs/DpkgMetadata" + }, + { + "$ref": "#/$defs/GemMetadata" + }, + { + "$ref": "#/$defs/GolangBinMetadata" + }, + { + "$ref": "#/$defs/GolangModMetadata" + }, + { + "$ref": "#/$defs/HackageMetadata" + }, + { + "$ref": "#/$defs/JavaMetadata" + }, + { + "$ref": "#/$defs/KbPackageMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelModuleMetadata" + }, + { + "$ref": "#/$defs/MixLockMetadata" + }, + { + "$ref": "#/$defs/NixStoreMetadata" + }, + { + "$ref": "#/$defs/NpmPackageJSONMetadata" + }, + { + "$ref": "#/$defs/NpmPackageLockJSONMetadata" + }, + { + "$ref": "#/$defs/PhpComposerJSONMetadata" + }, + { + "$ref": "#/$defs/PortageMetadata" + }, + { + "$ref": "#/$defs/PythonPackageMetadata" + }, + { + "$ref": "#/$defs/PythonPipfileLockMetadata" + }, + { + "$ref": "#/$defs/RebarLockMetadata" + }, + { + "$ref": "#/$defs/RpmMetadata" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "PomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/PomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "PomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PortageMetadata": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipfileLockMetadata": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "RebarLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "license": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmdbFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "license", + "vendor", + "modularityLabel", + "files" + ] + }, + "RpmdbFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "SearchResult": { + "properties": { + "classification": { + "type": "string" + }, + "lineNumber": { + "type": "integer" + }, + "lineOffset": { + "type": "integer" + }, + "seekPosition": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "classification", + "lineNumber", + "lineOffset", + "seekPosition", + "length" + ] + }, + "Secrets": { + "properties": { + "location": { + "$ref": "#/$defs/Coordinates" + }, + "secrets": { + "items": { + "$ref": "#/$defs/SearchResult" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "location", + "secrets" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "target": true + }, + "type": "object", + "required": [ + "id", + "type", + "target" + ] + } + } +} diff --git a/syft/formats/common/spdxhelpers/source_info.go b/syft/formats/common/spdxhelpers/source_info.go index 2222f757f..71007150c 100644 --- a/syft/formats/common/spdxhelpers/source_info.go +++ b/syft/formats/common/spdxhelpers/source_info.go @@ -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: diff --git a/syft/formats/common/spdxhelpers/source_info_test.go b/syft/formats/common/spdxhelpers/source_info_test.go index f70e00892..bb4eee7e8 100644 --- a/syft/formats/common/spdxhelpers/source_info_test.go +++ b/syft/formats/common/spdxhelpers/source_info_test.go @@ -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, diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden index f0ad7b3ae..c5b476c56 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden @@ -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" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index cccc7ae4e..0fb0b7907 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -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" } } diff --git a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index a5b6a2d83..3ad088543 100644 --- a/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden +++ b/syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden @@ -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" } } diff --git a/syft/pkg/cataloger/cataloger.go b/syft/pkg/cataloger/cataloger.go index 2d7cdf235..ca1ca0858 100644 --- a/syft/pkg/cataloger/cataloger.go +++ b/syft/pkg/cataloger/cataloger.go @@ -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) } diff --git a/syft/pkg/cataloger/config.go b/syft/pkg/cataloger/config.go index 2074f16e4..8a334a78a 100644 --- a/syft/pkg/cataloger/config.go +++ b/syft/pkg/cataloger/config.go @@ -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 +} diff --git a/syft/pkg/cataloger/kernel/cataloger.go b/syft/pkg/cataloger/kernel/cataloger.go new file mode 100644 index 000000000..492c20433 --- /dev/null +++ b/syft/pkg/cataloger/kernel/cataloger.go @@ -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 +} diff --git a/syft/pkg/cataloger/kernel/cataloger_test.go b/syft/pkg/cataloger/kernel/cataloger_test.go new file mode 100644 index 000000000..ec7927b73 --- /dev/null +++ b/syft/pkg/cataloger/kernel/cataloger_test.go @@ -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, + }, + ), + ) +} diff --git a/syft/pkg/cataloger/kernel/package.go b/syft/pkg/cataloger/kernel/package.go new file mode 100644 index 000000000..69fab21b3 --- /dev/null +++ b/syft/pkg/cataloger/kernel/package.go @@ -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() +} diff --git a/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go b/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go new file mode 100644 index 000000000..303e88192 --- /dev/null +++ b/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go @@ -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 +} diff --git a/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go b/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go new file mode 100644 index 000000000..fb0d818a5 --- /dev/null +++ b/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go @@ -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 +} diff --git a/syft/pkg/cataloger/kernel/test-fixtures/image-kernel-and-modules/Dockerfile b/syft/pkg/cataloger/kernel/test-fixtures/image-kernel-and-modules/Dockerfile new file mode 100644 index 000000000..1c99090bd --- /dev/null +++ b/syft/pkg/cataloger/kernel/test-fixtures/image-kernel-and-modules/Dockerfile @@ -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 diff --git a/syft/pkg/linux_kernel_metadata.go b/syft/pkg/linux_kernel_metadata.go new file mode 100644 index 000000000..3f69a284c --- /dev/null +++ b/syft/pkg/linux_kernel_metadata.go @@ -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"` +} diff --git a/syft/pkg/metadata.go b/syft/pkg/metadata.go index 6e79d0c3c..d6b8e14e0 100644 --- a/syft/pkg/metadata.go +++ b/syft/pkg/metadata.go @@ -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{}), diff --git a/syft/pkg/type.go b/syft/pkg/type.go index b9493553f..952ec2e99 100644 --- a/syft/pkg/type.go +++ b/syft/pkg/type.go @@ -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: diff --git a/syft/pkg/type_test.go b/syft/pkg/type_test.go index 76127f5cd..cfcd4b1d9 100644 --- a/syft/pkg/type_test.go +++ b/syft/pkg/type_test.go @@ -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) { diff --git a/test/cli/all_formats_convertible_test.go b/test/cli/all_formats_convertible_test.go index 63709d12c..4b3dd478d 100644 --- a/test/cli/all_formats_convertible_test.go +++ b/test/cli/all_formats_convertible_test.go @@ -55,11 +55,7 @@ func TestAllFormatsConvertable(t *testing.T) { for _, traitFn := range assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } diff --git a/test/cli/all_formats_expressible_test.go b/test/cli/all_formats_expressible_test.go index 1cc8e9e88..a5a9a710b 100644 --- a/test/cli/all_formats_expressible_test.go +++ b/test/cli/all_formats_expressible_test.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "strings" "testing" "github.com/stretchr/testify/require" @@ -34,11 +33,7 @@ func TestAllFormatsExpressible(t *testing.T) { for _, traitFn := range commonAssertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } diff --git a/test/cli/convert_cmd_test.go b/test/cli/convert_cmd_test.go index 9a6932cae..6cbd5b83c 100644 --- a/test/cli/convert_cmd_test.go +++ b/test/cli/convert_cmd_test.go @@ -40,11 +40,7 @@ func TestConvertCmd(t *testing.T) { for _, traitFn := range assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } diff --git a/test/cli/cyclonedx_valid_test.go b/test/cli/cyclonedx_valid_test.go index f72a18a80..bbd4b0b3a 100644 --- a/test/cli/cyclonedx_valid_test.go +++ b/test/cli/cyclonedx_valid_test.go @@ -52,11 +52,7 @@ func TestValidCycloneDX(t *testing.T) { for _, traitFn := range test.assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) validateCycloneDXJSON(t, stdout) }) @@ -95,11 +91,7 @@ func assertValidCycloneDX(tb testing.TB, stdout, stderr string, rc int) { tb.Errorf("expected no validation failures for cyclonedx-cli but got rc=%d", rc) } - if tb.Failed() { - tb.Log("STDOUT:\n", stdout) - tb.Log("STDERR:\n", stderr) - tb.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(tb, cmd, stdout, stderr) } // validate --input-format json --input-version v1_4 --input-file bom.json @@ -134,9 +126,5 @@ func validateCycloneDXJSON(t *testing.T, stdout string) { t.Errorf("expected no validation failures for cyclonedx-cli but found invalid BOM") } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) } diff --git a/test/cli/dir_root_scan_regression_test.go b/test/cli/dir_root_scan_regression_test.go index 88d5ce0d5..6fbea309a 100644 --- a/test/cli/dir_root_scan_regression_test.go +++ b/test/cli/dir_root_scan_regression_test.go @@ -2,7 +2,6 @@ package cli import ( "os/exec" - "strings" "testing" "time" ) @@ -37,10 +36,6 @@ func TestDirectoryScanCompletesWithinTimeout(t *testing.T) { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) } diff --git a/test/cli/packages_cmd_test.go b/test/cli/packages_cmd_test.go index 2e3749749..53e4d20e3 100644 --- a/test/cli/packages_cmd_test.go +++ b/test/cli/packages_cmd_test.go @@ -3,7 +3,6 @@ package cli import ( "fmt" "path/filepath" - "strings" "testing" ) @@ -97,7 +96,7 @@ func TestPackagesCmdFlags(t *testing.T) { name: "squashed-scope-flag", args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage}, assertions: []traitAssertion{ - assertPackageCount(35), + assertPackageCount(37), assertSuccessfulReturnCode, }, }, @@ -214,7 +213,7 @@ func TestPackagesCmdFlags(t *testing.T) { // the application config in the log matches that of what we expect to have been configured. assertInOutput("parallelism: 2"), assertInOutput("parallelism=2"), - assertPackageCount(35), + assertPackageCount(37), assertSuccessfulReturnCode, }, }, @@ -225,7 +224,7 @@ func TestPackagesCmdFlags(t *testing.T) { // the application config in the log matches that of what we expect to have been configured. assertInOutput("parallelism: 1"), assertInOutput("parallelism=1"), - assertPackageCount(35), + assertPackageCount(37), assertSuccessfulReturnCode, }, }, @@ -239,7 +238,7 @@ func TestPackagesCmdFlags(t *testing.T) { assertions: []traitAssertion{ assertNotInOutput("secret_password"), assertNotInOutput("secret_key_path"), - assertPackageCount(35), + assertPackageCount(37), assertSuccessfulReturnCode, }, }, @@ -251,11 +250,7 @@ func TestPackagesCmdFlags(t *testing.T) { for _, traitFn := range test.assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } @@ -337,11 +332,7 @@ func TestRegistryAuth(t *testing.T) { for _, traitAssertionFn := range test.assertions { traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } diff --git a/test/cli/power_user_cmd_test.go b/test/cli/power_user_cmd_test.go index ac48ff150..9d2f4aa62 100644 --- a/test/cli/power_user_cmd_test.go +++ b/test/cli/power_user_cmd_test.go @@ -1,7 +1,6 @@ package cli import ( - "strings" "testing" ) @@ -96,11 +95,7 @@ func TestPowerUserCmdFlags(t *testing.T) { for _, traitFn := range test.assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } diff --git a/test/cli/root_cmd_test.go b/test/cli/root_cmd_test.go index 468fc927c..2270ca539 100644 --- a/test/cli/root_cmd_test.go +++ b/test/cli/root_cmd_test.go @@ -3,7 +3,6 @@ package cli import ( "os" "path/filepath" - "strings" "testing" "github.com/sergi/go-diff/diffmatchpatch" @@ -109,11 +108,7 @@ func TestPersistentFlags(t *testing.T) { for _, traitFn := range test.assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } @@ -151,11 +146,7 @@ func TestLogFile(t *testing.T) { for _, traitFn := range test.assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } - if t.Failed() { - t.Log("STDOUT:\n", stdout) - t.Log("STDERR:\n", stderr) - t.Log("COMMAND:", strings.Join(cmd.Args, " ")) - } + logOutputOnFailure(t, cmd, stdout, stderr) }) } } diff --git a/test/cli/utils_test.go b/test/cli/utils_test.go index faf8533cf..e8a2218c5 100644 --- a/test/cli/utils_test.go +++ b/test/cli/utils_test.go @@ -3,6 +3,7 @@ package cli import ( "bufio" "bytes" + "flag" "fmt" "io" "math" @@ -21,6 +22,16 @@ import ( "github.com/anchore/stereoscope/pkg/imagetest" ) +var showOutput = flag.Bool("show-output", false, "show stdout and stderr for failing tests") + +func logOutputOnFailure(t testing.TB, cmd *exec.Cmd, stdout, stderr string) { + if t.Failed() && showOutput != nil && *showOutput { + t.Log("STDOUT:\n", stdout) + t.Log("STDERR:\n", stderr) + t.Log("COMMAND:", strings.Join(cmd.Args, " ")) + } +} + func runAndShow(t *testing.T, cmd *exec.Cmd) { t.Helper() diff --git a/test/integration/catalog_packages_cases_test.go b/test/integration/catalog_packages_cases_test.go index cfa02072f..b5bc26a63 100644 --- a/test/integration/catalog_packages_cases_test.go +++ b/test/integration/catalog_packages_cases_test.go @@ -11,6 +11,20 @@ type testCase struct { } var imageOnlyTestCases = []testCase{ + { + name: "find kernel packages", + pkgType: pkg.LinuxKernelPkg, + pkgInfo: map[string]string{ + "linux-kernel": "6.2.9-200.fc37.x86_64", + }, + }, + { + name: "find kernel module packages", + pkgType: pkg.LinuxKernelModulePkg, + pkgInfo: map[string]string{ + "fsa4480": "", + }, + }, { name: "find gemspec packages", pkgType: pkg.GemPkg, diff --git a/test/integration/catalog_packages_test.go b/test/integration/catalog_packages_test.go index 7ef25c783..74bb1324d 100644 --- a/test/integration/catalog_packages_test.go +++ b/test/integration/catalog_packages_test.go @@ -221,6 +221,8 @@ func TestPkgCoverageDirectory(t *testing.T) { observedPkgs.Remove(string(pkg.UnknownPkg)) definedPkgs.Remove(string(pkg.BinaryPkg)) definedPkgs.Remove(string(pkg.UnknownPkg)) + definedPkgs.Remove(string(pkg.LinuxKernelPkg)) + definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) // for directory scans we should not expect to see any of the following package types definedPkgs.Remove(string(pkg.KbPkg)) diff --git a/test/integration/test-fixtures/image-pkg-coverage/Dockerfile b/test/integration/test-fixtures/image-pkg-coverage/Dockerfile index 2f838567b..d5403f47f 100644 --- a/test/integration/test-fixtures/image-pkg-coverage/Dockerfile +++ b/test/integration/test-fixtures/image-pkg-coverage/Dockerfile @@ -1,5 +1,22 @@ +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 + COPY pkgs/ . # we duplicate to show a package count difference between all-layers and squashed scopes COPY lib lib -COPY etc/ . \ No newline at end of file +COPY etc/ .