mirror of
https://github.com/anchore/syft.git
synced 2026-04-04 05:40:37 +02:00
241 lines
7.1 KiB
Go
241 lines
7.1 KiB
Go
package php
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/anchore/packageurl-go"
|
|
"github.com/anchore/syft/internal"
|
|
"github.com/anchore/syft/internal/unknown"
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/cpe"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
|
)
|
|
|
|
type interpreterCataloger struct {
|
|
name string
|
|
extensionsGlob string
|
|
interpreterClassifiers []binutils.Classifier
|
|
}
|
|
|
|
// NewInterpreterCataloger returns a new cataloger for PHP interpreters (php and php-fpm) as well as any installed C extensions.
|
|
func NewInterpreterCataloger() pkg.Cataloger { //nolint:funlen
|
|
name := "php-interpreter-cataloger"
|
|
m := binutils.ContextualEvidenceMatchers{CatalogerName: name}
|
|
return interpreterCataloger{
|
|
name: name,
|
|
// example matches:
|
|
// - as found in php-fpm docker library images: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so
|
|
// - as found in alpine images: /usr/lib/php83/modules/bcmath.so
|
|
extensionsGlob: "**/php*/**/*.so",
|
|
interpreterClassifiers: []binutils.Classifier{
|
|
{
|
|
Class: "php-cli-binary",
|
|
FileGlob: "**/php*",
|
|
EvidenceMatcher: m.FileNameTemplateVersionMatcher(
|
|
`(.*/|^)php[0-9]*$`,
|
|
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
|
|
Package: "php-cli",
|
|
PURL: packageurl.PackageURL{
|
|
Type: packageurl.TypeGeneric,
|
|
Name: "php-cli",
|
|
// the version will be filled in dynamically
|
|
},
|
|
CPEs: []cpe.CPE{
|
|
{
|
|
Attributes: cpe.Attributes{
|
|
Part: "a",
|
|
Vendor: "php",
|
|
Product: "php",
|
|
},
|
|
Source: cpe.NVDDictionaryLookupSource,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Class: "php-fpm-binary",
|
|
FileGlob: "**/php-fpm*",
|
|
EvidenceMatcher: m.FileContentsVersionMatcher(
|
|
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
|
|
Package: "php-fpm",
|
|
PURL: packageurl.PackageURL{
|
|
Type: packageurl.TypeGeneric,
|
|
Name: "php-fpm",
|
|
// the version will be filled in dynamically
|
|
},
|
|
CPEs: []cpe.CPE{
|
|
{
|
|
Attributes: cpe.Attributes{
|
|
Part: "a",
|
|
Vendor: "php",
|
|
Product: "php",
|
|
},
|
|
Source: cpe.NVDDictionaryLookupSource,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Class: "php-apache-binary",
|
|
FileGlob: "**/apache*/**/libphp*.so",
|
|
EvidenceMatcher: m.FileContentsVersionMatcher(
|
|
`(?m)X-Powered-By: PHP\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)`),
|
|
Package: "libphp",
|
|
PURL: packageurl.PackageURL{
|
|
Type: packageurl.TypeGeneric,
|
|
Name: "php",
|
|
// the version will be filled in dynamically
|
|
},
|
|
CPEs: []cpe.CPE{
|
|
{
|
|
Attributes: cpe.Attributes{
|
|
Part: "a",
|
|
Vendor: "php",
|
|
Product: "php",
|
|
},
|
|
Source: cpe.NVDDictionaryLookupSource,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p interpreterCataloger) Name() string {
|
|
return p.name
|
|
}
|
|
|
|
func (p interpreterCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
|
interpreterPkgs, intErrs := p.catalogInterpreters(resolver)
|
|
extensionPkgs, extErrs := p.catalogExtensions(resolver)
|
|
|
|
// TODO: a future iteration of this cataloger could be to read all php.ini / php/conf.d/*.ini files and indicate which extensions are enabled
|
|
// and attempt to resolve the extension_dir. This can be tricky as it is a #define in the php source code and not always available
|
|
// in configuration. For the meantime we report all extensions present
|
|
|
|
// create a relationship for each interpreter package to the extensions
|
|
var relationships []artifact.Relationship
|
|
for _, interpreter := range interpreterPkgs {
|
|
for _, extension := range extensionPkgs {
|
|
relationships = append(relationships, artifact.Relationship{
|
|
From: extension,
|
|
To: interpreter,
|
|
Type: artifact.DependencyOfRelationship,
|
|
})
|
|
}
|
|
}
|
|
|
|
var allPkgs []pkg.Package
|
|
allPkgs = append(allPkgs, interpreterPkgs...)
|
|
allPkgs = append(allPkgs, extensionPkgs...)
|
|
|
|
return allPkgs, relationships, unknown.Join(intErrs, extErrs)
|
|
}
|
|
|
|
func (p interpreterCataloger) catalogInterpreters(resolver file.Resolver) ([]pkg.Package, error) {
|
|
var errs error
|
|
var packages []pkg.Package
|
|
for _, cls := range p.interpreterClassifiers {
|
|
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
|
if err != nil {
|
|
// convert any file.Resolver path errors to unknowns with locations
|
|
errs = unknown.Join(errs, unknown.ProcessPathErrors(err))
|
|
continue
|
|
}
|
|
for _, location := range locations {
|
|
pkgs, err := cls.EvidenceMatcher(cls, binutils.MatcherContext{Resolver: resolver, Location: location})
|
|
if err != nil {
|
|
errs = unknown.Append(errs, location, err)
|
|
continue
|
|
}
|
|
packages = append(packages, pkgs...)
|
|
}
|
|
}
|
|
return packages, errs
|
|
}
|
|
|
|
func (p interpreterCataloger) catalogExtensions(resolver file.Resolver) ([]pkg.Package, error) {
|
|
locations, err := resolver.FilesByGlob(p.extensionsGlob)
|
|
if err != nil {
|
|
// convert any file.Resolver path errors to unknowns with locations
|
|
return nil, unknown.ProcessPathErrors(err)
|
|
}
|
|
|
|
var packages []pkg.Package
|
|
var errs error
|
|
for _, location := range locations {
|
|
pkgs, err := p.catalogExtension(resolver, location)
|
|
if err != nil {
|
|
errs = unknown.Append(errs, location, err)
|
|
continue
|
|
}
|
|
packages = append(packages, pkgs...)
|
|
}
|
|
return packages, errs
|
|
}
|
|
|
|
func (p interpreterCataloger) catalogExtension(resolver file.Resolver, location file.Location) ([]pkg.Package, error) {
|
|
reader, err := resolver.FileContentsByLocation(location)
|
|
defer internal.CloseAndLogError(reader, location.RealPath)
|
|
if err != nil {
|
|
return nil, unknown.ProcessPathErrors(err)
|
|
}
|
|
|
|
name, cls := p.getClassifier(location.RealPath)
|
|
if name == "" || cls == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
pkgs, err := cls.EvidenceMatcher(*cls, binutils.MatcherContext{Resolver: resolver, Location: location})
|
|
if err != nil {
|
|
return nil, unknown.New(location, err)
|
|
}
|
|
|
|
return pkgs, err
|
|
}
|
|
|
|
func (p interpreterCataloger) getClassifier(realPath string) (string, *binutils.Classifier) {
|
|
if !strings.HasSuffix(realPath, ".so") {
|
|
return "", nil
|
|
}
|
|
|
|
base := path.Base(realPath)
|
|
name := strings.TrimSuffix(base, ".so")
|
|
|
|
var match string
|
|
switch name {
|
|
case "mysqli":
|
|
match = `(mysqlnd|mysqli)?\s*\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+API`
|
|
case "opcache":
|
|
match = `(?m)\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+`
|
|
case "zip":
|
|
match = `\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+Zip`
|
|
default:
|
|
match = fmt.Sprintf(`(?m)(\x00+%s)?\x00+(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00+API`, name)
|
|
}
|
|
|
|
return name, &binutils.Classifier{
|
|
Class: fmt.Sprintf("php-ext-%s-binary", name),
|
|
EvidenceMatcher: binutils.FileContentsVersionMatcher(p.name, match),
|
|
Package: name,
|
|
PURL: packageurl.PackageURL{
|
|
Type: packageurl.TypeGeneric,
|
|
Name: name,
|
|
// the version will be filled in dynamically
|
|
},
|
|
CPEs: []cpe.CPE{
|
|
{
|
|
Attributes: cpe.Attributes{
|
|
Part: "a",
|
|
Vendor: fmt.Sprintf("php-%s", name),
|
|
Product: fmt.Sprintf("php-%s", name),
|
|
},
|
|
Source: cpe.GeneratedSource,
|
|
},
|
|
},
|
|
}
|
|
}
|