From 5c6cf08a8c824f63e8a5e507228b1db80ecbcfce Mon Sep 17 00:00:00 2001 From: CAOShurong <3502119616@qq.com> Date: Thu, 20 Aug 2026 02:15:15 +0800 Subject: [PATCH] fix(binary): detect multi-arch ingress-nginx (#5179) * fix(binary): detect multi-arch ingress-nginx Add v1.9.6 ARM64, ARMv7, and s390x fixtures from the published controller image and match their version marker against the nearby Go build version. Assisted-by: OpenAI Codex Signed-off-by: Shurong Cao <170531907+CAOShurong@users.noreply.github.com> * fix(binary): match ingress-nginx release marker on all arches The release is injected with `-ldflags -X`, so it lands in its own aligned, NUL-padded data symbol. Only that padding is portable -- the surrounding bytes are an arch-specific float constant pool, which is why the existing matchers all anchor on incidental junk like `$a` and `S=v` followed by two NULs finds exactly one hit in every v1.9.6 binary (amd64, arm, arm64, s390x) and in all nine amd64 releases already under test. Two NULs matter -- with one, s390x matches a vendored `v1.19.0` earlier in the file and reports the wrong version. This also drops the ~8KB wildcard window the marker previously needed, so the new fixtures are ordinary 369 byte snippets instead of 8.4KB ones. Two things in the fixture tooling had to move for that to work: - `config.yaml` pinned the multi-arch *index* digest for all four platforms, but extraction runs `docker create ` with no `--platform`, so every entry resolved to whichever image was pulled last. `make download` failed outright on `linux/arm/v7`, since the registry labels that platform `armv7` and docker normalizes it to `arm`. Now pinned per-platform, matching the redis entry. - `write-snippet` only recognized EM_X86_64 and EM_AARCH64, so anything else landed in a `linux-unknown-` directory and had to be placed by hand. Verified against the real binaries with `-must-use-original-binaries`. Signed-off-by: Alex Goodman * fix(binary): put non-amd64 snippets where the tests look for them `write-snippet` used to fall back to `unknown-` for any ELF machine it didn't recognize, so three snippets were sitting in directories nothing reads: helm/3.12.0/linux-unknown-454d5f53333930 (hex of "EM_S390") helm/4.1.4/linux-unknown-454d5f53333930 redis-server/7.2.5/linux-unknown-454d5f333836 (hex of "EM_386") The test table asks for `linux-s390x` and `linux-386`, so those three cases were quietly falling through to downloading the full binaries instead. Renamed to match, and taught the tool about EM_386 and EM_RISCV so it stops producing dead directories (there is an existing `linux-riscv64` snippet it also couldn't have written). Signed-off-by: Alex Goodman --------- Signed-off-by: Shurong Cao <170531907+CAOShurong@users.noreply.github.com> Signed-off-by: Alex Goodman Co-authored-by: Shurong Cao <170531907+CAOShurong@users.noreply.github.com> Co-authored-by: Alex Goodman --- .../binary/classifier_cataloger_test.go | 33 ++++++++++++++++++ syft/pkg/cataloger/binary/classifiers.go | 5 +++ .../internal/cli/commands/write_snippet.go | 8 +++++ .../helm | Bin .../helm | Bin .../1.9.6/linux-arm/nginx-ingress-controller | Bin 0 -> 369 bytes .../linux-arm64/nginx-ingress-controller | Bin 0 -> 369 bytes .../linux-s390x/nginx-ingress-controller | Bin 0 -> 369 bytes .../redis-server | Bin .../pkg/cataloger/binary/testdata/config.yaml | 10 +++++- 10 files changed, 55 insertions(+), 1 deletion(-) rename syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/3.12.0/{linux-unknown-454d5f53333930 => linux-s390x}/helm (100%) rename syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/4.1.4/{linux-unknown-454d5f53333930 => linux-s390x}/helm (100%) create mode 100644 syft/pkg/cataloger/binary/testdata/classifiers/snippets/nginx-ingress-controller/1.9.6/linux-arm/nginx-ingress-controller create mode 100644 syft/pkg/cataloger/binary/testdata/classifiers/snippets/nginx-ingress-controller/1.9.6/linux-arm64/nginx-ingress-controller create mode 100644 syft/pkg/cataloger/binary/testdata/classifiers/snippets/nginx-ingress-controller/1.9.6/linux-s390x/nginx-ingress-controller rename syft/pkg/cataloger/binary/testdata/classifiers/snippets/redis-server/7.2.5/{linux-unknown-454d5f333836 => linux-386}/redis-server (100%) diff --git a/syft/pkg/cataloger/binary/classifier_cataloger_test.go b/syft/pkg/cataloger/binary/classifier_cataloger_test.go index 4cbd2f6f3..d8d3567ff 100644 --- a/syft/pkg/cataloger/binary/classifier_cataloger_test.go +++ b/syft/pkg/cataloger/binary/classifier_cataloger_test.go @@ -2613,6 +2613,39 @@ func Test_Cataloger_PositiveCases(t *testing.T) { Metadata: metadata("ingress-nginx-binary"), }, }, + { + logicalFixture: "nginx-ingress-controller/1.9.6/linux-arm64", + expected: pkg.Package{ + Name: "nginx-ingress-controller", + Version: "1.9.6", + Type: "binary", + PURL: "pkg:generic/nginx-ingress-controller@1.9.6", + Locations: locations("nginx-ingress-controller"), + Metadata: metadata("ingress-nginx-binary"), + }, + }, + { + logicalFixture: "nginx-ingress-controller/1.9.6/linux-arm", + expected: pkg.Package{ + Name: "nginx-ingress-controller", + Version: "1.9.6", + Type: "binary", + PURL: "pkg:generic/nginx-ingress-controller@1.9.6", + Locations: locations("nginx-ingress-controller"), + Metadata: metadata("ingress-nginx-binary"), + }, + }, + { + logicalFixture: "nginx-ingress-controller/1.9.6/linux-s390x", + expected: pkg.Package{ + Name: "nginx-ingress-controller", + Version: "1.9.6", + Type: "binary", + PURL: "pkg:generic/nginx-ingress-controller@1.9.6", + Locations: locations("nginx-ingress-controller"), + Metadata: metadata("ingress-nginx-binary"), + }, + }, { logicalFixture: "nginx-ingress-controller/1.7.1/linux-amd64", expected: pkg.Package{ diff --git a/syft/pkg/cataloger/binary/classifiers.go b/syft/pkg/cataloger/binary/classifiers.go index afd4c7536..78f6e18ed 100644 --- a/syft/pkg/cataloger/binary/classifiers.go +++ b/syft/pkg/cataloger/binary/classifiers.go @@ -1153,6 +1153,11 @@ func DefaultClassifiers() []binutils.Classifier { Class: "ingress-nginx-binary", FileGlob: "**/nginx-ingress-controller", EvidenceMatcher: binutils.MatchAny( + // the release is injected with -ldflags -X, which lands it in its own NUL-padded data symbol. + // the surrounding bytes are an arch-specific float constant pool, so only the padding is portable. + // e.g. v1.9.6[NUL][NUL] on each of linux/amd64, linux/arm, linux/arm64, and linux/s390x + // note: one trailing NUL is not enough -- on s390x that matches a vendored "v1.19.0" earlier in the file + m.FileContentsVersionMatcher(`v(?P[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9]+)?)\x00\x00`), // [NUL][NUL]v1.15.1[NUL][NUL]@e[ETX][NUL][NUL][NUL][NUL]go1.26.1[NUL][NUL][NUL] // �v1.15.1[NUL][NUL]�z[ETX][NUL][NUL][NUL][NUL]go1.24.4[NUL][NUL][NUL] m.FileContentsVersionMatcher(`v(?P[0-9]+\.[0-9]+\.[0-9]+)\x00+.{0,50}go[0-9]+\.[0-9]+(\-(alpha|beta)\.[0-9])?\.[0-9]+\x00+`), diff --git a/syft/pkg/cataloger/binary/internal/manager/internal/cli/commands/write_snippet.go b/syft/pkg/cataloger/binary/internal/manager/internal/cli/commands/write_snippet.go index 46f42b383..5d5f31725 100644 --- a/syft/pkg/cataloger/binary/internal/manager/internal/cli/commands/write_snippet.go +++ b/syft/pkg/cataloger/binary/internal/manager/internal/cli/commands/write_snippet.go @@ -215,6 +215,14 @@ func getPlatformElf(f *os.File) string { arch = amd64 case elf.EM_AARCH64: arch = arm64 + case elf.EM_ARM: + arch = "arm" + case elf.EM_S390: + arch = "s390x" + case elf.EM_386: + arch = "386" + case elf.EM_RISCV: + arch = "riscv64" // TODO... default: arch = fmt.Sprintf("unknown-%x", elfFile.Machine) diff --git a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/3.12.0/linux-unknown-454d5f53333930/helm b/syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/3.12.0/linux-s390x/helm similarity index 100% rename from syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/3.12.0/linux-unknown-454d5f53333930/helm rename to syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/3.12.0/linux-s390x/helm diff --git a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/4.1.4/linux-unknown-454d5f53333930/helm b/syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/4.1.4/linux-s390x/helm similarity index 100% rename from syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/4.1.4/linux-unknown-454d5f53333930/helm rename to syft/pkg/cataloger/binary/testdata/classifiers/snippets/helm/4.1.4/linux-s390x/helm diff --git a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/nginx-ingress-controller/1.9.6/linux-arm/nginx-ingress-controller b/syft/pkg/cataloger/binary/testdata/classifiers/snippets/nginx-ingress-controller/1.9.6/linux-arm/nginx-ingress-controller new file mode 100644 index 0000000000000000000000000000000000000000..748c0bd013e73b24ab15e43f1456b97e1feba2d2 GIT binary patch literal 369 zcmYk1Jx;_h5QQCBK!k>x%F+r^?aCg_qOIf7GUFd$N2 zH|}p47?3&Vo{-FlXcaSzaLJgGlw}$U4>|WBnJ`683d%F788u824>9SY~gp!#4uji+qdc8b7+9n4P?7t7m eEb7N}Ebepm2H8%4p+Mv~d&Zeh(Hw~N1p7QTd5wpxg--~(9L z=o|O~g1vBN_52v-o0)I;n2Gk#7dTF;crvWwWbWH`2uL5 zmJJ32>`pt6muKRx!6ALs9OEQq2%*c1m6TE}R=fRmgl@Nv=*Wy@k`fjPJ*S=DdgmE^ r-oL!P_Ol*0*FB!b__ssoV99^2eXIu{qO96vkPvJ!VmQW5G!*xwnD~h1@9t(J9Y;z+fP7=eWigvO=QJ z)<%j|4f`+^Rp>X3=^aA5P)eaCh7t;|yyvP=PDV6_Tdp#%OOG)c?!d}uOF&8Kcsyps zeF8R|$x_R(|Ej%Zy+qfKg!Dby^yBy{9h^N8awDdGY6)3?lWkoLcr@VaHyIH!``QeA e{j>9(41D(f@cHt5emeVo``9XnJAI$`@BagD&t&BQ literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/redis-server/7.2.5/linux-unknown-454d5f333836/redis-server b/syft/pkg/cataloger/binary/testdata/classifiers/snippets/redis-server/7.2.5/linux-386/redis-server similarity index 100% rename from syft/pkg/cataloger/binary/testdata/classifiers/snippets/redis-server/7.2.5/linux-unknown-454d5f333836/redis-server rename to syft/pkg/cataloger/binary/testdata/classifiers/snippets/redis-server/7.2.5/linux-386/redis-server diff --git a/syft/pkg/cataloger/binary/testdata/config.yaml b/syft/pkg/cataloger/binary/testdata/config.yaml index 9e6197353..37ed85815 100644 --- a/syft/pkg/cataloger/binary/testdata/config.yaml +++ b/syft/pkg/cataloger/binary/testdata/config.yaml @@ -1570,8 +1570,16 @@ from-images: - version: 1.9.6 images: - - ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:1405cc613bd95b2c6edd8b2a152510ae91c7e62aea4698500d23b2145960ab9c + # note: per-platform manifest digests (not the index digest) -- the extraction step creates a + # container from the ref alone, so each platform needs its own uniquely addressable ref + - ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:0939639a1f338a9eaaa490fd38b4a7881e47a7fd1a473baf8749ce15952b55b8 platform: linux/amd64 + - ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:5fb823cc617cbfbee4b250ad5e059c23078ea63e9a7e6acb6d4d30e4456eb0be + platform: linux/arm64 + - ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:dcea8d021e15cf27f59249ee0cfd211559ef9eb60b10651c3af299499848cf8b + platform: linux/arm + - ref: registry.k8s.io/ingress-nginx/controller:v1.9.6@sha256:fc53a604eb2d17fa0cf7a3f266d052c2f32fb97209869057d6826d1815cf622d + platform: linux/s390x paths: - /nginx-ingress-controller