mirror of
https://github.com/anchore/syft.git
synced 2026-11-04 08:26:38 +01:00
fix(binary): preserve semver build metadata in ffmpeg version (#5452)
Signed-off-by: Adam Shaver <ashaver@nvidia.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package binary
|
package binary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
@@ -20,6 +21,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/testutils"
|
"github.com/anchore/syft/internal/testutils"
|
||||||
"github.com/anchore/syft/syft/cpe"
|
"github.com/anchore/syft/syft/cpe"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/testutil"
|
"github.com/anchore/syft/syft/pkg/cataloger/binary/internal/manager/testutil"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
||||||
@@ -3192,6 +3194,134 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
|
|||||||
// Test_Cataloger_PackageTypes covers what assertPackagesAreEqual deliberately ignores: the package type and CPEs.
|
// Test_Cataloger_PackageTypes covers what assertPackagesAreEqual deliberately ignores: the package type and CPEs.
|
||||||
// The perl interpreter is reported as cpan so it lines up with the rest of the CPAN ecosystem, everything else
|
// The perl interpreter is reported as cpan so it lines up with the rest of the CPAN ecosystem, everything else
|
||||||
// keeps the default binary type.
|
// keeps the default binary type.
|
||||||
|
// Test_Cataloger_ffmpeg_versionSuffix verifies the ffmpeg classifiers no longer
|
||||||
|
// unconditionally truncate the embedded "FFmpeg version <ver>" string to X.Y[.Z].
|
||||||
|
// Semver build metadata (a backported-patch stamp such as
|
||||||
|
// "9.0.3+dev.9.0.2.p1.eb9cef3") must be preserved in the reported version, because
|
||||||
|
// it compares equal to its core version and so does not disturb vulnerability
|
||||||
|
// matching. A "-" pre-release suffix (a distro revision such as "6.1.1-3ubuntu5")
|
||||||
|
// must stay truncated: it sorts below the release in semver and would corrupt
|
||||||
|
// version-range matching. Suffix-free binaries match exactly as before, and
|
||||||
|
// trailing non-version text (or NUL padding) is never absorbed into the version.
|
||||||
|
func Test_Cataloger_ffmpeg_versionSuffix(t *testing.T) {
|
||||||
|
// collect the ffmpeg classifiers under test, keyed by their FileGlob
|
||||||
|
classifiers := map[string]binutils.Classifier{}
|
||||||
|
for _, c := range DefaultClassifiers() {
|
||||||
|
if c.Package == "ffmpeg" {
|
||||||
|
classifiers[c.FileGlob] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotEmpty(t, classifiers, "expected to find ffmpeg classifiers")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fileGlob string // which classifier to run
|
||||||
|
data string // simulated binary contents
|
||||||
|
want string // expected reported version ("" = no match expected)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "library bare version unchanged",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 7.1.1",
|
||||||
|
want: "7.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library two-component version unchanged",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 6.0",
|
||||||
|
want: "6.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library build metadata with patch provenance preserved",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 9.0.3+dev.9.0.2.p1.eb9cef3",
|
||||||
|
want: "9.0.3+dev.9.0.2.p1.eb9cef3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library build metadata short sha preserved",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 9.0.2+eb9cef3",
|
||||||
|
want: "9.0.2+eb9cef3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// a "-" pre-release suffix is intentionally NOT captured: it sorts below
|
||||||
|
// the release in semver and would corrupt version-range vulnerability
|
||||||
|
// matching, so distro revisions stay truncated exactly as before
|
||||||
|
name: "library distro hyphen revision truncated",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 6.1.1-3ubuntu5",
|
||||||
|
want: "6.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library distro dotted hyphen revision truncated",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 4.4.2-0ubuntu0.22.04.1",
|
||||||
|
want: "4.4.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library trailing text not captured",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 6.1.1 Copyright (c) 2000-2023 the FFmpeg developers",
|
||||||
|
want: "6.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library NUL-padded version not over-captured",
|
||||||
|
fileGlob: "**/libav*",
|
||||||
|
data: "FFmpeg version 9.0.3\x00libavformat.so",
|
||||||
|
want: "9.0.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "libswresample build metadata preserved",
|
||||||
|
fileGlob: "**/libswresample*",
|
||||||
|
data: "FFmpeg version 9.0.3+dev.9.0.2.p1.eb9cef3",
|
||||||
|
want: "9.0.3+dev.9.0.2.p1.eb9cef3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// the compiled-in printf format string in the ffmpeg binary is the
|
||||||
|
// literal "%s version <ver>" (the program name is substituted at runtime)
|
||||||
|
name: "binary build metadata preserved",
|
||||||
|
fileGlob: "**/ffmpeg",
|
||||||
|
data: "%s version 9.0.3+dev.9.0.2.p1.eb9cef3",
|
||||||
|
want: "9.0.3+dev.9.0.2.p1.eb9cef3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary bare version unchanged",
|
||||||
|
fileGlob: "**/ffmpeg",
|
||||||
|
data: "%s version 7.1.1",
|
||||||
|
want: "7.1.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cls, ok := classifiers[tt.fileGlob]
|
||||||
|
require.Truef(t, ok, "no ffmpeg classifier for glob %q", tt.fileGlob)
|
||||||
|
|
||||||
|
ctx := binutils.MatcherContext{
|
||||||
|
GetReader: func(binutils.MatcherContext) (unionreader.UnionReader, error) {
|
||||||
|
return unionreader.GetUnionReader(io.NopCloser(bytes.NewBufferString(tt.data)))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs, err := cls.EvidenceMatcher(cls, ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if tt.want == "" {
|
||||||
|
require.Empty(t, pkgs, "expected no package match")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Len(t, pkgs, 1, "expected exactly one package")
|
||||||
|
assert.Equal(t, tt.want, pkgs[0].Version)
|
||||||
|
// the fuller version must also flow into the PURL so the SBOM is not lossy;
|
||||||
|
// compare the decoded PURL version, since packageurl-go percent-encodes
|
||||||
|
// reserved characters (e.g. '+' -> %2B) in the serialized form
|
||||||
|
parsed, err := packageurl.FromString(pkgs[0].PURL)
|
||||||
|
require.NoError(t, err, "PURL should parse")
|
||||||
|
assert.Equal(t, tt.want, parsed.Version)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Cataloger_PackageTypes(t *testing.T) {
|
func Test_Cataloger_PackageTypes(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
logicalFixture string
|
logicalFixture string
|
||||||
|
|||||||
@@ -972,7 +972,10 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||||
// Pattern found in the binary: "%s version 7.1.1" or "%s version 6.0"
|
// Pattern found in the binary: "%s version 7.1.1" or "%s version 6.0"
|
||||||
// When executed outputs: "ffmpeg version 7.1.1" or "ffmpeg version 6.0"
|
// When executed outputs: "ffmpeg version 7.1.1" or "ffmpeg version 6.0"
|
||||||
`(?m)%s version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?)`,
|
// The optional "+" suffix preserves semver build metadata (e.g. a
|
||||||
|
// backported-patch stamp like "9.0.3+dev.9.0.2.p1.eb9cef3"); see the
|
||||||
|
// ffmpeg-library classifier for why "-" pre-release suffixes stay truncated.
|
||||||
|
`(?m)%s version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?)`,
|
||||||
),
|
),
|
||||||
Package: "ffmpeg",
|
Package: "ffmpeg",
|
||||||
PURL: mustPURL("pkg:generic/ffmpeg@version"),
|
PURL: mustPURL("pkg:generic/ffmpeg@version"),
|
||||||
@@ -982,8 +985,15 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
Class: "ffmpeg-library",
|
Class: "ffmpeg-library",
|
||||||
FileGlob: "**/libav*",
|
FileGlob: "**/libav*",
|
||||||
EvidenceMatcher: binutils.MatchAny(
|
EvidenceMatcher: binutils.MatchAny(
|
||||||
// Primary pattern: FFmpeg version found in most libraries
|
// Primary pattern: FFmpeg version found in most libraries.
|
||||||
m.FileContentsVersionMatcher(`(?m)FFmpeg version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?)`),
|
// The optional "+" suffix preserves semver build metadata (e.g. a
|
||||||
|
// backported-patch stamp like "9.0.3+dev.9.0.2.p1.eb9cef3") instead of
|
||||||
|
// truncating to X.Y[.Z]. Build metadata compares equal to its core
|
||||||
|
// version, so this does not change vulnerability matching. A "-"
|
||||||
|
// pre-release suffix (e.g. a distro revision "6.1.1-3ubuntu5") is
|
||||||
|
// deliberately NOT captured: it sorts below the release in semver and
|
||||||
|
// would corrupt version-range matching, so it stays truncated as before.
|
||||||
|
m.FileContentsVersionMatcher(`(?m)FFmpeg version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?)`),
|
||||||
// Fallback: library-specific version patterns for libavcodec and libavformat
|
// Fallback: library-specific version patterns for libavcodec and libavformat
|
||||||
m.FileContentsVersionMatcher(`(?m)Lavc(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
m.FileContentsVersionMatcher(`(?m)Lavc(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||||
m.FileContentsVersionMatcher(`(?m)Lavf(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
m.FileContentsVersionMatcher(`(?m)Lavf(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||||
@@ -996,8 +1006,9 @@ func DefaultClassifiers() []binutils.Classifier {
|
|||||||
Class: "ffmpeg-library",
|
Class: "ffmpeg-library",
|
||||||
FileGlob: "**/libswresample*",
|
FileGlob: "**/libswresample*",
|
||||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||||
// FFmpeg version pattern for libswresample
|
// FFmpeg version pattern for libswresample. Same optional "+" build
|
||||||
`(?m)FFmpeg version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?)`),
|
// metadata handling as the ffmpeg-library classifier above.
|
||||||
|
`(?m)FFmpeg version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?(?:\+[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?)`),
|
||||||
Package: "ffmpeg",
|
Package: "ffmpeg",
|
||||||
PURL: mustPURL("pkg:generic/ffmpeg@version"),
|
PURL: mustPURL("pkg:generic/ffmpeg@version"),
|
||||||
CPEs: singleCPE("cpe:2.3:a:ffmpeg:ffmpeg:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
CPEs: singleCPE("cpe:2.3:a:ffmpeg:ffmpeg:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||||
|
|||||||
Reference in New Issue
Block a user