mirror of
https://github.com/anchore/syft.git
synced 2026-10-27 04:22:31 +01:00
Compare commits
+4
-17
@@ -34,7 +34,7 @@ tools:
|
||||
# used for showing the changelog at release
|
||||
- name: glow
|
||||
version:
|
||||
want: v2.1.0
|
||||
want: v2.1.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: charmbracelet/glow
|
||||
@@ -58,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v2.9.0
|
||||
want: v2.10.2
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -87,23 +87,10 @@ tools:
|
||||
with:
|
||||
repo: wagoodman/go-bouncer
|
||||
|
||||
# used for showing benchmark testing
|
||||
- name: benchstat
|
||||
version:
|
||||
want: latest
|
||||
method: go-proxy
|
||||
with:
|
||||
module: golang.org/x/perf
|
||||
allow-unresolved-version: true
|
||||
method: go-install
|
||||
with:
|
||||
entrypoint: cmd/benchstat
|
||||
module: golang.org/x/perf
|
||||
|
||||
# used for running all local and CI tasks
|
||||
- name: task
|
||||
version:
|
||||
want: v3.43.3
|
||||
want: v3.44.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: go-task/task
|
||||
@@ -111,7 +98,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.72.0
|
||||
want: v2.74.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
name: "Benchmark testing"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
Benchmark-Test:
|
||||
name: "Benchmark tests"
|
||||
runs-on: ubuntu-24.04
|
||||
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
|
||||
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
|
||||
|
||||
- name: Run benchmark tests
|
||||
id: benchmark
|
||||
run: |
|
||||
REF_NAME=${GITHUB_REF##*/} make benchmark
|
||||
OUTPUT=$(make show-benchstat)
|
||||
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
|
||||
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
- name: Update PR benchmark results comment
|
||||
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db #v2.9.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
header: benchmark
|
||||
message: |
|
||||
### Benchmark Test Results
|
||||
|
||||
<details>
|
||||
<summary>Benchmark results from the latest changes vs base branch</summary>
|
||||
|
||||
```
|
||||
${{ steps.benchmark.outputs.result }}
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
|
||||
uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
|
||||
uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b #v3.28.17
|
||||
uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
|
||||
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
# for updating brew formula in anchore/homebrew-syft
|
||||
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
|
||||
|
||||
- uses: anchore/sbom-action@9f7302141466aa6482940f15371237e9d9f4c34a #v0.19.0
|
||||
- uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 #v0.20.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
file: go.mod
|
||||
|
||||
@@ -76,6 +76,9 @@ jobs:
|
||||
- name: Build snapshot artifacts
|
||||
run: make snapshot
|
||||
|
||||
- name: Smoke test snapshot build
|
||||
run: make snapshot-smoke-test
|
||||
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
|
||||
+187
-119
@@ -1,11 +1,11 @@
|
||||
version: 2
|
||||
project_name: syft
|
||||
|
||||
release:
|
||||
prerelease: auto
|
||||
draft: false
|
||||
|
||||
env:
|
||||
# required to support multi architecture docker builds
|
||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
- CGO_ENABLED=0
|
||||
|
||||
@@ -13,14 +13,8 @@ builds:
|
||||
- id: linux-build
|
||||
dir: ./cmd/syft
|
||||
binary: syft
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- ppc64le
|
||||
- s390x
|
||||
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
|
||||
goos: [linux]
|
||||
goarch: [amd64, arm64, ppc64le, s390x]
|
||||
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
|
||||
ldflags: &build-ldflags |
|
||||
-w
|
||||
@@ -34,11 +28,8 @@ builds:
|
||||
- id: darwin-build
|
||||
dir: ./cmd/syft
|
||||
binary: syft
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
goos: [darwin]
|
||||
goarch: [amd64, arm64]
|
||||
mod_timestamp: *build-timestamp
|
||||
ldflags: *build-ldflags
|
||||
hooks:
|
||||
@@ -50,115 +41,42 @@ builds:
|
||||
- id: windows-build
|
||||
dir: ./cmd/syft
|
||||
binary: syft
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
goos: [windows]
|
||||
goarch: [amd64]
|
||||
mod_timestamp: *build-timestamp
|
||||
ldflags: *build-ldflags
|
||||
|
||||
archives:
|
||||
- id: linux-archives
|
||||
builds:
|
||||
- linux-build
|
||||
|
||||
# note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted
|
||||
builds: [linux-build]
|
||||
- id: darwin-archives
|
||||
builds:
|
||||
- darwin-build
|
||||
|
||||
builds: [darwin-build]
|
||||
- id: windows-archives
|
||||
format: zip
|
||||
builds:
|
||||
- windows-build
|
||||
builds: [windows-build]
|
||||
|
||||
nfpms:
|
||||
- license: "Apache 2.0"
|
||||
maintainer: "Anchore, Inc"
|
||||
homepage: &website "https://github.com/anchore/syft"
|
||||
description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
|
||||
formats:
|
||||
- rpm
|
||||
- deb
|
||||
formats: [rpm, deb]
|
||||
|
||||
brews:
|
||||
- repository:
|
||||
owner: anchore
|
||||
name: homebrew-syft
|
||||
token: "{{.Env.GITHUB_BREW_TOKEN}}"
|
||||
ids:
|
||||
- darwin-archives
|
||||
- linux-archives
|
||||
ids: [darwin-archives, linux-archives]
|
||||
homepage: *website
|
||||
description: *description
|
||||
license: "Apache License 2.0"
|
||||
|
||||
dockers:
|
||||
# production images...
|
||||
- image_templates:
|
||||
- anchore/syft:debug
|
||||
- anchore/syft:{{.Tag}}-debug
|
||||
- ghcr.io/anchore/syft:debug
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug
|
||||
goarch: amd64
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:debug-s390x
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
- ghcr.io/anchore/syft:debug-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:latest
|
||||
- anchore/syft:{{.Tag}}
|
||||
- ghcr.io/anchore/syft:latest
|
||||
- ghcr.io/anchore/syft:{{.Tag}}
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
goarch: amd64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
@@ -208,51 +126,201 @@ dockers:
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
# nonroot images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
goarch: amd64
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
# debug images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
goarch: amd64
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
docker_manifests:
|
||||
- name_template: anchore/syft:latest
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}
|
||||
- anchore/syft:{{.Tag}}-arm64v8
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: anchore/syft:debug
|
||||
- anchore/syft:{{.Tag}}-debug
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
- anchore/syft:{{.Tag}}-arm64v8
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:latest
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
- anchore/syft:{{.Tag}}-arm64v8
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
# nonroot images...
|
||||
- name_template: anchore/syft:nonroot
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:nonroot
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}-nonroot
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
# debug images...
|
||||
- name_template: anchore/syft:debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}
|
||||
- name_template: anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
# this is relative to the snapshot/dist directory, not the root of the repo
|
||||
cmd: ../.tool/syft
|
||||
documents:
|
||||
- "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM gcr.io/distroless/static-debian12:debug@sha256:5c474275684bbf271ec40502ab50158b2f9826de5877d8feec27e22e8d6ee3d2 AS build
|
||||
FROM gcr.io/distroless/static-debian12:latest AS build
|
||||
|
||||
FROM scratch
|
||||
# needed for version check HTTPS request
|
||||
|
||||
+3
-1
@@ -1,10 +1,12 @@
|
||||
FROM gcr.io/distroless/static-debian12:debug@sha256:5c474275684bbf271ec40502ab50158b2f9826de5877d8feec27e22e8d6ee3d2
|
||||
FROM gcr.io/distroless/static-debian12:debug-nonroot
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
ARG VCS_URL
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE
|
||||
LABEL org.opencontainers.image.title="syft"
|
||||
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
|
||||
LABEL org.opencontainers.image.source=$VCS_URL
|
||||
LABEL org.opencontainers.image.revision=$VCS_REF
|
||||
LABEL org.opencontainers.image.vendor="Anchore, Inc."
|
||||
LABEL org.opencontainers.image.version=$BUILD_VERSION
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
|
||||
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
|
||||
LABEL io.artifacthub.package.license="Apache-2.0"
|
||||
|
||||
ENTRYPOINT ["/syft"]
|
||||
+18
-29
@@ -27,10 +27,12 @@ vars:
|
||||
sh: uname -s | tr '[:upper:]' '[:lower:]'
|
||||
ARCH:
|
||||
sh: |
|
||||
[ "$(uname -m)" = "x86_64" ] && echo "amd64_v1" || { [ "$(uname -m)" = "aarch64" ] && echo "arm64_v8.0" || echo $(uname -m); }
|
||||
[ "$(uname -m)" = "x86_64" ] && echo "amd64_v1" || { [ "$(uname -m)" = "aarch64" ] && echo "arm64_v8.0" || [ "$(uname -m)" = "arm64" ] && echo "arm64_v8.0" || echo $(uname -m); }
|
||||
PROJECT_ROOT:
|
||||
sh: echo $PWD
|
||||
|
||||
# note: the snapshot dir must be a relative path starting with ./
|
||||
# e.g. when installing snapshot debs from a local path, ./ forces the deb to be installed in the current working directory instead of referencing a package name
|
||||
SNAPSHOT_DIR: ./snapshot
|
||||
SNAPSHOT_BIN: "{{ .PROJECT_ROOT }}/{{ .SNAPSHOT_DIR }}/{{ .OS }}-build_{{ .OS }}_{{ .ARCH }}/{{ .PROJECT }}"
|
||||
SNAPSHOT_CMD: "{{ .TOOL_DIR }}/goreleaser release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign"
|
||||
@@ -75,7 +77,6 @@ tasks:
|
||||
- task: unit
|
||||
- task: integration
|
||||
- task: validate-cyclonedx-schema
|
||||
- task: benchmark
|
||||
- task: test-utils
|
||||
- task: cli
|
||||
- task: check-docker-cache
|
||||
@@ -231,10 +232,7 @@ tasks:
|
||||
|
||||
cli:
|
||||
desc: Run CLI tests
|
||||
# note: we don't want to regenerate the snapshot unless we have to. In CI it's probable
|
||||
# that the cache being restored with the correct binary will be rebuilt since the timestamps
|
||||
# and local checksums will not line up.
|
||||
deps: [tools, snapshot]
|
||||
deps: [tools]
|
||||
cmds:
|
||||
- cmd: "echo 'testing binary: {{ .SNAPSHOT_BIN }}'"
|
||||
silent: true
|
||||
@@ -251,27 +249,6 @@ tasks:
|
||||
- cmd: .github/scripts/labeler_test.py
|
||||
|
||||
|
||||
## Benchmark test targets #################################
|
||||
|
||||
benchmark:
|
||||
deps: [tmpdir]
|
||||
generates:
|
||||
- "{{ .TMP_DIR }}/benchmark-main.txt"
|
||||
cmds:
|
||||
- "go test -p 1 -run=^Benchmark -bench=. -count=7 -benchmem ./... | tee {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt"
|
||||
- |
|
||||
bash -c "(test -s {{ .TMP_DIR }}/benchmark-main.txt && \
|
||||
{{ .TOOL_DIR }}/benchstat {{ .TMP_DIR }}/benchmark-main.txt {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt || \
|
||||
{{ .TOOL_DIR }}/benchstat {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt) \
|
||||
| tee {{ .TMP_DIR }}/benchstat.txt"
|
||||
|
||||
show-benchstat:
|
||||
deps: [benchmark, tmpdir]
|
||||
cmds:
|
||||
- cmd: "cat {{ .TMP_DIR }}/benchstat.txt"
|
||||
silent: true
|
||||
|
||||
|
||||
## Test-fixture-related targets #################################
|
||||
|
||||
fingerprints:
|
||||
@@ -453,8 +430,8 @@ tasks:
|
||||
find . | grep cache | grep tar | xargs du
|
||||
echo "total $total_size KB"
|
||||
|
||||
if [ "$total_size" -gt 1048576 ]; then
|
||||
echo 'docker cache is larger than 1GB'
|
||||
if [ "$total_size" -gt 5242880 ]; then
|
||||
echo 'docker cache is larger than 5GB'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -574,6 +551,18 @@ tasks:
|
||||
|
||||
- "{{ .SNAPSHOT_CMD }}"
|
||||
|
||||
snapshot-smoke-test:
|
||||
desc: Run a smoke test on the snapshot builds + docker images
|
||||
cmds:
|
||||
- cmd: "echo 'testing snapshot binary: {{ .SNAPSHOT_BIN }}'"
|
||||
silent: true
|
||||
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
|
||||
silent: true
|
||||
- "{{ .SNAPSHOT_BIN }} version"
|
||||
- "{{ .SNAPSHOT_BIN }} scan alpine:latest"
|
||||
- docker run --rm anchore/syft:latest version
|
||||
- docker run --rm anchore/syft:latest scan alpine:latest
|
||||
|
||||
changelog:
|
||||
desc: Generate a changelog
|
||||
deps: [tools]
|
||||
|
||||
+6
-1
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/cmd/syft/internal"
|
||||
"github.com/anchore/syft/cmd/syft/internal/commands"
|
||||
constants "github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
// Application constructs the `syft packages` command and aliases the root command to `syft packages`.
|
||||
@@ -47,7 +48,7 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
|
||||
commands.Cataloger(app),
|
||||
commands.Attest(app),
|
||||
commands.Convert(app),
|
||||
clio.VersionCommand(id),
|
||||
clio.VersionCommand(id, schemaVersion),
|
||||
clio.ConfigCommand(app, nil),
|
||||
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
|
||||
)
|
||||
@@ -59,3 +60,7 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
|
||||
|
||||
return app, rootCmd
|
||||
}
|
||||
|
||||
func schemaVersion() (string, any) {
|
||||
return "SchemaVersion", constants.JSONSchemaVersion
|
||||
}
|
||||
|
||||
@@ -4,18 +4,13 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
type licenseConfig struct {
|
||||
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
|
||||
// Deprecated: please use include-license-content instead
|
||||
IncludeUnknownLicenseContent *bool `yaml:"-" json:"-" mapstructure:"include-unknown-license-content"`
|
||||
|
||||
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
|
||||
// Deprecated: please use coverage instead
|
||||
LicenseCoverage *float64 `yaml:"license-coverage" json:"license-coverage" mapstructure:"license-coverage"`
|
||||
|
||||
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
|
||||
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
|
||||
AvailableLicenseContent []cataloging.LicenseContent `yaml:"-" json:"-" mapstructure:"-"`
|
||||
}
|
||||
|
||||
@@ -25,43 +20,16 @@ var _ interface {
|
||||
|
||||
func (o *licenseConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.Content, fmt.Sprintf("include the content of licenses in the SBOM for a given syft scan; valid values are: %s", o.AvailableLicenseContent))
|
||||
descriptions.Add(&o.IncludeUnknownLicenseContent, `deprecated: please use 'license-content' instead`)
|
||||
|
||||
descriptions.Add(&o.Coverage, `adjust the percent as a fraction of the total text, in normalized words, that
|
||||
matches any valid license for the given inputs, expressed as a percentage across all of the licenses matched.`)
|
||||
descriptions.Add(&o.LicenseCoverage, `deprecated: please use 'coverage' instead`)
|
||||
}
|
||||
|
||||
func (o *licenseConfig) PostLoad() error {
|
||||
cfg := cataloging.DefaultLicenseConfig()
|
||||
defaultContent := cfg.IncludeContent
|
||||
defaultCoverage := cfg.Coverage
|
||||
|
||||
// if both legacy and new fields are specified, error out
|
||||
if o.IncludeUnknownLicenseContent != nil && o.Content != defaultContent {
|
||||
return fmt.Errorf("both 'include-unknown-license-content' and 'content' are set, please use only 'content'")
|
||||
validContent := internal.NewSet(o.AvailableLicenseContent...)
|
||||
if !validContent.Contains(o.Content) {
|
||||
return fmt.Errorf("could not use %q as license content option; valid values are: %v", o.Content, validContent.ToSlice())
|
||||
}
|
||||
|
||||
if o.LicenseCoverage != nil && o.Coverage != defaultCoverage {
|
||||
return fmt.Errorf("both 'license-coverage' and 'coverage' are set, please use only 'coverage'")
|
||||
}
|
||||
|
||||
// finalize the license content value
|
||||
if o.IncludeUnknownLicenseContent != nil {
|
||||
// convert 'include-unknown-license-content' -> 'license-content'
|
||||
v := cataloging.LicenseContentExcludeAll
|
||||
if *o.IncludeUnknownLicenseContent {
|
||||
v = cataloging.LicenseContentIncludeUnknown
|
||||
}
|
||||
o.Content = v
|
||||
}
|
||||
|
||||
// finalize the coverage value
|
||||
if o.LicenseCoverage != nil {
|
||||
// convert 'license-coverage' -> 'coverage'
|
||||
o.Coverage = *o.LicenseCoverage
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
scope: source.AllLayersScope,
|
||||
packageCount: 178, // without deduplication this would be ~600
|
||||
packageCount: 175, // without deduplication this would be ~600
|
||||
instanceCount: map[string]int{
|
||||
"basesystem": 1,
|
||||
"wget": 1,
|
||||
@@ -30,15 +30,15 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
locationCount: map[string]int{
|
||||
"basesystem-11-13.el9": 5, // in all layers
|
||||
"curl-minimal-7.76.1-26.el9_3.2.0.1": 2, // base + wget layer
|
||||
"curl-minimal-7.76.1-29.el9_4.1": 3, // curl upgrade layer + all above layers
|
||||
"curl-minimal-7.76.1-31.el9": 3, // curl upgrade layer + all above layers
|
||||
"wget-1.21.1-8.el9_4": 4, // wget + all above layers
|
||||
"vsftpd-3.0.5-5.el9": 2, // vsftpd + all above layers
|
||||
"httpd-2.4.57-11.el9_4.1": 1, // last layer
|
||||
"vsftpd-3.0.5-6.el9": 2, // vsftpd + all above layers
|
||||
"httpd-2.4.62-1.el9_5.2": 1, // last layer
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: source.SquashedScope,
|
||||
packageCount: 172,
|
||||
packageCount: 169,
|
||||
instanceCount: map[string]int{
|
||||
"basesystem": 1,
|
||||
"wget": 1,
|
||||
@@ -47,11 +47,11 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
"httpd": 1, // rpm, binary is now excluded by overlap
|
||||
},
|
||||
locationCount: map[string]int{
|
||||
"basesystem-11-13.el9": 1,
|
||||
"curl-minimal-7.76.1-29.el9_4.1": 1, // upgrade
|
||||
"wget-1.21.1-8.el9_4": 1,
|
||||
"vsftpd-3.0.5-5.el9": 1,
|
||||
"httpd-2.4.57-11.el9_4.1": 1,
|
||||
"basesystem-11-13.el9": 1,
|
||||
"curl-minimal-7.76.1-31.el9": 1, // upgrade
|
||||
"wget-1.21.1-8.el9_4": 1,
|
||||
"vsftpd-3.0.5-6.el9": 1,
|
||||
"httpd-2.4.62-1.el9_5.2": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ package integration
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestPhotonPackageRegression(t *testing.T) { // Regression: https://github.com/anchore/syft/pull/1997
|
||||
sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope)
|
||||
var packages []pkg.Package
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
packages = append(packages, p)
|
||||
var count int
|
||||
for range sbom.Artifacts.Packages.Enumerate(pkg.RpmPkg) {
|
||||
count++
|
||||
}
|
||||
|
||||
if len(packages) < 1 {
|
||||
t.Errorf("failed to find packages for photon distro; wanted > 0 got 0")
|
||||
}
|
||||
assert.Greater(t, count, 0, "expected to find RPM packages in the SBOM (but did not)")
|
||||
}
|
||||
|
||||
@@ -19,11 +19,12 @@ require (
|
||||
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d
|
||||
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec
|
||||
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
|
||||
github.com/anchore/stereoscope v0.1.4
|
||||
github.com/anchore/stereoscope v0.1.5
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||
@@ -39,14 +40,14 @@ require (
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.3.3
|
||||
github.com/gkampitakis/go-snaps v0.5.11
|
||||
github.com/gkampitakis/go-snaps v0.5.13
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.0
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1
|
||||
github.com/gohugoio/hashstructure v0.5.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-containerregistry v0.20.3
|
||||
github.com/google/go-containerregistry v0.20.5
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.5.4
|
||||
@@ -57,9 +58,8 @@ require (
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.7
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/knqyf263/go-rpmdb v0.1.1
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/mholt/archives v0.1.1
|
||||
github.com/mholt/archives v0.1.2
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
@@ -72,7 +72,7 @@ require (
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb
|
||||
github.com/spdx/tools-golang v0.5.5
|
||||
github.com/spf13/afero v1.14.0
|
||||
@@ -85,11 +85,11 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/mod v0.24.0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/mod v0.25.0
|
||||
golang.org/x/net v0.41.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.37.0
|
||||
modernc.org/sqlite v1.38.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -100,11 +100,11 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.7 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.2.0 // indirect
|
||||
github.com/STARRY-S/zip v0.2.1 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.1 // indirect
|
||||
@@ -133,10 +133,10 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.1.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v28.1.1+incompatible // indirect
|
||||
github.com/docker/cli v28.2.2+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v28.1.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
@@ -147,13 +147,13 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.3.1 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.3.2 // indirect
|
||||
github.com/gkampitakis/go-diff v1.3.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/goccy/go-yaml v1.15.13 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@@ -226,7 +226,7 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/vbatts/tar-split v0.11.6 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
@@ -235,31 +235,32 @@ require (
|
||||
github.com/zclconf/go-cty v1.13.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||
google.golang.org/grpc v1.67.3 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.62.1 // indirect
|
||||
modernc.org/libc v1.65.10 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.9.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
v1.25.0 // published with a replace directive (confusing for API users)
|
||||
v0.53.2
|
||||
v0.53.1 // Published accidentally with incorrect license in depdencies
|
||||
v0.53.1 // published accidentally with incorrect license in depdencies
|
||||
)
|
||||
|
||||
@@ -78,8 +78,8 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
|
||||
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
|
||||
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
@@ -110,6 +110,8 @@ github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4
|
||||
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
|
||||
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 h1:Ha+LSCVuXYSYGi7wIkJK6G8g6jI3LH7y6LbyEVyp4Io=
|
||||
@@ -120,11 +122,11 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
github.com/anchore/stereoscope v0.1.4 h1:e+iT9UdUzLBabWGe84hn5sTHDRioY+4IHsVzJXuJlek=
|
||||
github.com/anchore/stereoscope v0.1.4/go.mod h1:omWgXDEp/XfqCJlZXIByEo1c3ArZg/qTJ5LBKVLAIdw=
|
||||
github.com/anchore/stereoscope v0.1.5 h1:/wdjSoerdbYtXNvezReNrmzWeBOFJeHt7hLXz6Xg/WE=
|
||||
github.com/anchore/stereoscope v0.1.5/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
@@ -262,14 +264,14 @@ github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4i
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
|
||||
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
|
||||
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||
@@ -323,12 +325,12 @@ github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFA
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/github/go-spdx/v2 v2.3.3 h1:QI7evnHWEfWkT54eJwkoV/f3a0xD3gLlnVmT5wQG6LE=
|
||||
github.com/github/go-spdx/v2 v2.3.3/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ=
|
||||
github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgagapo=
|
||||
github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
|
||||
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
|
||||
github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY=
|
||||
github.com/gkampitakis/go-snaps v0.5.13 h1:Hhjmvv1WboSCxkR9iU2mj5PQ8tsz/y8ECGrIbjjPF8Q=
|
||||
github.com/gkampitakis/go-snaps v0.5.13/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -339,8 +341,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
|
||||
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -364,8 +366,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg=
|
||||
github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -426,8 +428,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
|
||||
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
|
||||
github.com/google/go-containerregistry v0.20.5 h1:4RnlYcDs5hoA++CeFjlbZ/U9Yp1EuWr+UhhTyYQjOP0=
|
||||
github.com/google/go-containerregistry v0.20.5/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
||||
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
||||
@@ -548,8 +550,6 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584=
|
||||
github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@@ -600,8 +600,8 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY=
|
||||
github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I=
|
||||
github.com/mholt/archives v0.1.2 h1:UBSe5NfYKHI1sy+S5dJsEsG9jsKKk8NJA4HCC+xTI4A=
|
||||
github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprUOwZWDs=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
@@ -751,8 +751,8 @@ github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEW
|
||||
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@@ -836,8 +836,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vbatts/go-mtree v0.5.4 h1:OMAb8jaCyiFA7zXj0Zc/oARcxBDBoeu2LizjB8BVJl0=
|
||||
github.com/vbatts/go-mtree v0.5.4/go.mod h1:5GqJbVhm9BBiCc4K5uc/c42FPgXulHaQs4sFUEfIWMo=
|
||||
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
|
||||
github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
|
||||
@@ -885,20 +885,22 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
|
||||
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
@@ -925,8 +927,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -937,8 +939,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -966,8 +968,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1012,8 +1014,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1043,8 +1045,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1137,8 +1139,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1199,8 +1201,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1390,26 +1392,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
|
||||
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
|
||||
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
||||
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
|
||||
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
|
||||
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
||||
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
|
||||
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
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 = "16.0.32"
|
||||
JSONSchemaVersion = "16.0.34"
|
||||
)
|
||||
|
||||
@@ -92,6 +92,12 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
|
||||
return relationships
|
||||
}
|
||||
|
||||
// Build a map of real paths to packages that directly own them. We'll use this
|
||||
// to check if a file is already owned by the same type of package when we're
|
||||
// determining ownership via symlink.
|
||||
directOwnership := directOwnersByPath(catalog)
|
||||
|
||||
// Now establish relationships, considering symlinks
|
||||
for _, candidateOwnerPkg := range catalog.Sorted() {
|
||||
id := candidateOwnerPkg.ID()
|
||||
if candidateOwnerPkg.Metadata == nil {
|
||||
@@ -105,16 +111,34 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
|
||||
}
|
||||
|
||||
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
|
||||
// find the first path that results in a hit
|
||||
for _, ownedPath := range allPaths(ownedFilePath, resolver) {
|
||||
if matchesAny(ownedPath, globsForbiddenFromBeingOwned) {
|
||||
if ownedFilePath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// find paths that result in a hit (includes resolving symlinks)
|
||||
resolvedPaths := resolvePaths(ownedFilePath, resolver)
|
||||
|
||||
for _, resolvedPath := range resolvedPaths {
|
||||
if matchesAny(resolvedPath, globsForbiddenFromBeingOwned) {
|
||||
// we skip over known exceptions to file ownership, such as the RPM package owning
|
||||
// the RPM DB path, otherwise the RPM package would "own" all RPMs, which is not intended
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip claiming ownership via symlink if another package of the same type
|
||||
// directly owns this real path. This is the specific fix for the issue where a
|
||||
// symlink shouldn't allow a package to claim ownership when another package of
|
||||
// the same type directly owns the real file.
|
||||
if resolvedPath != ownedFilePath { // This is a resolved symlink path
|
||||
// Check if another package of the same type directly owns this path
|
||||
if paths := directOwnership[candidateOwnerPkg.Type]; paths != nil && paths.Has(resolvedPath) {
|
||||
// Skip this path - a package of the same type directly owns it
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// look for package(s) in the catalog that may be owned by this package and mark the relationship
|
||||
for _, subPackage := range catalog.PackagesByPath(ownedPath) {
|
||||
for _, subPackage := range catalog.PackagesByPath(resolvedPath) {
|
||||
subID := subPackage.ID()
|
||||
if subID == id {
|
||||
continue
|
||||
@@ -126,7 +150,7 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
|
||||
if _, exists := relationships[id][subID]; !exists {
|
||||
relationships[id][subID] = strset.New()
|
||||
}
|
||||
relationships[id][subID].Add(ownedPath)
|
||||
relationships[id][subID].Add(resolvedPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +159,40 @@ func findOwnershipByFilesRelationships(resolver file.Resolver, catalog *pkg.Coll
|
||||
return relationships
|
||||
}
|
||||
|
||||
func allPaths(ownedFilePath string, resolver file.Resolver) []string {
|
||||
func directOwnersByPath(catalog *pkg.Collection) map[pkg.Type]*strset.Set {
|
||||
directOwnership := map[pkg.Type]*strset.Set{}
|
||||
|
||||
// First, identify direct ownership of all files
|
||||
for _, p := range catalog.Sorted() {
|
||||
if p.Metadata == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// check to see if this is a file owner
|
||||
pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
|
||||
if ownedFilePath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Register direct ownership
|
||||
paths := directOwnership[p.Type]
|
||||
if paths == nil {
|
||||
paths = strset.New()
|
||||
directOwnership[p.Type] = paths
|
||||
}
|
||||
paths.Add(ownedFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
return directOwnership
|
||||
}
|
||||
|
||||
func resolvePaths(ownedFilePath string, resolver file.Resolver) []string {
|
||||
// though we have a string path, we need to resolve symlinks and other filesystem oddities since we cannot assume this is a real path
|
||||
var locs []file.Location
|
||||
var err error
|
||||
|
||||
@@ -78,6 +78,64 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
|
||||
return []pkg.Package{parent, child}, []artifact.Relationship{relationship}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only-real-file-owner-gets-ownership-relationship-not-symlink-owner",
|
||||
resolver: mockFR{
|
||||
translate: map[string]string{
|
||||
"/usr/bin/jenkins.war-symlink": "/usr/share/jenkins/jenkins.war", // symlink to the real file
|
||||
},
|
||||
},
|
||||
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
|
||||
// Package that owns the symlink
|
||||
symlinkOwner := pkg.Package{
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "jenkins-symlink-pkg",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/usr/bin/jenkins.war-symlink"}, // this symlinks to the real file
|
||||
},
|
||||
},
|
||||
}
|
||||
symlinkOwner.SetID()
|
||||
|
||||
// Package that owns the real file
|
||||
realFileOwner := pkg.Package{
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "jenkins-real-pkg",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/usr/share/jenkins/jenkins.war"}, // the real file
|
||||
},
|
||||
},
|
||||
}
|
||||
realFileOwner.SetID()
|
||||
|
||||
// A third package that is "located at" the real file path
|
||||
// This simulates a package being discovered at the jenkins.war location
|
||||
consumerPackage := pkg.Package{
|
||||
Type: pkg.JavaPkg,
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/usr/share/jenkins/jenkins.war", "/usr/share/jenkins/jenkins.war"),
|
||||
),
|
||||
}
|
||||
consumerPackage.SetID()
|
||||
|
||||
// Only the real file owner should have an ownership relationship with the consumer package
|
||||
// The symlink owner should NOT have a relationship with the consumer
|
||||
relationship := artifact.Relationship{
|
||||
From: realFileOwner,
|
||||
To: consumerPackage,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
Data: ownershipByFilesMetadata{
|
||||
Files: []string{
|
||||
"/usr/share/jenkins/jenkins.war",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return []pkg.Package{symlinkOwner, realFileOwner, consumerPackage}, []artifact.Relationship{relationship}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "misses-by-dead-symlink",
|
||||
resolver: mockFR{
|
||||
|
||||
@@ -278,16 +278,16 @@ func applyLicenseContentRules(p *pkg.Package, cfg cataloging.LicenseConfig) {
|
||||
l := &licenses[i]
|
||||
switch cfg.IncludeContent {
|
||||
case cataloging.LicenseContentIncludeUnknown:
|
||||
// we don't have an SPDX expression, which means we didn't find an SPDX license
|
||||
// include the unknown licenses content in the final SBOM
|
||||
// we have an SPDX expression, which means this is NOT an unknown license
|
||||
// remove the content, we are only including content for unknown licenses by default
|
||||
if l.SPDXExpression != "" {
|
||||
licenses[i].Contents = ""
|
||||
}
|
||||
case cataloging.LicenseContentExcludeAll:
|
||||
// clear it all out
|
||||
licenses[i].Contents = ""
|
||||
case cataloging.LicenseContentIncludeAll:
|
||||
// always include the content
|
||||
default:
|
||||
// clear it all out
|
||||
licenses[i].Contents = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ func TestApplyLicenseContentRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IncludeLicenseContentDefault",
|
||||
name: "LicenseContentIncludeAll",
|
||||
inputLicenses: []pkg.License{
|
||||
licenseWithSPDX,
|
||||
licenseWithoutSPDX,
|
||||
@@ -193,6 +193,41 @@ func TestApplyLicenseContentRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default license config should be LicenseContentExcludeAll",
|
||||
inputLicenses: []pkg.License{
|
||||
licenseWithSPDX,
|
||||
licenseWithoutSPDX,
|
||||
},
|
||||
cfg: cataloging.DefaultLicenseConfig(),
|
||||
expectedLicenses: []pkg.License{
|
||||
{
|
||||
SPDXExpression: "MIT",
|
||||
},
|
||||
{
|
||||
Value: "License-Not-A-SPDX-Expression",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid license content cataloging config results in the default case",
|
||||
inputLicenses: []pkg.License{
|
||||
licenseWithSPDX,
|
||||
licenseWithoutSPDX,
|
||||
},
|
||||
cfg: cataloging.LicenseConfig{
|
||||
IncludeContent: cataloging.LicenseContent("invalid"),
|
||||
},
|
||||
expectedLicenses: []pkg.License{
|
||||
{
|
||||
SPDXExpression: "MIT",
|
||||
},
|
||||
{
|
||||
Value: "License-Not-A-SPDX-Expression",
|
||||
Contents: "", // content all removed
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Empty licenses",
|
||||
inputLicenses: []pkg.License{},
|
||||
|
||||
@@ -154,7 +154,8 @@ func DefaultPackageTaskFactories() Factories {
|
||||
},
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary",
|
||||
),
|
||||
newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package"),
|
||||
newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package", "elf"),
|
||||
newSimplePackageTaskFactory(binary.NewPEPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "pe-package", "pe", "dll", "exe"),
|
||||
newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
|
||||
newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
|
||||
newSimplePackageTaskFactory(java.NewJvmDistributionCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "java", "jvm", "jdk", "jre"),
|
||||
@@ -164,6 +165,7 @@ func DefaultPackageTaskFactories() Factories {
|
||||
},
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
|
||||
),
|
||||
newSimplePackageTaskFactory(php.NewInterpreterCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "php"),
|
||||
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
|
||||
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag),
|
||||
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.32/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.34/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -1971,6 +1971,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/OpamPackage"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/PeBinary"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/PhpComposerInstalledEntry"
|
||||
},
|
||||
@@ -2045,6 +2048,17 @@
|
||||
"purl"
|
||||
]
|
||||
},
|
||||
"PeBinary": {
|
||||
"properties": {
|
||||
"VersionResources": {
|
||||
"$ref": "#/$defs/KeyValues"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"VersionResources"
|
||||
]
|
||||
},
|
||||
"PhpComposerAuthors": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -2677,6 +2691,12 @@
|
||||
"sourceRpm": {
|
||||
"type": "string"
|
||||
},
|
||||
"signatures": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RpmSignature"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -2745,6 +2765,12 @@
|
||||
"sourceRpm": {
|
||||
"type": "string"
|
||||
},
|
||||
"signatures": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RpmSignature"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -2821,6 +2847,29 @@
|
||||
"flags"
|
||||
]
|
||||
},
|
||||
"RpmSignature": {
|
||||
"properties": {
|
||||
"algo": {
|
||||
"type": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"issuer": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"algo",
|
||||
"hash",
|
||||
"created",
|
||||
"issuer"
|
||||
]
|
||||
},
|
||||
"RubyGemspec": {
|
||||
"properties": {
|
||||
"name": {
|
||||
|
||||
@@ -14,10 +14,6 @@ const (
|
||||
)
|
||||
|
||||
type LicenseConfig struct {
|
||||
// IncludeUnknownLicenseContent controls whether the content of a license should be included in the SBOM when the license ID cannot be determined.
|
||||
// Deprecated: use IncludeContent instead
|
||||
IncludeUnknownLicenseContent bool `json:"-" yaml:"-" mapstructure:"-"`
|
||||
|
||||
// IncludeContent controls whether license copy discovered should be included in the SBOM.
|
||||
IncludeContent LicenseContent `json:"include-content" yaml:"include-content" mapstructure:"include-content"`
|
||||
|
||||
|
||||
@@ -255,6 +255,7 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t
|
||||
RelationshipsConfig: c.Relationships,
|
||||
DataGenerationConfig: c.DataGeneration,
|
||||
PackagesConfig: c.Packages,
|
||||
LicenseConfig: c.Licenses,
|
||||
ComplianceConfig: c.Compliance,
|
||||
FilesConfig: c.Files,
|
||||
}
|
||||
|
||||
@@ -958,7 +958,7 @@ func Test_otherLicenses(t *testing.T) {
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
|
||||
// Carries through the syft-json license value when we shasum large texts
|
||||
LicenseName: "LicenseRef-sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
|
||||
LicenseName: "sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
|
||||
ExtractedText: strings.TrimSpace(bigText),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/spdx/tools-golang/spdx/v2/common"
|
||||
|
||||
@@ -45,7 +46,7 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
|
||||
},
|
||||
}
|
||||
|
||||
collectSyftPackages(s, spdxIDMap, doc.Packages)
|
||||
collectSyftPackages(s, spdxIDMap, doc)
|
||||
|
||||
collectSyftFiles(s, spdxIDMap, doc)
|
||||
|
||||
@@ -279,8 +280,12 @@ func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, packages []*spdx.Package) {
|
||||
for _, p := range packages {
|
||||
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document) {
|
||||
skipIDs := packageIDsToSkip(doc)
|
||||
for _, p := range doc.Packages {
|
||||
if p == nil || skipIDs.Has(string(p.PackageSPDXIdentifier)) {
|
||||
continue
|
||||
}
|
||||
syftPkg := toSyftPackage(p)
|
||||
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
||||
s.Artifacts.Packages.Add(syftPkg)
|
||||
@@ -657,3 +662,15 @@ func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
|
||||
}
|
||||
return cpes
|
||||
}
|
||||
|
||||
// packageIDsToSkip returns a set of packageIDs that should not be imported
|
||||
func packageIDsToSkip(doc *spdx.Document) *strset.Set {
|
||||
skipIDs := strset.New()
|
||||
for i := 0; i < len(doc.Relationships); i++ {
|
||||
r := doc.Relationships[i]
|
||||
if r != nil && r.Relationship == spdx.RelationshipGeneratedFrom {
|
||||
skipIDs.Add(string(r.RefB.ElementRefID))
|
||||
}
|
||||
}
|
||||
return skipIDs
|
||||
}
|
||||
|
||||
@@ -759,3 +759,37 @@ func Test_useSPDXIdentifierOverDerivedSyftArtifactID(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, s.Artifacts.Packages.Package("1"))
|
||||
}
|
||||
|
||||
func Test_skipsPackagesWithGeneratedFromRelationship(t *testing.T) {
|
||||
doc := &spdx.Document{
|
||||
SPDXVersion: "SPDX-2.3",
|
||||
Packages: []*spdx.Package{
|
||||
{
|
||||
PackageName: "package-1",
|
||||
PackageSPDXIdentifier: "1",
|
||||
PackageVersion: "1.0.5",
|
||||
},
|
||||
{
|
||||
PackageName: "package-1-src",
|
||||
PackageSPDXIdentifier: "1-src",
|
||||
PackageVersion: "1.0.5-src",
|
||||
},
|
||||
},
|
||||
Relationships: []*spdx.Relationship{
|
||||
{
|
||||
Relationship: spdx.RelationshipGeneratedFrom,
|
||||
RefA: common.DocElementID{ // package 1
|
||||
ElementRefID: spdx.ElementID("1"),
|
||||
},
|
||||
RefB: common.DocElementID{ // generated from package 1-src
|
||||
ElementRefID: spdx.ElementID("1-src"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
s, err := ToSyftModel(doc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, s.Artifacts.Packages.Package("1"))
|
||||
assert.Nil(t, s.Artifacts.Packages.Package("1-src"))
|
||||
}
|
||||
|
||||
@@ -94,7 +94,15 @@ func redactor(values ...string) testutil.Redactor {
|
||||
testutil.PatternReplacement{
|
||||
// only the source component bom-ref (not package or other component bom-refs)
|
||||
Search: regexp.MustCompile(`<component bom-ref="(?P<redact>[^"]*)" type="file">`),
|
||||
Groups: []string{"redact"}, // use the regex to anchore the search, but only replace bytes within the capture group
|
||||
Groups: []string{"redact"}, // use the regex to anchor the search, but only replace bytes within the capture group
|
||||
Replace: "redacted",
|
||||
},
|
||||
).
|
||||
WithPatternRedactorSpec(
|
||||
testutil.PatternReplacement{
|
||||
// only the source component bom-ref (not package or other component bom-refs)
|
||||
Search: regexp.MustCompile(`<component bom-ref="(?P<redact>[^"]*)" type="container">`),
|
||||
Groups: []string{"redact"}, // use the regex to anchor the search, but only replace bytes within the capture group
|
||||
Replace: "redacted",
|
||||
},
|
||||
).
|
||||
|
||||
@@ -71,31 +71,14 @@ func Backfill(p *pkg.Package) {
|
||||
}
|
||||
}
|
||||
|
||||
func setJavaMetadataFromPurl(p *pkg.Package, purl packageurl.PackageURL) {
|
||||
func setJavaMetadataFromPurl(p *pkg.Package, _ packageurl.PackageURL) {
|
||||
if p.Type != pkg.JavaPkg {
|
||||
return
|
||||
}
|
||||
if purl.Namespace != "" {
|
||||
if p.Metadata == nil {
|
||||
p.Metadata = pkg.JavaArchive{}
|
||||
}
|
||||
meta, got := p.Metadata.(pkg.JavaArchive)
|
||||
if got && meta.PomProperties == nil {
|
||||
meta.PomProperties = &pkg.JavaPomProperties{}
|
||||
p.Metadata = meta
|
||||
}
|
||||
if meta.PomProperties != nil {
|
||||
// capture the group id from the purl if it is not already set
|
||||
if meta.PomProperties.ArtifactID == "" {
|
||||
meta.PomProperties.ArtifactID = purl.Name
|
||||
}
|
||||
if meta.PomProperties.GroupID == "" {
|
||||
meta.PomProperties.GroupID = purl.Namespace
|
||||
}
|
||||
if meta.PomProperties.Version == "" {
|
||||
meta.PomProperties.Version = purl.Version
|
||||
}
|
||||
}
|
||||
if p.Metadata == nil {
|
||||
// since we don't know if the purl elements directly came from pom properties or the manifest,
|
||||
// we can only go as far as to set the type to JavaArchive, but not fill in the group id and artifact id
|
||||
p.Metadata = pkg.JavaArchive{}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,13 +101,9 @@ func Test_Backfill(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Name: "some-thing",
|
||||
Version: "1.2.3",
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.apache",
|
||||
ArtifactID: "some-thing",
|
||||
Version: "1.2.3",
|
||||
},
|
||||
},
|
||||
// we intentionally don't claim we found a pom properties file with a groupID from the purl.
|
||||
// but we do claim that we found java data with an empty type.
|
||||
Metadata: pkg.JavaArchive{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
@@ -90,15 +91,19 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||
Locations: decodeLocations(values),
|
||||
Licenses: pkg.NewLicenseSet(decodeLicenses(c)...),
|
||||
CPEs: decodeCPEs(c),
|
||||
PURL: c.PackageURL,
|
||||
}
|
||||
|
||||
// note: this may write in syft package type information
|
||||
DecodeInto(p, values, "syft:package", CycloneDXFields)
|
||||
|
||||
metadataType := values["syft:package:metadataType"]
|
||||
|
||||
p.Metadata = decodePackageMetadata(values, c, metadataType)
|
||||
|
||||
// this will either use the purl from the component or generate a new one based off of any type information
|
||||
// that was decoded above.
|
||||
p.PURL = getPURL(c, p.Type)
|
||||
|
||||
if p.Type == "" {
|
||||
p.Type = pkg.TypeFromPURL(p.PURL)
|
||||
}
|
||||
@@ -111,6 +116,41 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||
return p
|
||||
}
|
||||
|
||||
func getPURL(c *cyclonedx.Component, ty pkg.Type) string {
|
||||
if c.PackageURL != "" {
|
||||
// if there is a purl that where the namespace does not match the group information, we may
|
||||
// accidentally drop group. We should consider adding group as a top-level syft package field.
|
||||
return c.PackageURL
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.BOMRef, "pkg:") {
|
||||
// the bomref is a purl, so try to use that as the purl
|
||||
_, err := packageurl.FromString(c.BOMRef)
|
||||
if err == nil {
|
||||
return c.BOMRef
|
||||
}
|
||||
}
|
||||
|
||||
if ty == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
tyStr := ty.PackageURLType()
|
||||
switch tyStr {
|
||||
case "", packageurl.TypeGeneric:
|
||||
return ""
|
||||
}
|
||||
|
||||
purl := packageurl.PackageURL{
|
||||
Type: tyStr,
|
||||
Namespace: c.Group,
|
||||
Name: c.Name,
|
||||
Version: c.Version,
|
||||
}
|
||||
|
||||
return purl.ToString()
|
||||
}
|
||||
|
||||
func setPackageName(p *pkg.Package, c *cyclonedx.Component) {
|
||||
name := c.Name
|
||||
if c.Group != "" {
|
||||
|
||||
@@ -275,6 +275,7 @@ func Test_decodeComponent(t *testing.T) {
|
||||
component cyclonedx.Component
|
||||
wantLanguage pkg.Language
|
||||
wantMetadata any
|
||||
wantPURL string
|
||||
}{
|
||||
{
|
||||
name: "derive language from pURL if missing",
|
||||
@@ -286,6 +287,18 @@ func Test_decodeComponent(t *testing.T) {
|
||||
BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
|
||||
},
|
||||
wantLanguage: pkg.Java,
|
||||
wantPURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
|
||||
},
|
||||
{
|
||||
name: "derive language from bomref if missing",
|
||||
component: cyclonedx.Component{
|
||||
Name: "ch.qos.logback/logback-classic",
|
||||
Version: "1.2.3",
|
||||
Type: "library",
|
||||
BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
|
||||
},
|
||||
wantLanguage: pkg.Java,
|
||||
wantPURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3",
|
||||
},
|
||||
{
|
||||
name: "handle RpmdbMetadata type without properties",
|
||||
@@ -303,6 +316,7 @@ func Test_decodeComponent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantMetadata: pkg.RpmDBEntry{},
|
||||
wantPURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
|
||||
},
|
||||
{
|
||||
name: "handle RpmdbMetadata type with properties",
|
||||
@@ -326,6 +340,24 @@ func Test_decodeComponent(t *testing.T) {
|
||||
wantMetadata: pkg.RpmDBEntry{
|
||||
Release: "some-release",
|
||||
},
|
||||
wantPURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8",
|
||||
},
|
||||
{
|
||||
name: "generate a purl from package type",
|
||||
component: cyclonedx.Component{
|
||||
Name: "log4j",
|
||||
Group: "org.apache.logging.log4j",
|
||||
Version: "2.0.4",
|
||||
Type: "library",
|
||||
BOMRef: "log4j",
|
||||
Properties: &[]cyclonedx.Property{
|
||||
{
|
||||
Name: "syft:package:type",
|
||||
Value: "java-archive",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantPURL: "pkg:maven/org.apache.logging.log4j/log4j@2.0.4",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -338,9 +370,122 @@ func Test_decodeComponent(t *testing.T) {
|
||||
if tt.wantMetadata != nil {
|
||||
assert.Truef(t, reflect.DeepEqual(tt.wantMetadata, p.Metadata), "metadata should match: %+v != %+v", tt.wantMetadata, p.Metadata)
|
||||
}
|
||||
if tt.wantMetadata == nil && tt.wantLanguage == "" {
|
||||
|
||||
if tt.wantPURL != "" {
|
||||
assert.Equal(t, tt.wantPURL, p.PURL, "purl should match")
|
||||
}
|
||||
|
||||
if tt.wantMetadata == nil && tt.wantLanguage == "" && tt.wantPURL == "" {
|
||||
t.Fatal("this is a useless test, please remove it")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
component *cyclonedx.Component
|
||||
pkgType pkg.Type
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "component with PackageURL",
|
||||
component: &cyclonedx.Component{
|
||||
PackageURL: "pkg:npm/lodash@4.17.21",
|
||||
Name: "lodash",
|
||||
Version: "4.17.20", // different version to verify PackageURL is used
|
||||
Group: "npm",
|
||||
},
|
||||
pkgType: pkg.NpmPkg,
|
||||
expected: "pkg:npm/lodash@4.17.21",
|
||||
},
|
||||
{
|
||||
name: "component with BOMRef as valid PURL",
|
||||
component: &cyclonedx.Component{
|
||||
BOMRef: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
|
||||
Name: "commons-lang3",
|
||||
Version: "3.11.0", // different version to verify BOMRef is used
|
||||
Group: "org.apache.commons",
|
||||
},
|
||||
pkgType: pkg.JavaPkg,
|
||||
expected: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
|
||||
},
|
||||
{
|
||||
name: "component with BOMRef not a valid PURL",
|
||||
component: &cyclonedx.Component{
|
||||
BOMRef: "not-a-purl-ref",
|
||||
Name: "commons-lang3",
|
||||
Version: "3.12.0",
|
||||
Group: "org.apache.commons",
|
||||
},
|
||||
pkgType: pkg.JavaPkg,
|
||||
expected: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
|
||||
},
|
||||
{
|
||||
name: "component with unknown package type",
|
||||
component: &cyclonedx.Component{
|
||||
Name: "some-component",
|
||||
Version: "1.0.0",
|
||||
Group: "org.example",
|
||||
},
|
||||
pkgType: pkg.UnknownPkg,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "component with empty package type",
|
||||
component: &cyclonedx.Component{
|
||||
Name: "some-component",
|
||||
Version: "1.0.0",
|
||||
Group: "org.example",
|
||||
},
|
||||
pkgType: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "component with generic package type",
|
||||
component: &cyclonedx.Component{
|
||||
Name: "some-component",
|
||||
Version: "1.0.0",
|
||||
Group: "org.example",
|
||||
},
|
||||
pkgType: pkg.LinuxKernelModulePkg,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "component with valid package type",
|
||||
component: &cyclonedx.Component{
|
||||
Name: "react",
|
||||
Version: "18.2.0",
|
||||
Group: "facebook",
|
||||
},
|
||||
pkgType: pkg.NpmPkg,
|
||||
expected: "pkg:npm/facebook/react@18.2.0",
|
||||
},
|
||||
{
|
||||
name: "component with no group",
|
||||
component: &cyclonedx.Component{
|
||||
Name: "express",
|
||||
Version: "4.18.2",
|
||||
},
|
||||
pkgType: pkg.NpmPkg,
|
||||
expected: "pkg:npm/express@4.18.2",
|
||||
},
|
||||
{
|
||||
name: "component with no version",
|
||||
component: &cyclonedx.Component{
|
||||
Name: "express",
|
||||
Group: "npm",
|
||||
},
|
||||
pkgType: pkg.NpmPkg,
|
||||
expected: "pkg:npm/npm/express",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getPURL(tt.component, tt.pkgType)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense, otherL
|
||||
for _, l := range raw {
|
||||
candidate := createSPDXLicense(l)
|
||||
|
||||
// isCustomLicense determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
|
||||
// this determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
|
||||
// of the SPDX spec, where:
|
||||
// - we should not have a complex SPDX expression
|
||||
// - if a single license, it should not be a known license (on the SPDX license list)
|
||||
|
||||
@@ -50,6 +50,14 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: gocyclo,f
|
||||
case pkg.DotnetPortableExecutableEntry:
|
||||
typ = orgType
|
||||
author = metadata.CompanyName
|
||||
case pkg.PEBinary:
|
||||
// this is a known common keyword used in version resources
|
||||
// for more info see: https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
|
||||
val, ok := metadata.VersionResources.Get("CompanyName")
|
||||
if ok {
|
||||
typ = orgType
|
||||
author = val
|
||||
}
|
||||
|
||||
case pkg.DpkgDBEntry:
|
||||
author = metadata.Maintainer
|
||||
|
||||
@@ -112,6 +112,21 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
originator: "Organization: Microsoft Corporation",
|
||||
supplier: "Organization: Microsoft Corporation",
|
||||
},
|
||||
{
|
||||
name: "from PE binary",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PEBinary{
|
||||
VersionResources: pkg.KeyValues{
|
||||
{
|
||||
Key: "CompanyName",
|
||||
Value: "Microsoft Corporation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Organization: Microsoft Corporation",
|
||||
supplier: "Organization: Microsoft Corporation",
|
||||
},
|
||||
{
|
||||
name: "from dpkg DB",
|
||||
input: pkg.Package{
|
||||
|
||||
@@ -165,13 +165,9 @@ func TestDecoder_Decode(t *testing.T) {
|
||||
Type: pkg.JavaPkg,
|
||||
PURL: "pkg:maven/org.apache/some-pkg@4.11.3",
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.apache",
|
||||
ArtifactID: "some-pkg",
|
||||
Version: "4.11.3",
|
||||
},
|
||||
},
|
||||
// we intentionally do not claim we found a pom properties file (don't derive this from the purl).
|
||||
// but we need a metadata allocated since all Java packages have a this metadata type (a consistency point)
|
||||
Metadata: pkg.JavaArchive{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Document represents the syft cataloging findings as a JSON document
|
||||
type Document struct {
|
||||
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||
@@ -11,6 +16,25 @@ type Document struct {
|
||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
}
|
||||
|
||||
func (d *Document) UnmarshalJSON(data []byte) error {
|
||||
type Alias *Document
|
||||
aux := Alias(d)
|
||||
|
||||
if err := json.Unmarshal(data, aux); err != nil {
|
||||
return fmt.Errorf("could not unmarshal syft JSON document: %w", err)
|
||||
}
|
||||
|
||||
// in previous versions of anchorectl, the file modes were stored as decimal values instead of octal.
|
||||
if d.Schema.Version == "1.0.0" && d.Descriptor.Name == "anchorectl" {
|
||||
// convert all file modes from decimal to octal
|
||||
for i := range d.Files {
|
||||
d.Files[i].Metadata.Mode = convertBase10ToBase8(d.Files[i].Metadata.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Descriptor describes what created the document as well as surrounding metadata
|
||||
type Descriptor struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData string
|
||||
modes []int
|
||||
}{
|
||||
{
|
||||
name: "schema version 1.0.0 + anchorectl",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 493}},
|
||||
{"metadata": {"mode": 420}}
|
||||
],
|
||||
"schema": {"version": "1.0.0"},
|
||||
"descriptor": {
|
||||
"name": "anchorectl"
|
||||
}
|
||||
}`,
|
||||
modes: []int{755, 644},
|
||||
},
|
||||
{
|
||||
name: "schema version 1.0.0 + syft",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 755}},
|
||||
{"metadata": {"mode": 644}}
|
||||
],
|
||||
"schema": {"version": "1.0.0"},
|
||||
"descriptor": {
|
||||
"name": "syft"
|
||||
}
|
||||
}`,
|
||||
modes: []int{755, 644},
|
||||
},
|
||||
{
|
||||
name: "schema version 2.0.0 + anchorectl",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 755}},
|
||||
{"metadata": {"mode": 644}}
|
||||
],
|
||||
"schema": {"version": "2.0.0"},
|
||||
"descriptor": {
|
||||
"name": "anchorectl"
|
||||
}
|
||||
}`,
|
||||
modes: []int{755, 644},
|
||||
},
|
||||
{
|
||||
name: "missing schema version should not convert modes",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 755}}
|
||||
],
|
||||
"schema": {}
|
||||
}`,
|
||||
modes: []int{755},
|
||||
},
|
||||
{
|
||||
name: "empty files array with version 1.0.0",
|
||||
jsonData: `{
|
||||
"files": [],
|
||||
"schema": {"version": "1.0.0"},
|
||||
"descriptor": {
|
||||
"name": "anchorectl"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var doc Document
|
||||
|
||||
err := json.Unmarshal([]byte(tt.jsonData), &doc)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
var modes []int
|
||||
for _, file := range doc.Files {
|
||||
modes = append(modes, file.Metadata.Mode)
|
||||
}
|
||||
|
||||
require.Len(t, doc.Files, len(tt.modes), "Unexpected number of files")
|
||||
assert.Equal(t, tt.modes, modes, "File modes do not match expected values")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
stereoFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
@@ -26,6 +31,57 @@ type FileMetadataEntry struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type auxFileMetadataEntry FileMetadataEntry
|
||||
type fileMetadataEntryWithLegacyHint struct {
|
||||
*auxFileMetadataEntry `json:",inline"`
|
||||
LegacyHint any `json:"FileInfo"`
|
||||
}
|
||||
|
||||
func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error {
|
||||
aux := fileMetadataEntryWithLegacyHint{
|
||||
auxFileMetadataEntry: (*auxFileMetadataEntry)(f),
|
||||
}
|
||||
if err := json.Unmarshal(data, &aux); err == nil {
|
||||
fieldsSpecified := f.Mode != 0 || f.Type != "" || f.LinkDestination != "" ||
|
||||
f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0
|
||||
if aux.LegacyHint == nil && fieldsSpecified {
|
||||
// we should have at least one field set to a non-zero value... (this is not a legacy shape)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var legacy sbomImportLegacyFileMetadataEntry
|
||||
if err := json.Unmarshal(data, &legacy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !legacy.Type.WasInt {
|
||||
// this occurs for document shapes from a non-import path and indicates that the mode has already been converted to octal.
|
||||
// That being said, we want to handle all legacy shapes the same, so we will convert this to base 10 for consistency.
|
||||
legacy.Mode = convertBase8ToBase10(legacy.Mode)
|
||||
}
|
||||
|
||||
f.Mode = legacy.Mode
|
||||
f.Type = legacy.Type.Value
|
||||
f.LinkDestination = legacy.LinkDestination
|
||||
f.UserID = legacy.UserID
|
||||
f.GroupID = legacy.GroupID
|
||||
f.MIMEType = legacy.MIMEType
|
||||
f.Size = legacy.Size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type sbomImportLegacyFileMetadataEntry struct {
|
||||
Mode int `json:"Mode"`
|
||||
Type intOrStringFileType `json:"Type"`
|
||||
LinkDestination string `json:"LinkDestination"`
|
||||
UserID int `json:"UserID"`
|
||||
GroupID int `json:"GroupID"`
|
||||
MIMEType string `json:"MIMEType"`
|
||||
Size int64 `json:"Size"`
|
||||
}
|
||||
|
||||
type FileLicense struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
@@ -38,3 +94,39 @@ type FileLicenseEvidence struct {
|
||||
Offset int `json:"offset"`
|
||||
Extent int `json:"extent"`
|
||||
}
|
||||
|
||||
type intOrStringFileType struct {
|
||||
Value string
|
||||
WasInt bool
|
||||
}
|
||||
|
||||
func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err == nil {
|
||||
lt.Value = str
|
||||
return nil
|
||||
}
|
||||
|
||||
var num stereoFile.Type
|
||||
if err := json.Unmarshal(data, &num); err != nil {
|
||||
return fmt.Errorf("file.Type must be either string or int, got: %s", string(data))
|
||||
}
|
||||
|
||||
lt.Value = num.String()
|
||||
lt.WasInt = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertBase10ToBase8(rawMode int) int {
|
||||
octalStr := fmt.Sprintf("%o", rawMode)
|
||||
// we don't need to check that this is a valid octal string since the input is always an integer
|
||||
result, _ := strconv.Atoi(octalStr)
|
||||
return result
|
||||
}
|
||||
|
||||
func convertBase8ToBase10(octalMode int) int {
|
||||
octalStr := strconv.Itoa(octalMode)
|
||||
result, _ := strconv.ParseInt(octalStr, 8, 64)
|
||||
|
||||
return int(result)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData []byte
|
||||
expected FileMetadataEntry
|
||||
}{
|
||||
{
|
||||
name: "unmarshal current format",
|
||||
jsonData: []byte(`{
|
||||
"mode": 644,
|
||||
"type": "RegularFile",
|
||||
"linkDestination": "/usr/bin/python3",
|
||||
"userID": 1000,
|
||||
"groupID": 1000,
|
||||
"mimeType": "text/plain",
|
||||
"size": 10174
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Mode: 644,
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "/usr/bin/python3",
|
||||
UserID: 1000,
|
||||
GroupID: 1000,
|
||||
MIMEType: "text/plain",
|
||||
Size: 10174,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal legacy image add internal document format",
|
||||
jsonData: []byte(`{
|
||||
"FileInfo": {},
|
||||
"Mode": 644,
|
||||
"Type": "RegularFile",
|
||||
"LinkDestination": "/usr/bin/python3",
|
||||
"UserID": 1000,
|
||||
"GroupID": 1000,
|
||||
"MIMEType": "text/plain",
|
||||
"Size": 10174
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Mode: 420, // important! we convert this to base 10 so that all documents are consistent
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "/usr/bin/python3",
|
||||
UserID: 1000,
|
||||
GroupID: 1000,
|
||||
MIMEType: "text/plain",
|
||||
Size: 10174,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal legacy sbom import format",
|
||||
jsonData: []byte(`{
|
||||
"FileInfo": {},
|
||||
"Mode": 644,
|
||||
"Type": 0,
|
||||
"LinkDestination": "/usr/bin/python3",
|
||||
"UserID": 1000,
|
||||
"GroupID": 1000,
|
||||
"MIMEType": "text/plain",
|
||||
"Size": 10174
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Mode: 644,
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "/usr/bin/python3",
|
||||
UserID: 1000,
|
||||
GroupID: 1000,
|
||||
MIMEType: "text/plain",
|
||||
Size: 10174,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal minimal current format",
|
||||
jsonData: []byte(`{
|
||||
"mode": 0,
|
||||
"type": "RegularFile",
|
||||
"userID": 0,
|
||||
"groupID": 0,
|
||||
"size": 0
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Type: "RegularFile",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal minimal legacy format",
|
||||
jsonData: []byte(`{
|
||||
"FileInfo": {},
|
||||
"Mode": 0,
|
||||
"Type": "RegularFile",
|
||||
"UserID": 0,
|
||||
"GroupID": 0,
|
||||
"Size": 0
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Type: "RegularFile",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var actual FileMetadataEntry
|
||||
err := actual.UnmarshalJSON(test.jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
if diff := cmp.Diff(test.expected, actual); diff != "" {
|
||||
t.Errorf("FileMetadataEntry mismatch (-expected +actual):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData []byte
|
||||
expected string
|
||||
expectedWasInt bool
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
// string inputs - should pass through unchanged
|
||||
{
|
||||
name: "string RegularFile",
|
||||
jsonData: []byte(`"RegularFile"`),
|
||||
expected: "RegularFile",
|
||||
},
|
||||
{
|
||||
name: "string HardLink",
|
||||
jsonData: []byte(`"HardLink"`),
|
||||
expected: "HardLink",
|
||||
},
|
||||
{
|
||||
name: "string Directory",
|
||||
jsonData: []byte(`"Directory"`),
|
||||
expected: "Directory",
|
||||
},
|
||||
{
|
||||
name: "string custom value",
|
||||
jsonData: []byte(`"CustomFileType"`),
|
||||
expected: "CustomFileType",
|
||||
},
|
||||
// integer inputs - should convert to string representation
|
||||
{
|
||||
name: "int 0 (TypeRegular)",
|
||||
jsonData: []byte(`0`),
|
||||
expected: "RegularFile",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 1 (TypeHardLink)",
|
||||
jsonData: []byte(`1`),
|
||||
expected: "HardLink",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 2 (TypeSymLink)",
|
||||
jsonData: []byte(`2`),
|
||||
expected: "SymbolicLink",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 3 (TypeCharacterDevice)",
|
||||
jsonData: []byte(`3`),
|
||||
expected: "CharacterDevice",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 4 (TypeBlockDevice)",
|
||||
jsonData: []byte(`4`),
|
||||
expected: "BlockDevice",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 5 (TypeDirectory)",
|
||||
jsonData: []byte(`5`),
|
||||
expected: "Directory",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 6 (TypeFIFO)",
|
||||
jsonData: []byte(`6`),
|
||||
expected: "FIFONode",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 7 (TypeSocket)",
|
||||
jsonData: []byte(`7`),
|
||||
expected: "Socket",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 8 (TypeIrregular)",
|
||||
jsonData: []byte(`8`),
|
||||
expected: "IrregularFile",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "unknown int",
|
||||
jsonData: []byte(`99`),
|
||||
expected: "Unknown",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "negative int",
|
||||
jsonData: []byte(`-1`),
|
||||
expected: "Unknown",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "null value",
|
||||
jsonData: []byte(`null`),
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
jsonData: []byte(`{`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "boolean value",
|
||||
jsonData: []byte(`true`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "array value",
|
||||
jsonData: []byte(`[]`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "object value",
|
||||
jsonData: []byte(`{}`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "float value",
|
||||
jsonData: []byte(`1.5`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.wantErr == nil {
|
||||
test.wantErr = require.NoError
|
||||
}
|
||||
var ft intOrStringFileType
|
||||
err := ft.UnmarshalJSON(test.jsonData)
|
||||
test.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, test.expected, ft.Value)
|
||||
assert.Equal(t, test.expectedWasInt, ft.WasInt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_convertBase10ToBase8(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "no permissions",
|
||||
input: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "symlink + rwxrwxrwx",
|
||||
input: 134218239,
|
||||
expected: 1000000777,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := convertBase10ToBase8(tt.input)
|
||||
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_convertBase8ToBase10(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "no permissions",
|
||||
input: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "symlink + rwxrwxrwx",
|
||||
input: 1000000777,
|
||||
expected: 134218239,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := convertBase8ToBase10(tt.input)
|
||||
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -144,13 +144,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
}
|
||||
|
||||
func safeFileModeConvert(val int) (fs.FileMode, error) {
|
||||
if val < math.MinInt32 || val > math.MaxInt32 {
|
||||
// Value is out of the range that int32 can represent
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
|
||||
if mode < 0 || mode > math.MaxUint32 {
|
||||
// value is out of the range that int32 can represent
|
||||
return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val)
|
||||
}
|
||||
|
||||
// Safe to convert to os.FileMode
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -491,6 +491,12 @@ func Test_safeFileModeConvert(t *testing.T) {
|
||||
want: os.FileMode(511), // 777 in octal equals 511 in decimal
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid perm with symlink type",
|
||||
val: 1000000777, // symlink + rwxrwxrwx
|
||||
want: os.FileMode(0o1000000777), // 134218239
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "outside int32 high",
|
||||
val: int(math.MaxInt32) + 1,
|
||||
|
||||
@@ -1451,7 +1451,7 @@ func TestFileResolver_FilesByGlob(t *testing.T) {
|
||||
|
||||
resolver, err := NewFromFile(parentPath, filePath)
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("*.txt")
|
||||
refs, err := resolver.FilesByGlob("**/*.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, refs, 1)
|
||||
|
||||
@@ -42,6 +42,7 @@ func AllTypes() []any {
|
||||
pkg.NpmPackage{},
|
||||
pkg.NpmPackageLockEntry{},
|
||||
pkg.OpamPackage{},
|
||||
pkg.PEBinary{},
|
||||
pkg.PhpComposerInstalledEntry{},
|
||||
pkg.PhpComposerLockEntry{},
|
||||
pkg.PhpPearEntry{},
|
||||
|
||||
@@ -94,6 +94,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.NpmPackage{}, "javascript-npm-package", "NpmPackageJsonMetadata"),
|
||||
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
|
||||
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
|
||||
jsonNames(pkg.PEBinary{}, "pe-binary"),
|
||||
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.PhpPeclEntry{}, "php-pecl-entry", "PhpPeclMetadata"),
|
||||
|
||||
@@ -46,3 +46,8 @@ type ELFBinaryPackageNoteJSONPayload struct {
|
||||
// Commit is the commit hash of the source repository for which the binary was built from
|
||||
Commit string `json:"commit,omitempty"`
|
||||
}
|
||||
|
||||
// PEBinary represents metadata captured from a Portable Executable formatted binary (dll, exe, etc.)
|
||||
type PEBinary struct {
|
||||
VersionResources KeyValues
|
||||
}
|
||||
|
||||
@@ -13,12 +13,13 @@ import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
||||
)
|
||||
|
||||
const catalogerName = "binary-classifier-cataloger"
|
||||
|
||||
type ClassifierCatalogerConfig struct {
|
||||
Classifiers []Classifier `yaml:"classifiers" json:"classifiers" mapstructure:"classifiers"`
|
||||
Classifiers []binutils.Classifier `yaml:"classifiers" json:"classifiers" mapstructure:"classifiers"`
|
||||
}
|
||||
|
||||
func DefaultClassifierCatalogerConfig() ClassifierCatalogerConfig {
|
||||
@@ -48,7 +49,7 @@ func (cfg ClassifierCatalogerConfig) MarshalJSON() ([]byte, error) {
|
||||
// related runtimes like Python, Go, Java, or Node. Some exceptions can be made for widely-used binaries such
|
||||
// as busybox.
|
||||
type cataloger struct {
|
||||
classifiers []Classifier
|
||||
classifiers []binutils.Classifier
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes the cataloger
|
||||
@@ -101,7 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
|
||||
target.Metadata = meta
|
||||
}
|
||||
|
||||
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
|
||||
func catalog(resolver file.Resolver, cls binutils.Classifier) (packages []pkg.Package, err error) {
|
||||
var errs error
|
||||
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
||||
if err != nil {
|
||||
@@ -109,7 +110,7 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
|
||||
return nil, err
|
||||
}
|
||||
for _, location := range locations {
|
||||
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
|
||||
pkgs, err := cls.EvidenceMatcher(cls, binutils.MatcherContext{Resolver: resolver, Location: location})
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
@@ -248,45 +249,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("httpd-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
// TODO: find original binary...
|
||||
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
|
||||
logicalFixture: "php-cli/8.2.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "php-cli",
|
||||
Version: "8.2.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/php-cli@8.2.1",
|
||||
Locations: locations("php"),
|
||||
Metadata: metadata("php-cli-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
// TODO: find original binary...
|
||||
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
|
||||
logicalFixture: "php-fpm/8.2.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "php-fpm",
|
||||
Version: "8.2.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/php-fpm@8.2.1",
|
||||
Locations: locations("php-fpm"),
|
||||
Metadata: metadata("php-fpm-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
// TODO: find original binary...
|
||||
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
|
||||
logicalFixture: "php-apache/8.2.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "libphp",
|
||||
Version: "8.2.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/php@8.2.1",
|
||||
Locations: locations("libphp.so"),
|
||||
Metadata: metadata("php-apache-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
// TODO: original binary is different than whats in config.yaml
|
||||
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
|
||||
@@ -1497,11 +1459,13 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
||||
Locations: locations("foo"),
|
||||
Metadata: metadata("foo-binary"),
|
||||
}
|
||||
fooClassifier := Classifier{
|
||||
fooClassifier := binutils.Classifier{
|
||||
Class: "foo-binary",
|
||||
FileGlob: "**/foo",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
EvidenceMatcher: binutils.FileContentsVersionMatcher(
|
||||
`(?m)foobar\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
catalogerName,
|
||||
),
|
||||
Package: "foo",
|
||||
PURL: mustPURL("pkg:generic/foo@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
|
||||
@@ -1516,7 +1480,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
||||
{
|
||||
name: "empty-negative",
|
||||
config: ClassifierCatalogerConfig{
|
||||
Classifiers: []Classifier{},
|
||||
Classifiers: []binutils.Classifier{},
|
||||
},
|
||||
fixtureDir: "test-fixtures/custom/go-1.14",
|
||||
expected: nil,
|
||||
@@ -1532,7 +1496,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
||||
{
|
||||
name: "nodefault-negative",
|
||||
config: ClassifierCatalogerConfig{
|
||||
Classifiers: []Classifier{fooClassifier},
|
||||
Classifiers: []binutils.Classifier{fooClassifier},
|
||||
},
|
||||
fixtureDir: "test-fixtures/custom/go-1.14",
|
||||
expected: nil,
|
||||
@@ -1541,7 +1505,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
||||
name: "default-extended-positive",
|
||||
config: ClassifierCatalogerConfig{
|
||||
Classifiers: append(
|
||||
append([]Classifier{}, defaultClassifers...),
|
||||
append([]binutils.Classifier{}, defaultClassifers...),
|
||||
fooClassifier,
|
||||
),
|
||||
},
|
||||
@@ -1553,11 +1517,11 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
||||
config: ClassifierCatalogerConfig{
|
||||
|
||||
Classifiers: append(
|
||||
append([]Classifier{}, defaultClassifers...),
|
||||
Classifier{
|
||||
append([]binutils.Classifier{}, defaultClassifers...),
|
||||
binutils.Classifier{
|
||||
Class: "foo-binary",
|
||||
FileGlob: "**/foo",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)not there`),
|
||||
EvidenceMatcher: binutils.FileContentsVersionMatcher(`(?m)not there`, catalogerName),
|
||||
Package: "foo",
|
||||
PURL: mustPURL("pkg:generic/foo@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:foo:foo:*:*:*:*:*:*:*:*"),
|
||||
@@ -1571,7 +1535,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
||||
name: "default-cutsom-positive",
|
||||
config: ClassifierCatalogerConfig{
|
||||
Classifiers: append(
|
||||
append([]Classifier{}, defaultClassifers...),
|
||||
append([]binutils.Classifier{}, defaultClassifers...),
|
||||
fooClassifier,
|
||||
),
|
||||
},
|
||||
@@ -1771,11 +1735,11 @@ func TestCatalogerConfig_MarshalJSON(t *testing.T) {
|
||||
{
|
||||
name: "only show names of classes",
|
||||
cfg: ClassifierCatalogerConfig{
|
||||
Classifiers: []Classifier{
|
||||
Classifiers: []binutils.Classifier{
|
||||
{
|
||||
Class: "class",
|
||||
FileGlob: "glob",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(".thing"),
|
||||
EvidenceMatcher: binutils.FileContentsVersionMatcher(".thing", catalogerName),
|
||||
Package: "pkg",
|
||||
PURL: packageurl.PackageURL{
|
||||
Type: "type",
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
||||
)
|
||||
|
||||
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
|
||||
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
|
||||
|
||||
//nolint:funlen
|
||||
func DefaultClassifiers() []Classifier {
|
||||
return []Classifier{
|
||||
func DefaultClassifiers() []binutils.Classifier {
|
||||
m := binutils.ContextualEvidenceMatchers{CatalogerName: catalogerName}
|
||||
|
||||
var libpythonMatcher = m.FileNameTemplateVersionMatcher(
|
||||
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
|
||||
pythonVersionTemplate,
|
||||
)
|
||||
|
||||
var rubyMatcher = m.FileContentsVersionMatcher(
|
||||
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
|
||||
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
|
||||
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
|
||||
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
|
||||
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
|
||||
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
|
||||
|
||||
return []binutils.Classifier{
|
||||
{
|
||||
Class: "python-binary",
|
||||
FileGlob: "**/python*",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
// try to find version information from libpython shared libraries
|
||||
sharedLibraryLookup(
|
||||
binutils.SharedLibraryLookup(
|
||||
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
|
||||
libpythonMatcher),
|
||||
// check for version information in the binary
|
||||
fileNameTemplateVersionMatcher(
|
||||
m.FileNameTemplateVersionMatcher(
|
||||
`(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`,
|
||||
pythonVersionTemplate),
|
||||
),
|
||||
@@ -41,7 +63,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "pypy-binary-lib",
|
||||
FileGlob: "**/libpypy*.so*",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\[PyPy (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "pypy",
|
||||
PURL: mustPURL("pkg:generic/pypy@version"),
|
||||
@@ -49,7 +71,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "go-binary",
|
||||
FileGlob: "**/go",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
|
||||
Package: "go",
|
||||
PURL: mustPURL("pkg:generic/go@version"),
|
||||
@@ -58,7 +80,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "julia-binary",
|
||||
FileGlob: "**/libjulia-internal.so",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)__init__\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00verify`),
|
||||
Package: "julia",
|
||||
PURL: mustPURL("pkg:generic/julia@version"),
|
||||
@@ -67,7 +89,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "helm",
|
||||
FileGlob: "**/helm",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
|
||||
Package: "helm",
|
||||
PURL: mustPURL("pkg:golang/helm.sh/helm@version"),
|
||||
@@ -76,13 +98,13 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "redis-binary",
|
||||
FileGlob: "**/redis-server",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
|
||||
FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
|
||||
m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
|
||||
// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
|
||||
FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
|
||||
m.FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
|
||||
// matches against older versions of redis (~v2), e.g. "Server started, Redis version 2.8.23"
|
||||
FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
m.FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
),
|
||||
Package: "redis",
|
||||
PURL: mustPURL("pkg:generic/redis@version"),
|
||||
@@ -94,13 +116,13 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "java-binary-openjdk",
|
||||
FileGlob: "**/java",
|
||||
EvidenceMatcher: matchExcluding(
|
||||
evidenceMatchers(
|
||||
FileContentsVersionMatcher(
|
||||
EvidenceMatcher: binutils.MatchExcluding(
|
||||
binutils.EvidenceMatchers(
|
||||
m.FileContentsVersionMatcher(
|
||||
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
|
||||
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
|
||||
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
|
||||
FileContentsVersionMatcher(
|
||||
m.FileContentsVersionMatcher(
|
||||
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
|
||||
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
|
||||
),
|
||||
@@ -115,7 +137,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "java-binary-ibm",
|
||||
FileGlob: "**/java",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]java[NUL]1.8[NUL][NUL][NUL][NUL]1.8.0-foreman_2022_09_22_15_30-b00[NUL]
|
||||
`(?m)\x00java\x00(?P<release>[0-9]+[.0-9]+)\x00{4}(?P<version>[0-9]+[-._a-zA-Z0-9]+)\x00`),
|
||||
Package: "java/jre",
|
||||
@@ -125,8 +147,8 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "java-binary-oracle",
|
||||
FileGlob: "**/java",
|
||||
EvidenceMatcher: matchExcluding(
|
||||
FileContentsVersionMatcher(
|
||||
EvidenceMatcher: binutils.MatchExcluding(
|
||||
m.FileContentsVersionMatcher(
|
||||
// [NUL]19.0.1+10-21[NUL]
|
||||
`(?m)\x00(?P<version>[0-9]+[.0-9]+[+][-0-9]+)\x00`),
|
||||
// don't match openjdk
|
||||
@@ -139,7 +161,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "java-binary-graalvm",
|
||||
FileGlob: "**/java",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00(?P<version>[0-9]+[.0-9]+[.0-9]+\+[0-9]+-jvmci-[0-9]+[.0-9]+-b[0-9]+)\x00`),
|
||||
Package: "java/graalvm",
|
||||
PURL: mustPURL("pkg:generic/java/graalvm@version"),
|
||||
@@ -148,7 +170,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "java-binary-jdk",
|
||||
FileGlob: "**/jdb",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?([-._a-zA-Z0-9]+)?)\x00`),
|
||||
Package: "java/jdk",
|
||||
PURL: mustPURL("pkg:generic/java/jdk@version"),
|
||||
@@ -157,13 +179,13 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "nodejs-binary",
|
||||
FileGlob: "**/node",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
// [NUL]node v0.10.48[NUL]
|
||||
// [NUL]v0.12.18[NUL]
|
||||
// [NUL]v4.9.1[NUL]
|
||||
// node.js/v22.9.0
|
||||
FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
|
||||
FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
m.FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
|
||||
m.FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
),
|
||||
Package: "node",
|
||||
PURL: mustPURL("pkg:generic/node@version"),
|
||||
@@ -172,7 +194,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "go-binary-hint",
|
||||
FileGlob: "**/VERSION*",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?(-[0-9a-f]{7})?)`),
|
||||
Package: "go",
|
||||
PURL: mustPURL("pkg:generic/go@version"),
|
||||
@@ -181,7 +203,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "busybox-binary",
|
||||
FileGlob: "**/busybox",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "busybox",
|
||||
PURL: mustPURL("pkg:generic/busybox@version"),
|
||||
@@ -190,7 +212,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "util-linux-binary",
|
||||
FileGlob: "**/getopt",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00util-linux\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
|
||||
Package: "util-linux",
|
||||
PURL: mustPURL("pkg:generic/util-linux@version"),
|
||||
@@ -199,10 +221,10 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "haproxy-binary",
|
||||
FileGlob: "**/haproxy",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
|
||||
FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
|
||||
FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
m.FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
|
||||
m.FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
|
||||
m.FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
|
||||
),
|
||||
Package: "haproxy",
|
||||
PURL: mustPURL("pkg:generic/haproxy@version"),
|
||||
@@ -211,44 +233,16 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "perl-binary",
|
||||
FileGlob: "**/perl",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\/usr\/local\/lib\/perl\d\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "perl",
|
||||
PURL: mustPURL("pkg:generic/perl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "php-cli-binary",
|
||||
FileGlob: "**/php*",
|
||||
EvidenceMatcher: 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: mustPURL("pkg:generic/php-cli@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "php-fpm-binary",
|
||||
FileGlob: "**/php-fpm*",
|
||||
EvidenceMatcher: 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: mustPURL("pkg:generic/php-fpm@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "php-apache-binary",
|
||||
FileGlob: "**/libphp*.so",
|
||||
EvidenceMatcher: 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: mustPURL("pkg:generic/php@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:php:php:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "php-composer-binary",
|
||||
FileGlob: "**/composer*",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)'pretty_version'\s*=>\s*'(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)'`),
|
||||
Package: "composer",
|
||||
PURL: mustPURL("pkg:generic/composer@version"),
|
||||
@@ -257,7 +251,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "httpd-binary",
|
||||
FileGlob: "**/httpd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)Apache\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "httpd",
|
||||
PURL: mustPURL("pkg:generic/httpd@version"),
|
||||
@@ -266,7 +260,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "memcached-binary",
|
||||
FileGlob: "**/memcached",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)memcached\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "memcached",
|
||||
PURL: mustPURL("pkg:generic/memcached@version"),
|
||||
@@ -275,7 +269,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "traefik-binary",
|
||||
FileGlob: "**/traefik",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]v1.7.34[NUL]
|
||||
// [NUL]2.9.6[NUL]
|
||||
// 3.0.4[NUL]
|
||||
@@ -287,7 +281,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "arangodb-binary",
|
||||
FileGlob: "**/arangosh",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s\[linux\]`),
|
||||
Package: "arangodb",
|
||||
PURL: mustPURL("pkg:generic/arangodb@version"),
|
||||
@@ -296,7 +290,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "postgresql-binary",
|
||||
FileGlob: "**/postgres",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]PostgreSQL 15beta4
|
||||
// [NUL]PostgreSQL 15.1
|
||||
// [NUL]PostgreSQL 9.6.24
|
||||
@@ -309,11 +303,11 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "mysql-binary",
|
||||
FileGlob: "**/mysql",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
// shutdown[NUL]8.0.37[NUL][NUL][NUL][NUL][NUL]mysql_real_esc
|
||||
FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
|
||||
m.FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
|
||||
// /export/home/pb2/build/sb_0-26781090-1516292385.58/release/mysql-8.0.4-rc/mysys_ssl/my_default.cc
|
||||
FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
|
||||
m.FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
|
||||
),
|
||||
Package: "mysql",
|
||||
PURL: mustPURL("pkg:generic/mysql@version"),
|
||||
@@ -322,7 +316,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "mysql-binary",
|
||||
FileGlob: "**/mysql",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m).*/percona-server-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
|
||||
Package: "percona-server",
|
||||
PURL: mustPURL("pkg:generic/percona-server@version"),
|
||||
@@ -334,7 +328,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "mysql-binary",
|
||||
FileGlob: "**/mysql",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m).*/Percona-XtraDB-Cluster-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
|
||||
Package: "percona-xtradb-cluster",
|
||||
PURL: mustPURL("pkg:generic/percona-xtradb-cluster@version"),
|
||||
@@ -347,7 +341,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "xtrabackup-binary",
|
||||
FileGlob: "**/xtrabackup",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m).*/percona-xtrabackup-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
|
||||
Package: "percona-xtrabackup",
|
||||
PURL: mustPURL("pkg:generic/percona-xtrabackup@version"),
|
||||
@@ -356,7 +350,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "mariadb-binary",
|
||||
FileGlob: "**/{mariadb,mysql}",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// 10.6.15-MariaDB
|
||||
`(?m)(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`),
|
||||
Package: "mariadb",
|
||||
@@ -366,7 +360,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "rust-standard-library-linux",
|
||||
FileGlob: "**/libstd-????????????????.so",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// clang LLVM (rustc version 1.48.0 (7eac88abb 2020-11-16))
|
||||
`(?m)(\x00)clang LLVM \(rustc version (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
|
||||
Package: "rust",
|
||||
@@ -376,7 +370,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "rust-standard-library-macos",
|
||||
FileGlob: "**/libstd-????????????????.dylib",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// c 1.48.0 (7eac88abb 2020-11-16)
|
||||
`(?m)c (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
|
||||
Package: "rust",
|
||||
@@ -386,9 +380,9 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "ruby-binary",
|
||||
FileGlob: "**/ruby",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
rubyMatcher,
|
||||
sharedLibraryLookup(
|
||||
binutils.SharedLibraryLookup(
|
||||
// try to find version information from libruby shared libraries
|
||||
`^libruby\.so.*$`,
|
||||
rubyMatcher),
|
||||
@@ -400,12 +394,12 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "erlang-binary",
|
||||
FileGlob: "**/erlexec",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
m.FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
|
||||
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
FileContentsVersionMatcher(
|
||||
m.FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
|
||||
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
@@ -417,16 +411,16 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "erlang-alpine-binary",
|
||||
FileGlob: "**/beam.smp",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
m.FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
|
||||
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
FileContentsVersionMatcher(
|
||||
m.FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
|
||||
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
FileContentsVersionMatcher(
|
||||
m.FileContentsVersionMatcher(
|
||||
// [NUL][NUL]26.1.2[NUL][NUL][NUL][NUL][NUL][NUL][NUL]NUL[NUL][NUL]Erlang/OTP
|
||||
`\x00+(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)\x00+Erlang/OTP`,
|
||||
),
|
||||
@@ -438,12 +432,12 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "erlang-library",
|
||||
FileGlob: "**/liberts_internal.a",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(
|
||||
EvidenceMatcher: binutils.EvidenceMatchers(
|
||||
m.FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
|
||||
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
FileContentsVersionMatcher(
|
||||
m.FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
|
||||
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
@@ -455,7 +449,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "swipl-binary",
|
||||
FileGlob: "**/swipl",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)swipl-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\/`,
|
||||
),
|
||||
Package: "swipl",
|
||||
@@ -465,7 +459,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "dart-binary",
|
||||
FileGlob: "**/dart",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// MathAtan[NUL]2.12.4 (stable)
|
||||
// "%s"[NUL]3.0.0 (stable)
|
||||
// Dart,GC"[NUL]3.5.2 (stable)
|
||||
@@ -479,7 +473,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "haskell-ghc-binary",
|
||||
FileGlob: "**/ghc*",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00GHC (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "haskell/ghc",
|
||||
@@ -489,7 +483,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "haskell-cabal-binary",
|
||||
FileGlob: "**/cabal",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00Cabal-(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?)-`,
|
||||
),
|
||||
Package: "haskell/cabal",
|
||||
@@ -499,7 +493,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "haskell-stack-binary",
|
||||
FileGlob: "**/stack",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)Version\s*(?P<version>[0-9]+\.[0-9]+\.[0-9]+),\s*Git`,
|
||||
),
|
||||
Package: "haskell/stack",
|
||||
@@ -509,7 +503,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "consul-binary",
|
||||
FileGlob: "**/consul",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// NOTE: This is brittle and may not work for past or future versions
|
||||
`CONSUL_VERSION: (?P<version>\d+\.\d+\.\d+)`,
|
||||
),
|
||||
@@ -520,7 +514,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "nginx-binary",
|
||||
FileGlob: "**/nginx",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1'
|
||||
// [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part
|
||||
`(?m)(\x00|\?)nginx version: [^\/]+\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`,
|
||||
@@ -535,7 +529,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "bash-binary",
|
||||
FileGlob: "**/bash",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// @(#)Bash version 5.2.15(1) release GNU
|
||||
// @(#)Bash version 5.2.0(1) alpha GNU
|
||||
// @(#)Bash version 5.2.0(1) beta GNU
|
||||
@@ -549,7 +543,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "openssl-binary",
|
||||
FileGlob: "**/openssl",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]OpenSSL 3.1.4'
|
||||
// [NUL]OpenSSL 1.1.1w'
|
||||
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
|
||||
@@ -561,7 +555,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "gcc-binary",
|
||||
FileGlob: "**/gcc",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// GCC: \(GNU\) 12.3.0'
|
||||
`GCC: \(GNU\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
),
|
||||
@@ -572,7 +566,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "fluent-bit-binary",
|
||||
FileGlob: "**/fluent-bit",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]3.0.2[NUL]%sFluent Bit
|
||||
// [NUL]2.2.3[NUL]Fluent Bit
|
||||
// [NUL]2.2.1[NUL][NUL][NUL]Fluent Bit
|
||||
@@ -587,7 +581,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "wordpress-cli-binary",
|
||||
FileGlob: "**/wp",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// wp-cli/wp-cli 2.9.0'
|
||||
`(?m)wp-cli/wp-cli (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
),
|
||||
@@ -598,7 +592,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "curl-binary",
|
||||
FileGlob: "**/curl",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`curl/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
),
|
||||
Package: "curl",
|
||||
@@ -608,7 +602,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "lighttpd-binary",
|
||||
FileGlob: "**/lighttpd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00lighttpd/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "lighttpd",
|
||||
@@ -618,7 +612,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "proftpd-binary",
|
||||
FileGlob: "**/proftpd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00ProFTPD Version (?P<version>[0-9]+\.[0-9]+\.[0-9]+[a-z]?)\x00`,
|
||||
),
|
||||
Package: "proftpd",
|
||||
@@ -628,7 +622,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "zstd-binary",
|
||||
FileGlob: "**/zstd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "zstd",
|
||||
@@ -638,7 +632,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "xz-binary",
|
||||
FileGlob: "**/xz",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00xz \(XZ Utils\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "xz",
|
||||
@@ -648,7 +642,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "gzip-binary",
|
||||
FileGlob: "**/gzip",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "gzip",
|
||||
@@ -658,7 +652,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "sqlcipher-binary",
|
||||
FileGlob: "**/sqlcipher",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`[^0-9]\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "sqlcipher",
|
||||
@@ -668,7 +662,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "jq-binary",
|
||||
FileGlob: "**/jq",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`\x00(?P<version>[0-9]{1,3}\.[0-9]{1,3}(\.[0-9]+)?)\x00`,
|
||||
),
|
||||
Package: "jq",
|
||||
@@ -678,7 +672,7 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "chrome-binary",
|
||||
FileGlob: "**/chrome",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
// [NUL]127.0.6533.119[NUL]Default
|
||||
`\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\x00Default`,
|
||||
),
|
||||
@@ -689,18 +683,22 @@ func DefaultClassifiers() []Classifier {
|
||||
}
|
||||
}
|
||||
|
||||
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
|
||||
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
|
||||
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
|
||||
// cpe string cannot be parsed into valid CPE Attributes
|
||||
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
|
||||
src := cpe.GeneratedSource
|
||||
if len(source) > 0 {
|
||||
src = source[0]
|
||||
}
|
||||
return []cpe.CPE{
|
||||
cpe.Must(cpeString, src),
|
||||
}
|
||||
}
|
||||
|
||||
var libpythonMatcher = fileNameTemplateVersionMatcher(
|
||||
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
|
||||
pythonVersionTemplate,
|
||||
)
|
||||
|
||||
var rubyMatcher = FileContentsVersionMatcher(
|
||||
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
|
||||
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
|
||||
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
|
||||
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
|
||||
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
|
||||
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
|
||||
func mustPURL(purl string) packageurl.PackageURL {
|
||||
p, err := packageurl.FromString(purl)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid PURL: %s", p))
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package binary
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
|
||||
|
||||
// Note: all generic utilities for catalogers have been moved to the internal/binutils package.
|
||||
|
||||
// Deprecated: This package is deprecated and will be removed in syft v2
|
||||
type Classifier = binutils.Classifier
|
||||
|
||||
// Deprecated: This package is deprecated and will be removed in syft v2
|
||||
type EvidenceMatcher = binutils.EvidenceMatcher
|
||||
|
||||
// Deprecated: This package is deprecated and will be removed in syft v2
|
||||
func FileContentsVersionMatcher(
|
||||
pattern string,
|
||||
) EvidenceMatcher {
|
||||
return binutils.FileContentsVersionMatcher(pattern, catalogerName)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ func newELFPackage(ctx context.Context, metadata elfBinaryPackageNotes, location
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, metadata.License)),
|
||||
PURL: packageURL(metadata),
|
||||
PURL: elfPackageURL(metadata),
|
||||
Type: pkgType(metadata.Type),
|
||||
Locations: locations,
|
||||
Metadata: metadata.ELFBinaryPackageNoteJSONPayload,
|
||||
@@ -26,7 +26,7 @@ func newELFPackage(ctx context.Context, metadata elfBinaryPackageNotes, location
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
func elfPackageURL(metadata elfBinaryPackageNotes) string {
|
||||
var qualifiers []packageurl.Qualifier
|
||||
|
||||
os, osVersion := osNameAndVersionFromMetadata(metadata)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
func Test_ELFPackageCataloger(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
@@ -130,7 +130,7 @@ func Test_packageURL(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.want, packageURL(test.metadata))
|
||||
assert.Equal(t, test.want, elfPackageURL(test.metadata))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
// spaceRegex includes nbsp (#160) considered to be a space character
|
||||
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
|
||||
numberRegex = regexp.MustCompile(`\d`)
|
||||
)
|
||||
|
||||
func newPEPackage(versionResources map[string]string, f file.Location) pkg.Package {
|
||||
name := findNameFromVR(versionResources)
|
||||
|
||||
if name == "" {
|
||||
// it's possible that the version resources are empty, so we fall back to the file name
|
||||
name = strings.TrimSuffix(strings.TrimSuffix(path.Base(f.RealPath), ".exe"), ".dll")
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: findVersionFromVR(versionResources),
|
||||
Locations: file.NewLocationSet(f.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: newPEBinaryVersionResourcesFromMap(versionResources),
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func newPEBinaryVersionResourcesFromMap(vr map[string]string) pkg.PEBinary {
|
||||
var kvs pkg.KeyValues
|
||||
for k, v := range vr {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
kvs = append(kvs, pkg.KeyValue{
|
||||
Key: k,
|
||||
Value: spaceNormalize(v),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(kvs, func(i, j int) bool {
|
||||
return kvs[i].Key < kvs[j].Key
|
||||
})
|
||||
|
||||
return pkg.PEBinary{
|
||||
VersionResources: kvs,
|
||||
}
|
||||
}
|
||||
|
||||
func findNameFromVR(versionResources map[string]string) string {
|
||||
// PE files not authored by Microsoft tend to use ProductName as an identifier.
|
||||
nameFields := []string{"ProductName", "FileDescription", "InternalName", "OriginalFilename"}
|
||||
|
||||
if isMicrosoftVR(versionResources) {
|
||||
// for Microsoft files, prioritize FileDescription.
|
||||
nameFields = []string{"FileDescription", "InternalName", "OriginalFilename", "ProductName"}
|
||||
}
|
||||
|
||||
var name string
|
||||
for _, field := range nameFields {
|
||||
value := spaceNormalize(versionResources[field])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
name = value
|
||||
break
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
func isMicrosoftVR(versionResources map[string]string) bool {
|
||||
return strings.Contains(strings.ToLower(versionResources["CompanyName"]), "microsoft") ||
|
||||
strings.Contains(strings.ToLower(versionResources["ProductName"]), "microsoft")
|
||||
}
|
||||
|
||||
// spaceNormalize trims and normalizes whitespace in a string.
|
||||
func spaceNormalize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
// ensure valid UTF-8.
|
||||
value = strings.ToValidUTF8(value, "")
|
||||
// consolidate all whitespace.
|
||||
value = spaceRegex.ReplaceAllString(value, " ")
|
||||
// remove non-printable characters.
|
||||
value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "")
|
||||
// consolidate again and trim.
|
||||
value = spaceRegex.ReplaceAllString(value, " ")
|
||||
value = strings.TrimSpace(value)
|
||||
return value
|
||||
}
|
||||
|
||||
func findVersionFromVR(versionResources map[string]string) string {
|
||||
productVersion := extractVersionFromResourcesValue(versionResources["ProductVersion"])
|
||||
fileVersion := extractVersionFromResourcesValue(versionResources["FileVersion"])
|
||||
|
||||
if productVersion != "" {
|
||||
return productVersion
|
||||
}
|
||||
|
||||
return fileVersion
|
||||
}
|
||||
|
||||
func extractVersionFromResourcesValue(version string) string {
|
||||
version = strings.TrimSpace(version)
|
||||
out := ""
|
||||
for i, f := range strings.Fields(version) {
|
||||
if containsNumber(out) && !containsNumber(f) {
|
||||
return out
|
||||
}
|
||||
if i == 0 {
|
||||
out = f
|
||||
} else {
|
||||
out += " " + f
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func containsNumber(s string) bool {
|
||||
return numberRegex.MatchString(s)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pe"
|
||||
)
|
||||
|
||||
// NewPEPackageCataloger returns a cataloger that interprets packages from DLL and EXE files.
|
||||
func NewPEPackageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("pe-binary-package-cataloger").
|
||||
WithParserByGlobs(parsePE, "**/*.dll", "**/*.exe")
|
||||
}
|
||||
|
||||
func parsePE(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
f, err := pe.Read(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse PE file %q: %w", reader.RealPath, err)
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
return nil, nil, unknown.Newf(reader, "unable to determine packages")
|
||||
}
|
||||
|
||||
if f.CLR.HasEvidenceOfCLR() {
|
||||
// this is for a .NET application, which is covered by other catalogers already
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
p := newPEPackage(f.VersionResources, f.Location)
|
||||
|
||||
return []pkg.Package{p}, nil, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_PEPackageCataloger(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "non-.NET package",
|
||||
fixture: "image-jruby",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "JRuby",
|
||||
Version: "9.3.15.0",
|
||||
Type: pkg.BinaryPkg,
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/jruby_windows_9_3_15_0.exe"),
|
||||
),
|
||||
FoundBy: "pe-binary-package-cataloger",
|
||||
Metadata: pkg.PEBinary{
|
||||
VersionResources: pkg.KeyValues{
|
||||
{Key: "CompanyName", Value: "JRuby Dev Team"},
|
||||
{Key: "FileDescription", Value: "JRuby"},
|
||||
{Key: "FileVersion", Value: "9.3.15.0"},
|
||||
{Key: "InternalName", Value: "jruby"},
|
||||
{Key: "LegalCopyright", Value: "JRuby Dev Team"},
|
||||
{Key: "OriginalFilename", Value: "jruby_windows-x32_9_3_15_0.exe"},
|
||||
{Key: "ProductName", Value: "JRuby"},
|
||||
{Key: "ProductVersion", Value: "9.3.15.0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore .NET packages",
|
||||
fixture: "image-dotnet-app",
|
||||
expected: nil, // expect nothing!
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range cases {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, v.fixture).
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(v.expected, nil).
|
||||
TestCataloger(t, NewPEPackageCataloger())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
name: php
|
||||
offset: unknown
|
||||
length: unknown
|
||||
snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f
|
||||
fileSha256: unknown
|
||||
|
||||
### byte snippet to follow ###
|
||||
%s'
|
||||
%s,%s
|
||||
X-Powered-By: PHP/8.2.1
|
||||
index pointer
|
||||
PHP_VERSION
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
name: php-fpm
|
||||
offset: unknown
|
||||
length: unknown
|
||||
snippetSha256: d39ac8dadf5ba868455c487f1d0bb4c8bec64006fd7e5d76e3e27a26e47e637f
|
||||
fileSha256: unknown
|
||||
|
||||
### byte snippet to follow ###
|
||||
%s'
|
||||
%s,%s
|
||||
X-Powered-By: PHP/8.2.1
|
||||
index pointer
|
||||
PHP_VERSION
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM alpine:latest AS builder
|
||||
|
||||
RUN apk add --no-cache wget unzip
|
||||
RUN mkdir -p /app
|
||||
RUN wget -O /app/minver.nupkg https://www.nuget.org/api/v2/package/minver-cli/4.3.0
|
||||
RUN unzip /app/minver.nupkg -d /app/minver
|
||||
|
||||
from busybox:latest
|
||||
|
||||
COPY --from=builder /app/minver /minver
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM --platform=linux/amd64 alpine:latest AS builder
|
||||
|
||||
RUN wget -O jruby_windows_9_3_15_0.exe https://s3.amazonaws.com/jruby.org/downloads/9.3.15.0/jruby_windows_9_3_15_0.exe
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /jruby_windows_9_3_15_0.exe /jruby_windows_9_3_15_0.exe
|
||||
@@ -473,7 +473,7 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
ldpe, err := getLogicalDotnetPE(file.NewLocationReadCloser(loc, reader))
|
||||
ldpe, err := readLogicalPE(file.NewLocationReadCloser(loc, reader))
|
||||
if err != nil {
|
||||
return nil, unknown.New(loc, fmt.Errorf("unable to parse PE file: %w", err))
|
||||
}
|
||||
@@ -482,7 +482,7 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !ldpe.CLR.hasEvidenceOfCLR() {
|
||||
if !ldpe.CLR.HasEvidenceOfCLR() {
|
||||
// this is not a .NET binary
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -252,8 +252,8 @@ func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
|
||||
if !bundlingDetected && knownBundlers.Has(name) {
|
||||
bundlingDetected = true
|
||||
}
|
||||
p.AnyChildClaimsDLLs = searchForDLLClaims(packageMap, p.dependencyNameVersions()...)
|
||||
p.AnyChildHasDLLs = searchForDLLEvidence(packageMap, p.dependencyNameVersions()...)
|
||||
p.AnyChildClaimsDLLs = searchForDLLClaims(packageMap, strset.New(), p.dependencyNameVersions()...)
|
||||
p.AnyChildHasDLLs = searchForDLLEvidence(packageMap, strset.New(), p.dependencyNameVersions()...)
|
||||
packages[p.NameVersion] = *p
|
||||
}
|
||||
|
||||
@@ -286,31 +286,35 @@ func mergeSets(s1, s2 *strset.Set) *strset.Set {
|
||||
type visitorFunc func(p *logicalDepsJSONPackage) bool
|
||||
|
||||
// searchForDLLEvidence recursively searches for executables found for any of the given nameVersions and children recursively.
|
||||
func searchForDLLEvidence(packageMap map[string]*logicalDepsJSONPackage, nameVersions ...string) bool {
|
||||
func searchForDLLEvidence(packageMap map[string]*logicalDepsJSONPackage, visited *strset.Set, nameVersions ...string) bool {
|
||||
return traverseDependencies(packageMap, func(p *logicalDepsJSONPackage) bool {
|
||||
return p.FoundDLLs(true)
|
||||
}, nameVersions...)
|
||||
}, visited, nameVersions...)
|
||||
}
|
||||
|
||||
// searchForDLLClaims recursively searches for DLL claims in the deps.json for any of the given nameVersions and children recursively.
|
||||
func searchForDLLClaims(packageMap map[string]*logicalDepsJSONPackage, nameVersions ...string) bool {
|
||||
func searchForDLLClaims(packageMap map[string]*logicalDepsJSONPackage, visited *strset.Set, nameVersions ...string) bool {
|
||||
return traverseDependencies(packageMap, func(p *logicalDepsJSONPackage) bool {
|
||||
return p.ClaimsDLLs(true)
|
||||
}, nameVersions...)
|
||||
}, visited, nameVersions...)
|
||||
}
|
||||
|
||||
func traverseDependencies(packageMap map[string]*logicalDepsJSONPackage, visitor visitorFunc, nameVersions ...string) bool {
|
||||
func traverseDependencies(packageMap map[string]*logicalDepsJSONPackage, visitor visitorFunc, visited *strset.Set, nameVersions ...string) bool {
|
||||
if len(nameVersions) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, nameVersion := range nameVersions {
|
||||
if visited.Has(nameVersion) {
|
||||
continue
|
||||
}
|
||||
visited.Add(nameVersion)
|
||||
if p, ok := packageMap[nameVersion]; ok {
|
||||
if visitor(p) {
|
||||
return true
|
||||
}
|
||||
|
||||
if traverseDependencies(packageMap, visitor, p.dependencyNameVersions()...) {
|
||||
if traverseDependencies(packageMap, visitor, visited, p.dependencyNameVersions()...) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,11 @@ func TestGetLogicalDepsJSON_MergeTargets(t *testing.T) {
|
||||
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll": {},
|
||||
},
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Common/4.0.0": {
|
||||
Dependencies: map[string]string{
|
||||
"Microsoft.CodeAnalysis.CSharp": "4.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Libraries: map[string]depsLibrary{
|
||||
|
||||
@@ -150,7 +150,7 @@ func newDotnetDepsEntry(lp logicalDepsJSONPackage) pkg.DotnetDepsEntry {
|
||||
}
|
||||
}
|
||||
|
||||
// newDotnetPortableExecutableEntry creates a portable executable entry from a logicalPE.
|
||||
// newDotnetPortableExecutableEntry creates a portable executable entry from a File.
|
||||
func newDotnetPortableExecutableEntry(pe logicalPE) pkg.DotnetPortableExecutableEntry {
|
||||
return newDotnetPortableExecutableEntryFromMap(pe.VersionResources)
|
||||
}
|
||||
|
||||
@@ -1,740 +1,30 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/scylladb/go-set/u32set"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pe"
|
||||
)
|
||||
|
||||
const peMaxAllowedDirectoryEntries = 0x1000
|
||||
|
||||
var imageDirectoryEntryIndexes = []int{
|
||||
pe.IMAGE_DIRECTORY_ENTRY_RESOURCE, // where version resources are stored
|
||||
pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // where info about the CLR is stored
|
||||
}
|
||||
|
||||
// logicalPE does not directly represent a binary shape to be parsed, instead it represents the
|
||||
// information of interest extracted from a PE file.
|
||||
// logicalPE represents a PE file within the context of a .NET project (considering the deps.json file).
|
||||
type logicalPE struct {
|
||||
// Location is where the PE file was found
|
||||
Location file.Location
|
||||
pe.File
|
||||
|
||||
// TargetPath is the path is the deps.json target entry. This is not present in the PE file
|
||||
// but instead is used in downstream processing to track associations between the PE file and the deps.json file.
|
||||
TargetPath string
|
||||
|
||||
// CLR is the information about the CLR (common language runtime) version found in the PE file which helps
|
||||
// understand if this executable is even a .NET application.
|
||||
CLR *clrEvidence
|
||||
|
||||
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
|
||||
VersionResources map[string]string
|
||||
}
|
||||
|
||||
// clrEvidence is basic info about the CLR (common language runtime) version from the COM descriptor.
|
||||
// This is not a complete representation of the CLR version, but rather a subset of the information that is
|
||||
// useful to us.
|
||||
type clrEvidence struct {
|
||||
// HasClrResourceNames is true if there are CLR resource names found in the PE file (e.g. "CLRDEBUGINFO").
|
||||
HasClrResourceNames bool
|
||||
|
||||
// MajorVersion is the minimum supported major version of the CLR.
|
||||
MajorVersion uint16
|
||||
|
||||
// MinorVersion is the minimum supported minor version of the CLR.
|
||||
MinorVersion uint16
|
||||
}
|
||||
|
||||
// hasEvidenceOfCLR returns true if the PE file has evidence of a CLR (common language runtime) version.
|
||||
func (c *clrEvidence) hasEvidenceOfCLR() bool {
|
||||
return c != nil && (c.MajorVersion != 0 && c.MinorVersion != 0 || c.HasClrResourceNames)
|
||||
}
|
||||
|
||||
type peDosHeader struct {
|
||||
Magic [2]byte // "MZ"
|
||||
Unused [58]byte
|
||||
AddressOfNewEXEHeader uint32 // offset to PE header
|
||||
}
|
||||
|
||||
// peImageCore20 represents the .NET Core 2.0 header structure.
|
||||
// Source: https://github.com/dotnet/msbuild/blob/9fa9d800dabce3bfcf8365f651f3a713e01f8a85/src/Tasks/NativeMethods.cs#L761-L775
|
||||
type peImageCore20 struct {
|
||||
Cb uint32
|
||||
MajorRuntimeVersion uint16
|
||||
MinorRuntimeVersion uint16
|
||||
}
|
||||
|
||||
// peImageResourceDirectory represents the resource directory structure.
|
||||
type peImageResourceDirectory struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
NumberOfNamedEntries uint16
|
||||
NumberOfIDEntries uint16
|
||||
}
|
||||
|
||||
// peImageResourceDirectoryEntry represents an entry in the resource directory entries.
|
||||
type peImageResourceDirectoryEntry struct {
|
||||
Name uint32
|
||||
OffsetToData uint32
|
||||
}
|
||||
|
||||
// peImageResourceDataEntry is the unit of raw data in the Resource Data area.
|
||||
type peImageResourceDataEntry struct {
|
||||
OffsetToData uint32
|
||||
Size uint32
|
||||
CodePage uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
// peVsFixedFileInfo represents the fixed file information structure.
|
||||
type peVsFixedFileInfo struct {
|
||||
Signature uint32
|
||||
StructVersion uint32
|
||||
FileVersionMS uint32
|
||||
FileVersionLS uint32
|
||||
ProductVersionMS uint32
|
||||
ProductVersionLS uint32
|
||||
FileFlagsMask uint32
|
||||
FileFlags uint32
|
||||
FileOS uint32
|
||||
FileType uint32
|
||||
FileSubtype uint32
|
||||
FileDateMS uint32
|
||||
FileDateLS uint32
|
||||
}
|
||||
|
||||
type peVsVersionInfo peLenValLenType
|
||||
|
||||
type peStringFileInfo peLenValLenType
|
||||
|
||||
type peStringTable peLenValLenType
|
||||
|
||||
type peString peLenValLenType
|
||||
|
||||
type peLenValLenType struct {
|
||||
Length uint16
|
||||
ValueLength uint16
|
||||
Type uint16
|
||||
}
|
||||
|
||||
type extractedSection struct {
|
||||
RVA uint32
|
||||
BaseRVA uint32
|
||||
Size uint32
|
||||
Reader *bytes.Reader
|
||||
}
|
||||
|
||||
func (s extractedSection) exists() bool {
|
||||
return s.RVA != 0 && s.Size != 0
|
||||
}
|
||||
|
||||
func directoryName(i int) string {
|
||||
switch i {
|
||||
case pe.IMAGE_DIRECTORY_ENTRY_RESOURCE:
|
||||
return "Resource"
|
||||
case pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
|
||||
return "COM Descriptor"
|
||||
}
|
||||
return fmt.Sprintf("Unknown (%d)", i)
|
||||
}
|
||||
|
||||
func getLogicalDotnetPE(f file.LocationReadCloser) (*logicalPE, error) {
|
||||
r, err := unionreader.GetUnionReader(f)
|
||||
func readLogicalPE(reader file.LocationReadCloser) (*logicalPE, error) {
|
||||
peFile, err := pe.Read(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sections, _, err := parsePEFile(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
|
||||
}
|
||||
|
||||
dirs := u32set.New() // keep track of the RVAs we have already parsed (prevent infinite recursion edge cases)
|
||||
versionResources := make(map[string]string) // map of version resource keys to their values
|
||||
resourceNames := strset.New() // set of resource names found in the PE file
|
||||
err = parseResourceDirectory(sections[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE], dirs, versionResources, resourceNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := parseCLR(sections[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR], resourceNames)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
|
||||
if peFile == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &logicalPE{
|
||||
Location: f.Location,
|
||||
CLR: c,
|
||||
VersionResources: versionResources,
|
||||
File: *peFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parsePEFile creates readers for targeted sections of the binary used by downstream processing.
|
||||
func parsePEFile(file unionreader.UnionReader) (map[int]*extractedSection, []pe.SectionHeader32, error) {
|
||||
fileHeader, magic, err := parsePEHeader(file)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing PE header: %w", err)
|
||||
}
|
||||
|
||||
soi, headers, err := parseSectionHeaders(file, magic, fileHeader.NumberOfSections)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing section headers: %w", err)
|
||||
}
|
||||
|
||||
for i, sec := range soi {
|
||||
if !sec.exists() {
|
||||
continue
|
||||
}
|
||||
data, err := readDataFromRVA(file, sec.RVA, sec.Size, headers)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading %q section data: %w", directoryName(i), err)
|
||||
}
|
||||
sec.Reader = data
|
||||
}
|
||||
|
||||
return soi, headers, nil
|
||||
}
|
||||
|
||||
// parsePEHeader reads the beginning of a PE formatted file, returning the file header and "magic" indicator
|
||||
// for downstream logic to determine 32/64 bit parsing.
|
||||
func parsePEHeader(file unionreader.UnionReader) (*pe.FileHeader, uint16, error) {
|
||||
var dosHeader peDosHeader
|
||||
if err := binary.Read(file, binary.LittleEndian, &dosHeader); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading DOS header: %w", err)
|
||||
}
|
||||
if string(dosHeader.Magic[:]) != "MZ" {
|
||||
return nil, 0, fmt.Errorf("invalid DOS header magic")
|
||||
}
|
||||
|
||||
peOffset := int64(dosHeader.AddressOfNewEXEHeader)
|
||||
if _, err := file.Seek(peOffset, io.SeekStart); err != nil {
|
||||
return nil, 0, fmt.Errorf("error seeking to PE header: %w", err)
|
||||
}
|
||||
|
||||
var signature [4]byte
|
||||
if err := binary.Read(file, binary.LittleEndian, &signature); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading PE signature: %w", err)
|
||||
}
|
||||
if !bytes.Equal(signature[:], []byte("PE\x00\x00")) {
|
||||
return nil, 0, fmt.Errorf("invalid PE signature")
|
||||
}
|
||||
|
||||
var fileHeader pe.FileHeader
|
||||
if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading file header: %w", err)
|
||||
}
|
||||
|
||||
var magic uint16
|
||||
if err := binary.Read(file, binary.LittleEndian, &magic); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading optional header magic: %w", err)
|
||||
}
|
||||
|
||||
// seek back to before reading magic (since that value is in the header)
|
||||
if _, err := file.Seek(-2, io.SeekCurrent); err != nil {
|
||||
return nil, 0, fmt.Errorf("error seeking back to before reading magic: %w", err)
|
||||
}
|
||||
|
||||
return &fileHeader, magic, nil
|
||||
}
|
||||
|
||||
// parseSectionHeaders reads the section headers from the PE file and extracts the virtual addresses + section size
|
||||
// information for the sections of interest. Additionally, all section headers are returned to aid in downstream processing.
|
||||
func parseSectionHeaders(file unionreader.UnionReader, magic uint16, numberOfSections uint16) (map[int]*extractedSection, []pe.SectionHeader32, error) {
|
||||
soi := make(map[int]*extractedSection)
|
||||
switch magic {
|
||||
case 0x10B: // PE32
|
||||
var optHeader pe.OptionalHeader32
|
||||
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading optional header (PE32): %w", err)
|
||||
}
|
||||
|
||||
for _, i := range imageDirectoryEntryIndexes {
|
||||
sectionHeader := optHeader.DataDirectory[i]
|
||||
if sectionHeader.Size == 0 {
|
||||
continue
|
||||
}
|
||||
soi[i] = &extractedSection{
|
||||
RVA: sectionHeader.VirtualAddress,
|
||||
Size: sectionHeader.Size,
|
||||
}
|
||||
}
|
||||
case 0x20B: // PE32+ (64 bit)
|
||||
var optHeader pe.OptionalHeader64
|
||||
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading optional header (PE32+): %w", err)
|
||||
}
|
||||
|
||||
for _, i := range imageDirectoryEntryIndexes {
|
||||
sectionHeader := optHeader.DataDirectory[i]
|
||||
if sectionHeader.Size == 0 {
|
||||
continue
|
||||
}
|
||||
soi[i] = &extractedSection{
|
||||
RVA: sectionHeader.VirtualAddress,
|
||||
Size: sectionHeader.Size,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown optional header magic: 0x%x", magic)
|
||||
}
|
||||
|
||||
// read section headers
|
||||
headers := make([]pe.SectionHeader32, numberOfSections)
|
||||
for i := 0; i < int(numberOfSections); i++ {
|
||||
if err := binary.Read(file, binary.LittleEndian, &headers[i]); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading section header: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return soi, headers, nil
|
||||
}
|
||||
|
||||
// parseCLR extracts the CLR (common language runtime) version information from the COM descriptor and makes
|
||||
// present/not-present determination based on the presence of CLR resource names.
|
||||
func parseCLR(sec *extractedSection, resourceNames *strset.Set) (*clrEvidence, error) {
|
||||
hasCLRDebugResourceNames := resourceNames.HasAny("CLRDEBUGINFO")
|
||||
if sec == nil || sec.Reader == nil {
|
||||
return &clrEvidence{
|
||||
HasClrResourceNames: hasCLRDebugResourceNames,
|
||||
}, nil
|
||||
}
|
||||
|
||||
reader := sec.Reader
|
||||
var c peImageCore20
|
||||
if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
|
||||
return nil, fmt.Errorf("error reading CLR header: %w", err)
|
||||
}
|
||||
|
||||
return &clrEvidence{
|
||||
HasClrResourceNames: hasCLRDebugResourceNames,
|
||||
MajorVersion: c.MajorRuntimeVersion,
|
||||
MinorVersion: c.MinorRuntimeVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// rvaToFileOffset is a helper function to convert RVA to file offset using section headers
|
||||
func rvaToFileOffset(rva uint32, sections []pe.SectionHeader32) (uint32, error) {
|
||||
for _, section := range sections {
|
||||
if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize {
|
||||
return section.PointerToRawData + (rva - section.VirtualAddress), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("RVA 0x%x not found in any section", rva)
|
||||
}
|
||||
|
||||
// readDataFromRVA will read data from a specific RVA in the PE file
|
||||
func readDataFromRVA(file io.ReadSeeker, rva, size uint32, sections []pe.SectionHeader32) (*bytes.Reader, error) {
|
||||
if size == 0 {
|
||||
return nil, fmt.Errorf("zero size specified")
|
||||
}
|
||||
|
||||
offset, err := rvaToFileOffset(rva, sections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := file.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("error seeking to data: %w", err)
|
||||
}
|
||||
|
||||
data := make([]byte, size)
|
||||
if _, err := io.ReadFull(file, data); err != nil {
|
||||
return nil, fmt.Errorf("error reading data: %w", err)
|
||||
}
|
||||
|
||||
return bytes.NewReader(data), nil
|
||||
}
|
||||
|
||||
// parseResourceDirectory recursively parses a PE resource directory. This takes a relative virtual address (offset of
|
||||
// a piece of data or code relative to the base address), the size of the resource directory, the set of RVAs already
|
||||
// parsed, and the map to populate discovered version resource values.
|
||||
//
|
||||
// .rsrc Section
|
||||
// +------------------------------+
|
||||
// | Resource Directory Table |
|
||||
// +------------------------------+
|
||||
// | Resource Directory Entries |
|
||||
// | +------------------------+ |
|
||||
// | | Subdirectory or Data | |
|
||||
// | +------------------------+ |
|
||||
// +------------------------------+
|
||||
// | Resource Data Entries |
|
||||
// | +------------------------+ |
|
||||
// | | Resource Data | |
|
||||
// | +------------------------+ |
|
||||
// +------------------------------+
|
||||
// | Actual Resource Data |
|
||||
// +------------------------------+
|
||||
//
|
||||
// sources:
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section
|
||||
// - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)#pe-file-resources
|
||||
func parseResourceDirectory(sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
|
||||
if sec == nil || sec.Size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sec.Reader == nil {
|
||||
return errors.New("resource section not found")
|
||||
}
|
||||
|
||||
baseRVA := sec.BaseRVA
|
||||
if baseRVA == 0 {
|
||||
baseRVA = sec.RVA
|
||||
}
|
||||
|
||||
offset := int64(sec.RVA - baseRVA)
|
||||
if _, err := sec.Reader.Seek(offset, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to directory offset: %w", err)
|
||||
}
|
||||
|
||||
var directoryHeader peImageResourceDirectory
|
||||
if err := readIntoStruct(sec.Reader, &directoryHeader); err != nil {
|
||||
return fmt.Errorf("error reading directory header: %w", err)
|
||||
}
|
||||
|
||||
numEntries := int(directoryHeader.NumberOfNamedEntries + directoryHeader.NumberOfIDEntries)
|
||||
switch {
|
||||
case numEntries > peMaxAllowedDirectoryEntries:
|
||||
return fmt.Errorf("too many entries in resource directory: %d", numEntries)
|
||||
case numEntries == 0:
|
||||
return fmt.Errorf("no entries in resource directory")
|
||||
case numEntries < 0:
|
||||
return fmt.Errorf("invalid number of entries in resource directory: %d", numEntries)
|
||||
}
|
||||
|
||||
for i := 0; i < numEntries; i++ {
|
||||
var entry peImageResourceDirectoryEntry
|
||||
|
||||
entryOffset := offset + int64(binary.Size(directoryHeader)) + int64(i*binary.Size(entry))
|
||||
if _, err := sec.Reader.Seek(entryOffset, io.SeekStart); err != nil {
|
||||
log.Tracef("error seeking to PE entry offset: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := readIntoStruct(sec.Reader, &entry); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := processResourceEntry(entry, baseRVA, sec, dirs, fields, names); err != nil {
|
||||
log.Tracef("error processing resource entry: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processResourceEntry(entry peImageResourceDirectoryEntry, baseRVA uint32, sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
|
||||
// if the high bit is set, this is a directory entry, otherwise it is a data entry
|
||||
isDirectory := entry.OffsetToData&0x80000000 != 0
|
||||
|
||||
// note: the offset is relative to the beginning of the resource section, not an RVA
|
||||
entryOffsetToData := entry.OffsetToData & 0x7FFFFFFF
|
||||
|
||||
nameIsString := entry.Name&0x80000000 != 0
|
||||
nameOffset := entry.Name & 0x7FFFFFFF
|
||||
|
||||
// read the string name of the resource directory
|
||||
if nameIsString {
|
||||
currentPos, err := sec.Reader.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting current reader position: %w", err)
|
||||
}
|
||||
|
||||
if _, err := sec.Reader.Seek(int64(nameOffset), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error restoring reader position: %w", err)
|
||||
}
|
||||
|
||||
name, err := readUTF16WithLength(sec.Reader)
|
||||
if err == nil {
|
||||
names.Add(name)
|
||||
}
|
||||
|
||||
if _, err := sec.Reader.Seek(currentPos, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error restoring reader position: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if isDirectory {
|
||||
subRVA := baseRVA + entryOffsetToData
|
||||
if dirs.Has(subRVA) {
|
||||
// some malware uses recursive PE references to evade analysis
|
||||
return fmt.Errorf("recursive PE reference detected; skipping directory at baseRVA=0x%x subRVA=0x%x", baseRVA, subRVA)
|
||||
}
|
||||
|
||||
dirs.Add(subRVA)
|
||||
err := parseResourceDirectory(
|
||||
&extractedSection{
|
||||
RVA: subRVA,
|
||||
BaseRVA: baseRVA,
|
||||
Size: sec.Size - (sec.RVA - baseRVA),
|
||||
Reader: sec.Reader,
|
||||
},
|
||||
dirs, fields, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return parseResourceDataEntry(sec.Reader, baseRVA, baseRVA+entryOffsetToData, sec.Size, fields)
|
||||
}
|
||||
|
||||
func parseResourceDataEntry(reader *bytes.Reader, baseRVA, rva, remainingSize uint32, fields map[string]string) error {
|
||||
var dataEntry peImageResourceDataEntry
|
||||
offset := int64(rva - baseRVA)
|
||||
|
||||
if _, err := reader.Seek(offset, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to data entry offset: %w", err)
|
||||
}
|
||||
|
||||
if err := readIntoStruct(reader, &dataEntry); err != nil {
|
||||
return fmt.Errorf("error reading resource data entry: %w", err)
|
||||
}
|
||||
|
||||
if remainingSize < dataEntry.Size {
|
||||
return fmt.Errorf("resource data entry size exceeds remaining size")
|
||||
}
|
||||
|
||||
data := make([]byte, dataEntry.Size)
|
||||
if _, err := reader.Seek(int64(dataEntry.OffsetToData-baseRVA), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to resource data: %w", err)
|
||||
}
|
||||
|
||||
if _, err := reader.Read(data); err != nil {
|
||||
return fmt.Errorf("error reading resource data: %w", err)
|
||||
}
|
||||
|
||||
return parseVersionResourceSection(bytes.NewReader(data), fields)
|
||||
}
|
||||
|
||||
// parseVersionResourceSection parses a PE version resource section from within a resource directory.
|
||||
//
|
||||
// "The main structure in a version resource is the VS_FIXEDFILEINFO structure. Additional structures include the
|
||||
// VarFileInfo structure to store language information data, and StringFileInfo for user-defined string information.
|
||||
// All strings in a version resource are in Unicode format. Each block of information is aligned on a DWORD boundary."
|
||||
//
|
||||
// "VS_VERSIONINFO" (utf16)
|
||||
// +---------------------------------------------------+
|
||||
// | wLength (2 bytes) |
|
||||
// | wValueLength (2 bytes) |
|
||||
// | wType (2 bytes) |
|
||||
// | szKey ("VS_VERSION_INFO") (utf16) |
|
||||
// | Padding (to DWORD) |
|
||||
// +---------------------------------------------------+
|
||||
// | VS_FIXEDFILEINFO (52 bytes) |
|
||||
// +---------------------------------------------------+
|
||||
// | "StringFileInfo" (utf16) |
|
||||
// +---------------------------------------------------+
|
||||
// | wLength (2 bytes) |
|
||||
// | wValueLength (2 bytes) |
|
||||
// | wType (2 bytes) |
|
||||
// | szKey ("StringFileInfo") (utf16) |
|
||||
// | Padding (to DWORD) |
|
||||
// | StringTable |
|
||||
// | +--------------------------------------------+ |
|
||||
// | | wLength (2 bytes) | |
|
||||
// | | wValueLength (2 bytes) | |
|
||||
// | | wType (2 bytes) | |
|
||||
// | | szKey ("040904b0") | |
|
||||
// | | Padding (to DWORD) | |
|
||||
// | | String | |
|
||||
// | | +--------------------------------------+ | |
|
||||
// | | | wLength (2 bytes) | | |
|
||||
// | | | wValueLength (2 bytes) | | |
|
||||
// | | | wType (2 bytes) | | |
|
||||
// | | | szKey ("FileVersion") | | |
|
||||
// | | | Padding (to DWORD) | | |
|
||||
// | | | szValue ("15.00.0913.015") | | |
|
||||
// | | | Padding (to DWORD) | | |
|
||||
// | +--------------------------------------------+ |
|
||||
// +---------------------------------------------------+
|
||||
// | VarFileInfo (utf16) |
|
||||
// +---------------------------------------------------+
|
||||
// | (skip!) |
|
||||
// +---------------------------------------------------+
|
||||
//
|
||||
// sources:
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable
|
||||
func parseVersionResourceSection(reader *bytes.Reader, fields map[string]string) error {
|
||||
offset := 0
|
||||
|
||||
var info peVsVersionInfo
|
||||
if szKey, err := readIntoStructAndSzKey(reader, &info, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE version info: %v", err)
|
||||
} else if szKey != "VS_VERSION_INFO" {
|
||||
// this is a resource section, but not the version resources
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := alignAndSeek(reader, &offset); err != nil {
|
||||
return fmt.Errorf("error aligning past PE version info: %w", err)
|
||||
}
|
||||
|
||||
var fixedFileInfo peVsFixedFileInfo
|
||||
if err := readIntoStruct(reader, &fixedFileInfo, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE FixedFileInfo: %v", err)
|
||||
}
|
||||
|
||||
for reader.Len() > 0 {
|
||||
if err := alignAndSeek(reader, &offset); err != nil {
|
||||
return fmt.Errorf("error seeking to PE StringFileInfo: %w", err)
|
||||
}
|
||||
|
||||
var sfiHeader peStringFileInfo
|
||||
if szKey, err := readIntoStructAndSzKey(reader, &sfiHeader, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE string file info header: %v", err)
|
||||
} else if szKey != "StringFileInfo" {
|
||||
// we only care about extracting strings from any string tables, skip this
|
||||
offset += int(sfiHeader.ValueLength)
|
||||
continue
|
||||
}
|
||||
|
||||
var stOffset int
|
||||
|
||||
// note: the szKey for the prStringTable is the language
|
||||
var stHeader peStringTable
|
||||
if _, err := readIntoStructAndSzKey(reader, &stHeader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error reading PE string table header: %v", err)
|
||||
}
|
||||
|
||||
for stOffset < int(stHeader.Length) {
|
||||
var stringHeader peString
|
||||
if err := readIntoStruct(reader, &stringHeader, &offset, &stOffset); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
key := readUTF16(reader, &offset, &stOffset)
|
||||
|
||||
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error aligning to next PE string table value: %w", err)
|
||||
}
|
||||
|
||||
var value string
|
||||
if stringHeader.ValueLength > 0 {
|
||||
value = readUTF16(reader, &offset, &stOffset)
|
||||
}
|
||||
|
||||
fields[key] = value
|
||||
|
||||
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error aligning to next PE string table key: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fields["FileVersion"] == "" {
|
||||
// we can derive the file version from the fixed file info if it is not already specified as a string entry... neat!
|
||||
fields["FileVersion"] = fmt.Sprintf("%d.%d.%d.%d",
|
||||
fixedFileInfo.FileVersionMS>>16, fixedFileInfo.FileVersionMS&0xFFFF,
|
||||
fixedFileInfo.FileVersionLS>>16, fixedFileInfo.FileVersionLS&0xFFFF)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readIntoStructAndSzKey reads a struct from the reader and updates the offsets if provided, returning the szKey value.
|
||||
// This is only useful in the context of the resource directory parsing in narrow cases (this is invalid to use outside of that context).
|
||||
func readIntoStructAndSzKey[T any](reader *bytes.Reader, data *T, offsets ...*int) (string, error) {
|
||||
if err := readIntoStruct(reader, data, offsets...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return readUTF16(reader, offsets...), nil
|
||||
}
|
||||
|
||||
// readIntoStruct reads a struct from the reader and updates the offsets if provided.
|
||||
func readIntoStruct[T any](reader io.Reader, data *T, offsets ...*int) error {
|
||||
if err := binary.Read(reader, binary.LittleEndian, data); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range offsets {
|
||||
*offsets[i] += binary.Size(*data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// alignAndSeek aligns the reader to the next DWORD boundary and seeks to the new offset (updating any provided trackOffsets).
|
||||
func alignAndSeek(reader io.Seeker, offset *int, trackOffsets ...*int) error {
|
||||
ogOffset := *offset
|
||||
*offset = alignToDWORD(*offset)
|
||||
diff := *offset - ogOffset
|
||||
for i := range trackOffsets {
|
||||
*trackOffsets[i] += diff
|
||||
}
|
||||
_, err := reader.Seek(int64(*offset), io.SeekStart)
|
||||
return err
|
||||
}
|
||||
|
||||
// alignToDWORD aligns the offset to the next DWORD boundary (4 byte boundary)
|
||||
func alignToDWORD(offset int) int {
|
||||
return (offset + 3) & ^3
|
||||
}
|
||||
|
||||
// readUTF16 is a helper function to read a null-terminated UTF16 string
|
||||
func readUTF16(reader *bytes.Reader, offsets ...*int) string {
|
||||
startPos, err := reader.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var result []rune
|
||||
for {
|
||||
var char uint16
|
||||
err := binary.Read(reader, binary.LittleEndian, &char)
|
||||
if err != nil || char == 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, rune(char))
|
||||
}
|
||||
|
||||
// calculate how many bytes we've actually read (including null terminator)
|
||||
endPos, _ := reader.Seek(0, io.SeekCurrent)
|
||||
bytesRead := int(endPos - startPos)
|
||||
|
||||
for i := range offsets {
|
||||
*offsets[i] += bytesRead
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// readUTF16WithLength reads a length-prefixed UTF-16 string from reader.
|
||||
// The first 2 bytes represent the number of UTF-16 code units.
|
||||
func readUTF16WithLength(reader *bytes.Reader) (string, error) {
|
||||
var length uint16
|
||||
if err := binary.Read(reader, binary.LittleEndian, &length); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if length == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// read length UTF-16 code units.
|
||||
codes := make([]uint16, length)
|
||||
if err := binary.Read(reader, binary.LittleEndian, &codes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(utf16.Decode(codes)), nil
|
||||
}
|
||||
|
||||
+51
-59
@@ -1,4 +1,4 @@
|
||||
package binary
|
||||
package binutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -34,7 +34,7 @@ type Classifier struct {
|
||||
// location. If the matcher returns a package, the file will be considered a candidate.
|
||||
EvidenceMatcher EvidenceMatcher `json:"-"`
|
||||
|
||||
// Information below is used to specify the Package information when returned
|
||||
// The information below is used to specify the Package information when returned
|
||||
|
||||
// Package is the name to use for the package
|
||||
Package string `json:"package"`
|
||||
@@ -72,16 +72,16 @@ func (cfg Classifier) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
// EvidenceMatcher is a function called to catalog Packages that match some sort of evidence
|
||||
type EvidenceMatcher func(classifier Classifier, context matcherContext) ([]pkg.Package, error)
|
||||
type EvidenceMatcher func(classifier Classifier, context MatcherContext) ([]pkg.Package, error)
|
||||
|
||||
type matcherContext struct {
|
||||
resolver file.Resolver
|
||||
location file.Location
|
||||
getReader func(resolver matcherContext) (unionreader.UnionReader, error)
|
||||
type MatcherContext struct {
|
||||
Resolver file.Resolver
|
||||
Location file.Location
|
||||
GetReader func(resolver MatcherContext) (unionreader.UnionReader, error)
|
||||
}
|
||||
|
||||
func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
|
||||
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
|
||||
func EvidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
|
||||
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
|
||||
for _, matcher := range matchers {
|
||||
match, err := matcher(classifier, context)
|
||||
if err != nil {
|
||||
@@ -95,14 +95,26 @@ func evidenceMatchers(matchers ...EvidenceMatcher) EvidenceMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher {
|
||||
type ContextualEvidenceMatchers struct {
|
||||
CatalogerName string
|
||||
}
|
||||
|
||||
func (c ContextualEvidenceMatchers) FileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) EvidenceMatcher {
|
||||
return FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, c.CatalogerName)
|
||||
}
|
||||
|
||||
func (c ContextualEvidenceMatchers) FileContentsVersionMatcher(pattern string) EvidenceMatcher {
|
||||
return FileContentsVersionMatcher(pattern, c.CatalogerName)
|
||||
}
|
||||
|
||||
func FileNameTemplateVersionMatcher(fileNamePattern, contentTemplate, catalogerName string) EvidenceMatcher {
|
||||
pat := regexp.MustCompile(fileNamePattern)
|
||||
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
|
||||
if !pat.MatchString(context.location.RealPath) {
|
||||
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
|
||||
if !pat.MatchString(context.Location.RealPath) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, context.location.RealPath)
|
||||
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, context.Location.RealPath)
|
||||
|
||||
// versions like 3.5 should not match any character, but explicit dot
|
||||
for k, v := range filepathNamedGroupValues {
|
||||
@@ -135,7 +147,7 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
|
||||
return nil, fmt.Errorf("unable to match version: %w", err)
|
||||
}
|
||||
|
||||
p := newClassifierPackage(classifier, context.location, matchMetadata)
|
||||
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -144,9 +156,9 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
|
||||
}
|
||||
}
|
||||
|
||||
func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
|
||||
func FileContentsVersionMatcher(pattern, catalogerName string) EvidenceMatcher {
|
||||
pat := regexp.MustCompile(pattern)
|
||||
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
|
||||
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
|
||||
contents, err := getReader(context)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
|
||||
@@ -173,7 +185,7 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
p := newClassifierPackage(classifier, context.location, matchMetadata)
|
||||
p := NewClassifierPackage(classifier, context.Location, matchMetadata, catalogerName)
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -182,14 +194,14 @@ func FileContentsVersionMatcher(pattern string) EvidenceMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// matchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
|
||||
// MatchExcluding tests the provided regular expressions against the file, and if matched, DOES NOT return
|
||||
// anything that the matcher would otherwise return
|
||||
func matchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
|
||||
func MatchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string) EvidenceMatcher {
|
||||
var nonMatchPatterns []*regexp.Regexp
|
||||
for _, p := range contentPatternsToExclude {
|
||||
nonMatchPatterns = append(nonMatchPatterns, regexp.MustCompile(p))
|
||||
}
|
||||
return func(classifier Classifier, context matcherContext) ([]pkg.Package, error) {
|
||||
return func(classifier Classifier, context MatcherContext) ([]pkg.Package, error) {
|
||||
contents, err := getReader(context)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
|
||||
@@ -205,9 +217,9 @@ func matchExcluding(matcher EvidenceMatcher, contentPatternsToExclude ...string)
|
||||
}
|
||||
}
|
||||
|
||||
func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
|
||||
func SharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher EvidenceMatcher) EvidenceMatcher {
|
||||
pat := regexp.MustCompile(sharedLibraryPattern)
|
||||
return func(classifier Classifier, context matcherContext) (packages []pkg.Package, _ error) {
|
||||
return func(classifier Classifier, context MatcherContext) (packages []pkg.Package, _ error) {
|
||||
libs, err := sharedLibraries(context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -217,24 +229,24 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
|
||||
continue
|
||||
}
|
||||
|
||||
locations, err := context.resolver.FilesByGlob("**/" + lib)
|
||||
locations, err := context.Resolver.FilesByGlob("**/" + lib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, libraryLocation := range locations {
|
||||
newResolver := matcherContext{
|
||||
resolver: context.resolver,
|
||||
location: libraryLocation,
|
||||
getReader: context.getReader,
|
||||
newResolver := MatcherContext{
|
||||
Resolver: context.Resolver,
|
||||
Location: libraryLocation,
|
||||
GetReader: context.GetReader,
|
||||
}
|
||||
newResolver.location = libraryLocation
|
||||
newResolver.Location = libraryLocation
|
||||
pkgs, err := sharedLibraryMatcher(classifier, newResolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
// set the source binary as the first location
|
||||
locationSet := file.NewLocationSet(context.location)
|
||||
locationSet := file.NewLocationSet(context.Location)
|
||||
locationSet.Add(p.Locations.ToSlice()...)
|
||||
p.Locations = locationSet
|
||||
meta, _ := p.Metadata.(pkg.BinarySignature)
|
||||
@@ -242,7 +254,7 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
|
||||
Matches: append([]pkg.ClassifierMatch{
|
||||
{
|
||||
Classifier: classifier.Class,
|
||||
Location: context.location,
|
||||
Location: context.Location,
|
||||
},
|
||||
}, meta.Matches...),
|
||||
}
|
||||
@@ -254,19 +266,11 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher Evide
|
||||
}
|
||||
}
|
||||
|
||||
func mustPURL(purl string) packageurl.PackageURL {
|
||||
p, err := packageurl.FromString(purl)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid PURL: %s", p))
|
||||
func getReader(context MatcherContext) (unionreader.UnionReader, error) {
|
||||
if context.GetReader != nil {
|
||||
return context.GetReader(context)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func getReader(context matcherContext) (unionreader.UnionReader, error) {
|
||||
if context.getReader != nil {
|
||||
return context.getReader(context)
|
||||
}
|
||||
reader, err := context.resolver.FileContentsByLocation(context.location) //nolint:gocritic
|
||||
reader, err := context.Resolver.FileContentsByLocation(context.Location) //nolint:gocritic
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -274,32 +278,20 @@ func getReader(context matcherContext) (unionreader.UnionReader, error) {
|
||||
return unionreader.GetUnionReader(reader)
|
||||
}
|
||||
|
||||
// singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
|
||||
// cpe string cannot be parsed into valid CPE Attributes
|
||||
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
|
||||
src := cpe.GeneratedSource
|
||||
if len(source) > 0 {
|
||||
src = source[0]
|
||||
}
|
||||
return []cpe.CPE{
|
||||
cpe.Must(cpeString, src),
|
||||
}
|
||||
}
|
||||
|
||||
// sharedLibraries returns a list of all shared libraries found within a binary, currently
|
||||
// supporting: elf, macho, and windows pe
|
||||
func sharedLibraries(context matcherContext) ([]string, error) {
|
||||
func sharedLibraries(context MatcherContext) ([]string, error) {
|
||||
contents, err := getReader(context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(contents, context.location.RealPath)
|
||||
defer internal.CloseAndLogError(contents, context.Location.RealPath)
|
||||
|
||||
e, _ := elf.NewFile(contents)
|
||||
if e != nil {
|
||||
symbols, err := e.ImportedLibraries()
|
||||
if err != nil {
|
||||
log.Debugf("unable to read elf binary at: %s -- %s", context.location.RealPath, err)
|
||||
log.Debugf("unable to read elf binary at: %s -- %s", context.Location.RealPath, err)
|
||||
}
|
||||
return symbols, nil
|
||||
}
|
||||
@@ -311,7 +303,7 @@ func sharedLibraries(context matcherContext) ([]string, error) {
|
||||
if m != nil {
|
||||
symbols, err := m.ImportedLibraries()
|
||||
if err != nil {
|
||||
log.Debugf("unable to read macho binary at: %s -- %s", context.location.RealPath, err)
|
||||
log.Debugf("unable to read macho binary at: %s -- %s", context.Location.RealPath, err)
|
||||
}
|
||||
return symbols, nil
|
||||
}
|
||||
@@ -323,7 +315,7 @@ func sharedLibraries(context matcherContext) ([]string, error) {
|
||||
if p != nil {
|
||||
symbols, err := p.ImportedLibraries()
|
||||
if err != nil {
|
||||
log.Debugf("unable to read pe binary at: %s -- %s", context.location.RealPath, err)
|
||||
log.Debugf("unable to read pe binary at: %s -- %s", context.Location.RealPath, err)
|
||||
}
|
||||
return symbols, nil
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package binary
|
||||
package binutils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
var emptyPURL = packageurl.PackageURL{}
|
||||
|
||||
func newClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string) *pkg.Package {
|
||||
func NewClassifierPackage(classifier Classifier, location file.Location, matchMetadata map[string]string, catalogerName string) *pkg.Package {
|
||||
version, ok := matchMetadata["version"]
|
||||
if !ok {
|
||||
return nil
|
||||
+11
-11
@@ -1,4 +1,4 @@
|
||||
package binary
|
||||
package binutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -27,7 +27,7 @@ func Test_ClassifierCPEs(t *testing.T) {
|
||||
classifier: Classifier{
|
||||
Package: "some-app",
|
||||
FileGlob: "**/version.txt",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
|
||||
CPEs: []cpe.CPE{},
|
||||
},
|
||||
cpes: nil,
|
||||
@@ -38,7 +38,7 @@ func Test_ClassifierCPEs(t *testing.T) {
|
||||
classifier: Classifier{
|
||||
Package: "some-app",
|
||||
FileGlob: "**/version.txt",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
|
||||
},
|
||||
@@ -53,7 +53,7 @@ func Test_ClassifierCPEs(t *testing.T) {
|
||||
classifier: Classifier{
|
||||
Package: "some-app",
|
||||
FileGlob: "**/version.txt",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`),
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)my-verison:(?P<version>[0-9.]+)`, "cataloger-name"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
|
||||
cpe.Must("cpe:2.3:a:some:apps:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
|
||||
@@ -70,7 +70,7 @@ func Test_ClassifierCPEs(t *testing.T) {
|
||||
classifier: Classifier{
|
||||
Package: "some-app",
|
||||
FileGlob: "**/version-parts.txt",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`),
|
||||
EvidenceMatcher: FileContentsVersionMatcher(`(?m)\x00(?P<major>[0-9.]+)\x00(?P<minor>[0-9.]+)\x00(?P<patch>[0-9.]+)\x00`, "cataloger-name"),
|
||||
CPEs: []cpe.CPE{},
|
||||
},
|
||||
cpes: nil,
|
||||
@@ -84,7 +84,7 @@ func Test_ClassifierCPEs(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ls, 1)
|
||||
|
||||
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, matcherContext{resolver: resolver, location: ls[0]})
|
||||
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, MatcherContext{Resolver: resolver, Location: ls[0]})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, pkgs, 1)
|
||||
@@ -113,7 +113,7 @@ func TestClassifier_MarshalJSON(t *testing.T) {
|
||||
classifier: Classifier{
|
||||
Class: "class",
|
||||
FileGlob: "glob",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(".thing"),
|
||||
EvidenceMatcher: FileContentsVersionMatcher(".thing", "cataloger-name"),
|
||||
Package: "pkg",
|
||||
PURL: packageurl.PackageURL{
|
||||
Type: "type",
|
||||
@@ -165,12 +165,12 @@ func TestFileContentsVersionMatcher(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockGetContent := func(context matcherContext) (unionreader.UnionReader, error) {
|
||||
mockGetContent := func(context MatcherContext) (unionreader.UnionReader, error) {
|
||||
return unionreader.GetUnionReader(io.NopCloser(bytes.NewBufferString(tt.data)))
|
||||
}
|
||||
fn := FileContentsVersionMatcher(tt.pattern)
|
||||
p, err := fn(Classifier{}, matcherContext{
|
||||
getReader: mockGetContent,
|
||||
fn := FileContentsVersionMatcher(tt.pattern, "cataloger-name")
|
||||
p, err := fn(Classifier{}, MatcherContext{
|
||||
GetReader: mockGetContent,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -486,7 +486,6 @@ var defaultCandidateAdditions = buildCandidateLookup(
|
||||
candidateKey{PkgName: "dnsmasq", Vendor: "dnsmasq"},
|
||||
candidateAddition{AdditionalVendors: []string{"thekelleys"}},
|
||||
},
|
||||
//
|
||||
// Binary packages
|
||||
{
|
||||
pkg.BinaryPkg,
|
||||
@@ -630,7 +629,38 @@ var defaultCandidateRemovals = buildCandidateRemovalLookup(
|
||||
candidateKey{PkgName: "grpc"},
|
||||
candidateRemovals{ProductsToRemove: []string{"grpc"}},
|
||||
},
|
||||
// PHP Packages
|
||||
// Rust packages
|
||||
{
|
||||
pkg.RustPkg,
|
||||
candidateKey{PkgName: "hyper"},
|
||||
// Avoid matching CVE-2024-23741
|
||||
candidateRemovals{VendorsToRemove: []string{"vercel"}},
|
||||
},
|
||||
{
|
||||
pkg.RustPkg,
|
||||
candidateKey{PkgName: "opentelemetry"},
|
||||
// Avoid matching CVE-2023-45142
|
||||
candidateRemovals{ProductsToRemove: []string{"opentelemetry"}},
|
||||
},
|
||||
{
|
||||
pkg.RustPkg,
|
||||
candidateKey{PkgName: "prometheus"},
|
||||
// Avoid matching CVE-2019-3826
|
||||
candidateRemovals{VendorsToRemove: []string{"prometheus"}},
|
||||
},
|
||||
{
|
||||
pkg.RustPkg,
|
||||
candidateKey{PkgName: "phf"},
|
||||
// Avoid matching CVE-2000-1186
|
||||
candidateRemovals{VendorsToRemove: []string{"phf"}},
|
||||
},
|
||||
{
|
||||
pkg.RustPkg,
|
||||
candidateKey{PkgName: "redis"},
|
||||
// Avoid matching CVE-2022-24735
|
||||
candidateRemovals{VendorsToRemove: []string{"redis"}},
|
||||
},
|
||||
// PHP packages
|
||||
{
|
||||
pkg.PhpPearPkg,
|
||||
candidateKey{PkgName: "redis"},
|
||||
|
||||
@@ -3848,6 +3848,9 @@
|
||||
"localhost-now": [
|
||||
"cpe:2.3:a:localhost-now_project:localhost-now:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"lockfile-lint-api": [
|
||||
"cpe:2.3:a:lirantal:lockfile-lint-api:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"locutus": [
|
||||
"cpe:2.3:a:locutus:locutus:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -7072,6 +7075,9 @@
|
||||
"versionize": [
|
||||
"cpe:2.3:a:versionize_project:versionize:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"wasmi": [
|
||||
"cpe:2.3:a:wasmi-labs:wasmi:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"wasmtime": [
|
||||
"cpe:2.3:a:bytecodealliance:wasmtime:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -7319,6 +7325,9 @@
|
||||
"aco-product-labels-for-woocommerce": [
|
||||
"cpe:2.3:a:acowebs:product_labels_for_woocommerce_\\(sale_badges\\):*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"aco-woo-dynamic-pricing": [
|
||||
"cpe:2.3:a:acowebs:dynamic_pricing_with_discount_rules_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"actionwear-products-sync": [
|
||||
"cpe:2.3:a:marcoingraiti:actionwear_products_sync:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -7473,6 +7482,10 @@
|
||||
"admin-side-data-storage-for-contact-form-7": [
|
||||
"cpe:2.3:a:zestard:admin_side_data_storage_for_contact_form_7:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"admin-site-enhancements": [
|
||||
"cpe:2.3:a:wpase:admin_and_site_enhancements:*:*:*:*:free:wordpress:*:*",
|
||||
"cpe:2.3:a:wpase:admin_and_site_enhancements:*:*:*:*:pro:wordpress:*:*"
|
||||
],
|
||||
"adminer": [
|
||||
"cpe:2.3:a:adminer_login_project:adminer_login:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -7539,6 +7552,7 @@
|
||||
"advanced-custom-fields": [
|
||||
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:-:wordpress:*:*",
|
||||
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:free:wordpress:*:*",
|
||||
"cpe:2.3:a:advancedcustomfields:advanced_custom_fields:*:*:*:*:pro:wordpress:*:*"
|
||||
],
|
||||
"advanced-database-cleaner": [
|
||||
@@ -7668,6 +7682,9 @@
|
||||
"ajax-bootmodal-login": [
|
||||
"cpe:2.3:a:ajax_bootmodal_login_project:ajax_bootmodal_login:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ajax-filter-posts": [
|
||||
"cpe:2.3:a:addonmaster:post_grid_master:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ajax-load-more": [
|
||||
"cpe:2.3:a:connekthq:ajax_load_more:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -7873,6 +7890,9 @@
|
||||
"animate-it": [
|
||||
"cpe:2.3:a:eleopard:animate_it\\!:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"animated-al-list": [
|
||||
"cpe:2.3:a:alexdtn:animated_al_list:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"animated-counters": [
|
||||
"cpe:2.3:a:eralion:animated_counters:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8018,6 +8038,9 @@
|
||||
"arprice-responsive-pricing-table": [
|
||||
"cpe:2.3:a:reputeinfosystems:arprice_lite:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"artibot": [
|
||||
"cpe:2.3:a:artibot:artibot:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"article-directory": [
|
||||
"cpe:2.3:a:article_directory_project:article_directory:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8109,6 +8132,9 @@
|
||||
"auto-hyperlink-urls": [
|
||||
"cpe:2.3:a:auto-hyperlink_urls_project:auto-hyperlink_urls:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"auto-iframe": [
|
||||
"cpe:2.3:a:toolstack:auto_iframe:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"auto-limit-posts-reloaded": [
|
||||
"cpe:2.3:a:thefreewindows:auto_limit_posts_reloaded:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8201,6 +8227,9 @@
|
||||
"awesome-weather": [
|
||||
"cpe:2.3:a:awesome_weather_widget_project:awesome_weather_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"awin-advertiser-tracking": [
|
||||
"cpe:2.3:a:awin:awin_-_advertiser_tracking_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"awin-data-feed": [
|
||||
"cpe:2.3:a:awin:awin_data_feed:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8359,6 +8388,9 @@
|
||||
"cpe:2.3:a:web-x:be-popia-compliant:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:web-x:be_popia_compliant:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"beaf-before-and-after-gallery": [
|
||||
"cpe:2.3:a:themefic:ultimate_before_after_image_slider_\\\u0026_gallery:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"beam-me-up-scotty": [
|
||||
"cpe:2.3:a:outtheboxthemes:beam_me_up_scotty:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8801,6 +8833,9 @@
|
||||
"button-block": [
|
||||
"cpe:2.3:a:bplugins:button_block:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"button-contact-vr": [
|
||||
"cpe:2.3:a:buttonizer:call_\\/_chat_\\/_contact_button:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"button-generation": [
|
||||
"cpe:2.3:a:wow-company:button_generator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8933,6 +8968,9 @@
|
||||
"canto": [
|
||||
"cpe:2.3:a:canto:canto:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"canvasflow": [
|
||||
"cpe:2.3:a:canvasflow:canvasflow:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"canvasio3d-light": [
|
||||
"cpe:2.3:a:virtuellwerk:canvasio3d_light:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9080,6 +9118,9 @@
|
||||
"cf7-store-to-db-lite": [
|
||||
"cpe:2.3:a:accesspressthemes:form_store_to_db:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cf7-submission-dom-tracking": [
|
||||
"cpe:2.3:a:apasionados:submission_dom_tracking_for_contact_form_7:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cf7-widget-elementor": [
|
||||
"cpe:2.3:a:voidcoders:void_contact_form_7_widget_for_elementor_page_builder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9126,7 +9167,7 @@
|
||||
"cpe:2.3:a:bluecoral:chat_bubble:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"chatbot": [
|
||||
"cpe:2.3:a:quantumcloud:ai_chatbot:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:quantumcloud:wpbot:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"chatbot-chatgpt": [
|
||||
"cpe:2.3:a:kognetics:kognetiks_chatbot:*:*:*:*:*:wordpress:*:*",
|
||||
@@ -9221,6 +9262,9 @@
|
||||
"clickdesk-live-support-chat-plugin": [
|
||||
"cpe:2.3:a:clickdesk:clickdesk:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"clickwhale": [
|
||||
"cpe:2.3:a:flowdee:clickwhale:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"clictracker": [
|
||||
"cpe:2.3:a:wp_clictracker_project:wp_clictracker:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9266,12 +9310,19 @@
|
||||
"cm-download-manager": [
|
||||
"cpe:2.3:a:cminds:cm_download_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cm-email-blacklist": [
|
||||
"cpe:2.3:a:cminds:cm_e-mail_blacklist:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cm-on-demand-search-and-replace": [
|
||||
"cpe:2.3:a:cminds:cm_on_demand_search_and_replace:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:cminds:cm_on_demand_search_and_replace:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:cminds:cm_search_and_replace:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cm-pop-up-banners": [
|
||||
"cpe:2.3:a:cminds:cm_popup:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cm-table-of-content": [
|
||||
"cpe:2.3:a:cminds:cm_table_of_contents:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cm-video-lesson-manager": [
|
||||
"cpe:2.3:a:creativemindssolutions:video_lessons_manager:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:creativemindssolutions:video_lessons_manager_pro:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -9330,6 +9381,9 @@
|
||||
"colorful-categories": [
|
||||
"cpe:2.3:a:gesundheit-bewegt:colorful_categories:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"colorlib-coming-soon-maintenance": [
|
||||
"cpe:2.3:a:colorlib:coming_soon_\\\u0026_maintenance_mode:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"column-matic": [
|
||||
"cpe:2.3:a:column-matic_project:column-matic:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9701,6 +9755,9 @@
|
||||
"core-web-vitals-pagespeed-booster": [
|
||||
"cpe:2.3:a:magazine3:core_web_vitals_\\\u0026_pagespeed_booster:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"coreactivity": [
|
||||
"cpe:2.3:a:dev4press:coreactivity:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"corner-ad": [
|
||||
"cpe:2.3:a:dwbooster:corner_ad:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9711,7 +9768,7 @@
|
||||
"cpe:2.3:a:marcorulicke:coru_lfmember:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cost-calculator-builder": [
|
||||
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:stylemixthemes:cost_calculator_builder:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"countdown-block": [
|
||||
"cpe:2.3:a:wpdeveloper:countdown_block:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -10063,6 +10120,9 @@
|
||||
"cpe:2.3:a:fastlinemedia:customizer_export\\/import:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpbeaverbuilder:customizer_export\\/import:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cww-companion": [
|
||||
"cpe:2.3:a:codeworkweb:cww_companion:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cybersoldier": [
|
||||
"cpe:2.3:a:webbigt:cybersoldier:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10159,7 +10219,8 @@
|
||||
"cpe:2.3:a:webtoffee:decorator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"defender-security": [
|
||||
"cpe:2.3:a:wmpudev:defender_security:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpmudev:defender:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpmudev:defender:*:*:*:*:free:wordpress:*:*",
|
||||
"cpe:2.3:a:wpmudev:defender_security:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"definitive-addons-for-elementor": [
|
||||
@@ -10287,9 +10348,15 @@
|
||||
"dmsguestbook": [
|
||||
"cpe:2.3:a:dmsguestbook_project:dmsguestbook:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"dn-footer-contacts": [
|
||||
"cpe:2.3:a:digireturn:footer_contacts_bar:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"dn-popup": [
|
||||
"cpe:2.3:a:digireturn:dn_popup:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"dn-shipping-by-weight": [
|
||||
"cpe:2.3:a:digireturn:shipping_by_weight_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"document-emberdder": [
|
||||
"cpe:2.3:a:bplugins:document_embedder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10307,7 +10374,7 @@
|
||||
"cpe:2.3:a:wedevs:dokan:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"dokan-pro": [
|
||||
"cpe:2.3:a:dokan:dokan_pro_plugin:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:dokan:dokan:*:*:*:*:pro:wordpress:*:*"
|
||||
],
|
||||
"dologin": [
|
||||
"cpe:2.3:a:wpdo:dologin_security:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -10568,6 +10635,9 @@
|
||||
"easy-newsletter-signups": [
|
||||
"cpe:2.3:a:alphabpo:easy_newsletter_signups:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"easy-notify-lite": [
|
||||
"cpe:2.3:a:ghozylab:popup_builder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"easy-paypal-donation": [
|
||||
"cpe:2.3:a:wpplugin:accept_donations_with_paypal:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10610,6 +10680,9 @@
|
||||
"easy-table": [
|
||||
"cpe:2.3:a:easy_table_project:easy_table:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"easy-table-of-contents": [
|
||||
"cpe:2.3:a:magazine3:easy_table_of_contents:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"easy-testimonial-manager": [
|
||||
"cpe:2.3:a:easy_testimonial_manager_project:easy_testimonial_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10718,6 +10791,9 @@
|
||||
"eg-attachments": [
|
||||
"cpe:2.3:a:egeorjon:eg-attachments:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ekc-tournament-manager": [
|
||||
"cpe:2.3:a:lukashuser:ekc_tournament_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"elastic-email-sender": [
|
||||
"cpe:2.3:a:elasticemail:elastic_email_sender:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10952,7 +11028,8 @@
|
||||
"cpe:2.3:a:estatik:estatik:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"estatik-mortgage-calculator": [
|
||||
"cpe:2.3:a:estatik:estatik_mortgage_calculator:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:estatik:estatik_mortgage_calculator:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:estatik:mortgage_calculator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"etsy-shop": [
|
||||
"cpe:2.3:a:etsy_shop_project:etsy_shop:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -11209,6 +11286,9 @@
|
||||
"fast-search-powered-by-solr": [
|
||||
"cpe:2.3:a:fast-search-powered-by-solr_project:fast-search-powered-by-solr:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fast-tube": [
|
||||
"cpe:2.3:a:caspie:fast_tube:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fast-velocity-minify": [
|
||||
"cpe:2.3:a:fastvelocity:minify:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11255,6 +11335,9 @@
|
||||
"featured-image-from-url": [
|
||||
"cpe:2.3:a:fifu:featured_image_from_url:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"featured-image-plus": [
|
||||
"cpe:2.3:a:krasenslavov:featured_image_plus:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"feed-changer": [
|
||||
"cpe:2.3:a:wp-master:feed_changer_\\\u0026_remover:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11276,6 +11359,9 @@
|
||||
"feedzy-rss-feeds": [
|
||||
"cpe:2.3:a:themeisle:rss_aggregator_by_feedzy:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fetch-jft": [
|
||||
"cpe:2.3:a:pjaudiomv:fetch_jft:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fg-drupal-to-wp": [
|
||||
"cpe:2.3:a:fredericgilles:fg_drupal:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11342,6 +11428,7 @@
|
||||
"cpe:2.3:a:find_my_blocks_project:find_my_blocks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"first-graders-toolbox": [
|
||||
"cpe:2.3:a:atakanau:1_click_disable_all:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:atakanau:one_click_disable_all:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"fish-and-ships": [
|
||||
@@ -11400,6 +11487,9 @@
|
||||
"floating-button": [
|
||||
"cpe:2.3:a:wow-company:floating_button:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"floating-contact": [
|
||||
"cpe:2.3:a:just-a-web-developer:floating_contact_button:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"floating-div": [
|
||||
"cpe:2.3:a:floating_div_project:floating_div:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11449,6 +11539,9 @@
|
||||
"fluid-responsive-slideshow": [
|
||||
"cpe:2.3:a:tonjoostudio:fluid-responsive-slideshow:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"flynax-bridge": [
|
||||
"cpe:2.3:a:flynax:flynax_bridge:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"folders": [
|
||||
"cpe:2.3:a:premio:folders:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -11823,6 +11916,9 @@
|
||||
"cpe:2.3:a:ghost:ghost:*:*:*:*:*:node.js:*:*",
|
||||
"cpe:2.3:a:ghost:ghost:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gianism": [
|
||||
"cpe:2.3:a:takahashifumiki:gianism:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gift-certificate-creator": [
|
||||
"cpe:2.3:a:bobcares:gift-certificate-creator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12045,7 +12141,8 @@
|
||||
"cpe:2.3:a:groundhogg:groundhogg:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gs-logo-slider": [
|
||||
"cpe:2.3:a:gsplugins:gs_logo_slider:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:gsplugins:gs_logo_slider:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:gsplugins:logo_slider:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"gs-portfolio": [
|
||||
"cpe:2.3:a:gsplugins:gs_filterable_portfolio:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -12399,12 +12496,16 @@
|
||||
"ideapush": [
|
||||
"cpe:2.3:a:northernbeacheswebsites:ideapush:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"idonate": [
|
||||
"cpe:2.3:a:themeatelier:idonate:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"idx-broker-platinum": [
|
||||
"cpe:2.3:a:idxbroker:idx_broker:*:*:*:*:platinum:wordpress:*:*",
|
||||
"cpe:2.3:a:idxbroker:impress_for_idx_broker:*:*:*:*:platinum:wordpress:*:*"
|
||||
],
|
||||
"if-so": [
|
||||
"cpe:2.3:a:if-so:dynamic_content_personalization:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:if-so:dynamic_content_personalization:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:if-so:if-so:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ifeature-slider": [
|
||||
"cpe:2.3:a:cyberchimps:ifeature_slider:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -12494,6 +12595,9 @@
|
||||
"image-vertical-reel-scroll-slideshow": [
|
||||
"cpe:2.3:a:gopiplus:image_vertical_reel_scroll_slideshow:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"image-widget": [
|
||||
"cpe:2.3:a:stellarwp:image_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"imageboss": [
|
||||
"cpe:2.3:a:imageboss:imageboss:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12650,6 +12754,9 @@
|
||||
"instant-images": [
|
||||
"cpe:2.3:a:connekthq:instant_images_-_one_click_unsplash_uploads:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"instantio": [
|
||||
"cpe:2.3:a:themefic:instantio:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"instawp-connect": [
|
||||
"cpe:2.3:a:instawp:instawp_connect:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12782,6 +12889,12 @@
|
||||
"jetformbuilder": [
|
||||
"cpe:2.3:a:crocoblock:jetformbuilder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"jetpack": [
|
||||
"cpe:2.3:a:automattic:jetpack:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"jetpack-boost": [
|
||||
"cpe:2.3:a:automattic:jetpack_boost:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"jetwidgets-for-elementor": [
|
||||
"cpe:2.3:a:crocoblock:jetwidgets_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12887,6 +13000,9 @@
|
||||
"just-tables": [
|
||||
"cpe:2.3:a:hasthemes:justtables:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"justrows-free": [
|
||||
"cpe:2.3:a:canaveralstudio:justrows_free:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"jvm-rich-text-icons": [
|
||||
"cpe:2.3:a:jorisvm:jvm_gutenberg_rich_text_icons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12920,6 +13036,9 @@
|
||||
"kb-support": [
|
||||
"cpe:2.3:a:logon:kb_support:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"kbucket": [
|
||||
"cpe:2.3:a:optimalaccess:kbucket:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"keep-backup-daily": [
|
||||
"cpe:2.3:a:androidbubbles:keep_backup_daily:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12959,6 +13078,9 @@
|
||||
"kk-star-ratings": [
|
||||
"cpe:2.3:a:kamalkhan:kk_star_ratings:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"kkprogressbar": [
|
||||
"cpe:2.3:a:krzysztof-furtak:kkprogressbar2:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"klarna-checkout-for-woocommerce": [
|
||||
"cpe:2.3:a:klarna:klarna_checkout_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13122,6 +13244,9 @@
|
||||
"legal-pages": [
|
||||
"cpe:2.3:a:wpwax:legal_pages:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"legull": [
|
||||
"cpe:2.3:a:alwayscurious:legull:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"leira-cron-jobs": [
|
||||
"cpe:2.3:a:leira:cron_jobs:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13340,6 +13465,9 @@
|
||||
"logwpmail": [
|
||||
"cpe:2.3:a:premierethemes:log_wp_mail:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"lokalyze-call-now": [
|
||||
"cpe:2.3:a:lokalyze:call_me_now:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ltl-freight-quotes-unishippers-edition": [
|
||||
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:unishippers:wordpress:*:*"
|
||||
],
|
||||
@@ -13434,7 +13562,7 @@
|
||||
"cpe:2.3:a:mailoptin:mailoptin:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mailpoet": [
|
||||
"cpe:2.3:a:mailpoet:mailpoet:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:automattic:mailpoet:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"mailtree-log-mail": [
|
||||
"cpe:2.3:a:oacstudio:mailtree_log_mail:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -13442,6 +13570,9 @@
|
||||
"maintenance": [
|
||||
"cpe:2.3:a:webfactoryltd:maintenance:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"maintenance-notice": [
|
||||
"cpe:2.3:a:codevibrant:maintenance_notice:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"maintenance-page": [
|
||||
"cpe:2.3:a:themegrill:maintenance_page:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13610,6 +13741,9 @@
|
||||
"media-library-plus": [
|
||||
"cpe:2.3:a:maxfoundry:media_library_folders:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"media-library-tools": [
|
||||
"cpe:2.3:a:wptinysolutions:media_library_tools:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"media-list": [
|
||||
"cpe:2.3:a:drelton:medialist:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13723,6 +13857,9 @@
|
||||
"message-ticker": [
|
||||
"cpe:2.3:a:gopiplus:message_ticker:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meta-box": [
|
||||
"cpe:2.3:a:metabox:meta_box:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meta-store-elements": [
|
||||
"cpe:2.3:a:mysticalthemes:meta_store_elements:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13984,6 +14121,9 @@
|
||||
"cpe:2.3:a:codepeople:music_store:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:musicstore:music_store:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"muslim-prayer-time-bd": [
|
||||
"cpe:2.3:a:realwebcare:muslim_prayer_time_bd:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mw-wp-form": [
|
||||
"cpe:2.3:a:web-soudan:mw_wp_form:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14165,6 +14305,9 @@
|
||||
"newsletter-optin-box": [
|
||||
"cpe:2.3:a:noptin:noptin:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"newsletter-popup": [
|
||||
"cpe:2.3:a:mndpsingh287:newsletter_popup:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"newsletters-lite": [
|
||||
"cpe:2.3:a:tribulant:newsletters:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14426,6 +14569,12 @@
|
||||
"opening-hours": [
|
||||
"cpe:2.3:a:designextreme:we\\'re_open\\!:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"openpgp-form-encryption": [
|
||||
"cpe:2.3:a:arnesonium:openpgp_form_encryption:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"opensheetmusicdisplay": [
|
||||
"cpe:2.3:a:opensheetmusicdisplay:opensheetmusicdisplay:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ops-robots-txt": [
|
||||
"cpe:2.3:a:ops-robots-txt_project:ops-robots-txt:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14561,6 +14710,9 @@
|
||||
"pagepost-content-shortcode": [
|
||||
"cpe:2.3:a:page\\/post_content_shortcode_project:page\\/post_content_shortcode:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"pagerank-tools": [
|
||||
"cpe:2.3:a:mahype:pagerank_tools:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"pagination": [
|
||||
"cpe:2.3:a:bestwebsoft:pagination:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14769,6 +14921,9 @@
|
||||
"cpe:2.3:a:greentreelabs:gallery_photoblocks:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpchill:gallery_photoblocks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"photoshow": [
|
||||
"cpe:2.3:a:codepeople:smart_image_gallery:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"photospace": [
|
||||
"cpe:2.3:a:photospace_gallery_project:photospace_gallery:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15012,6 +15167,9 @@
|
||||
"post-highlights": [
|
||||
"cpe:2.3:a:post_highlights_projects:post_highlights:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"post-in-page-for-elementor": [
|
||||
"cpe:2.3:a:migaweb:post_in_page_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"post-index": [
|
||||
"cpe:2.3:a:post_index_project:post_index:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15032,8 +15190,7 @@
|
||||
"cpe:2.3:a:i13websolution:post_sliders_\\\u0026_post_grids:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"post-smtp": [
|
||||
"cpe:2.3:a:wpexperts:post_smtp:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpexperts:post_smtp_mailer:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:wpexperts:post_smtp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"post-snippets": [
|
||||
"cpe:2.3:a:postsnippets:post_snippets:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -15236,6 +15393,9 @@
|
||||
"product-designer": [
|
||||
"cpe:2.3:a:pickplugins:product_designer:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"product-enquiry-for-woocommerce": [
|
||||
"cpe:2.3:a:wisdmlabs:product_enquiry_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"product-expiry-for-woocommerce": [
|
||||
"cpe:2.3:a:webcodingplace:product_expiry_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15379,6 +15539,9 @@
|
||||
"cpe:2.3:a:quick_chat_project:quick_chat:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:techytalk:quick_chat:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"quick-code": [
|
||||
"cpe:2.3:a:gwycon:quick_code:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"quick-contact-form": [
|
||||
"cpe:2.3:a:fullworksplugins:quick_contact_form:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15410,6 +15573,9 @@
|
||||
"quickswish": [
|
||||
"cpe:2.3:a:hasthemes:quickswish:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"quillforms": [
|
||||
"cpe:2.3:a:mdmag:quill_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"quiz-expert": [
|
||||
"cpe:2.3:a:wepupil:quiz_expert_-_easy_quiz_maker\\,_exam_and_test_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15460,6 +15626,7 @@
|
||||
"cpe:2.3:a:softlabbd:radio_player:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"rafflepress": [
|
||||
"cpe:2.3:a:rafflepress:rafflepress:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:seedprod:rafflepress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"random-banner": [
|
||||
@@ -16082,6 +16249,9 @@
|
||||
"sendpress": [
|
||||
"cpe:2.3:a:pressified:sendpress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sendpulse-email-marketing-newsletter": [
|
||||
"cpe:2.3:a:sendpulse:sendpulse_email_marketing_newsletter:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"sendpulse-web-push": [
|
||||
"cpe:2.3:a:sendpulse:free_web_push:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16156,6 +16326,9 @@
|
||||
"cpe:2.3:a:legalweb:wp_dsgvo_tools:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:shapepress:wp_dsgvo_tools:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"share-buttons": [
|
||||
"cpe:2.3:a:artlosk:social_share_buttons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"share-on-diaspora": [
|
||||
"cpe:2.3:a:share_on_diaspora_project:share_on_diaspora:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16236,7 +16409,8 @@
|
||||
],
|
||||
"shortcodes-ultimate": [
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:-:wordpress:*:*"
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:-:wordpress:*:*",
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:pro:wordpress:*:*"
|
||||
],
|
||||
"shortcut-macros": [
|
||||
"cpe:2.3:a:shortcut_macros_project:shortcut_macros:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -16306,6 +16480,9 @@
|
||||
"simple-ajax-chat": [
|
||||
"cpe:2.3:a:plugin-planet:simple_ajax_chat:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-al-slider": [
|
||||
"cpe:2.3:a:alexdtn:simple_al_slider:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-author-box": [
|
||||
"cpe:2.3:a:webfactoryltd:simple_author_box:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16322,6 +16499,9 @@
|
||||
"simple-bitcoin-faucets": [
|
||||
"cpe:2.3:a:simple_bitcoin_faucets_project:simple_bitcoin_faucets:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-buttons-creator": [
|
||||
"cpe:2.3:a:robbychen:simple_buttons_creator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-calendar-for-elementor": [
|
||||
"cpe:2.3:a:migaweb:simple_calendar_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16381,6 +16561,9 @@
|
||||
"cpe:2.3:a:simplefilelist:simple-file-list:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:simplefilelist:simple_file_list:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-form": [
|
||||
"cpe:2.3:a:devsabbirahmed:simple_form:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-history": [
|
||||
"cpe:2.3:a:simple_history_project:simple_history:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16409,6 +16592,9 @@
|
||||
"simple-ldap-login": [
|
||||
"cpe:2.3:a:objectiv:simple_ldap_login:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-lightbox": [
|
||||
"cpe:2.3:a:archetyped:simple_lightbox:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-link-directory": [
|
||||
"cpe:2.3:a:quantumcloud:simple_link_directory:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16494,6 +16680,9 @@
|
||||
"simple-site-verify": [
|
||||
"cpe:2.3:a:idoweb:simple_site_verify:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"simple-sitemap": [
|
||||
"cpe:2.3:a:wpgoplugins:simple_sitemap:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"simple-slug-translate": [
|
||||
"cpe:2.3:a:simple_slug_translate_project:simple_slug_translate:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16788,7 +16977,8 @@
|
||||
"cpe:2.3:a:wpmudev:smartcrawl:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"smartsearchwp": [
|
||||
"cpe:2.3:a:smartsearchwp:smartsearchwp:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:smartsearchwp:smartsearchwp:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:webdigit:chatbot_with_chatgpt:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"smartsoftbutton-widget-de-botones-de-chat": [
|
||||
"cpe:2.3:a:smartsoft:button_widget_smartsoft:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -16862,6 +17052,9 @@
|
||||
"social-media-feather": [
|
||||
"cpe:2.3:a:sharethis:social_media_feather:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"social-media-widget": [
|
||||
"cpe:2.3:a:bmwebproperties:social_media_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"social-networks-auto-poster-facebook-twitter-g": [
|
||||
"cpe:2.3:a:nextscripts:social_networks_auto_poster:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16965,6 +17158,9 @@
|
||||
"spice-starter-sites": [
|
||||
"cpe:2.3:a:spicethemes:spice_starter_sites:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"spider-contacts": [
|
||||
"cpe:2.3:a:10web:spidercontacts:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"spider-event-calendar": [
|
||||
"cpe:2.3:a:web-dorado:spider_calendar:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:web-dorado:spider_event_calendar:*:*:*:*:*:*:*:*"
|
||||
@@ -17206,6 +17402,9 @@
|
||||
"support-chat": [
|
||||
"cpe:2.3:a:ninjateam:click_to_chat:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"support-svg": [
|
||||
"cpe:2.3:a:sayedulsayem:support_svg:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"supportcandy": [
|
||||
"cpe:2.3:a:supportcandy:supportcandy:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17233,12 +17432,18 @@
|
||||
"surveys": [
|
||||
"cpe:2.3:a:surveys_project:surveys:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"svg-block": [
|
||||
"cpe:2.3:a:boldblocks:svg_block:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"svg-support": [
|
||||
"cpe:2.3:a:benbodhi:svg_support:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"svg-vector-icon-plugin": [
|
||||
"cpe:2.3:a:wp_svg_icons_project:wp_svg_icons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"svgator": [
|
||||
"cpe:2.3:a:svgator:svgator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"svgmagic": [
|
||||
"cpe:2.3:a:andibauer:svgmagic:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17627,6 +17832,9 @@
|
||||
"tourfic": [
|
||||
"cpe:2.3:a:themefic:tourfic:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tournamatch": [
|
||||
"cpe:2.3:a:tournamatch:tournamatch:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tp-education": [
|
||||
"cpe:2.3:a:themepalace:tp_education:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17642,6 +17850,9 @@
|
||||
"tracking-code-manager": [
|
||||
"cpe:2.3:a:data443:tracking_code_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tradedoubler-affiliate-tracker": [
|
||||
"cpe:2.3:a:tradedoubler:grow:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"tradetracker-store": [
|
||||
"cpe:2.3:a:wpaffiliatefeed:tradetracker-store:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17684,6 +17895,9 @@
|
||||
"tripetto": [
|
||||
"cpe:2.3:a:tripetto:tripetto:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"truebooker-appointment-booking": [
|
||||
"cpe:2.3:a:themetechmount:truebooker:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ts-webfonts-for-conoha": [
|
||||
"cpe:2.3:a:gmo:typesquare_webfonts_for_conoha:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17914,6 +18128,9 @@
|
||||
"underconstruction": [
|
||||
"cpe:2.3:a:underconstruction_project:underconstruction:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ungallery": [
|
||||
"cpe:2.3:a:mmond:ungallery:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"uninstall": [
|
||||
"cpe:2.3:a:wordpress_uninstall_project:wordpress_uninstall:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18493,6 +18710,9 @@
|
||||
"widget-twitter": [
|
||||
"cpe:2.3:a:web-dorado:wd_widgettwitter:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"widget4call": [
|
||||
"cpe:2.3:a:apidaze:widget4call:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"widgetkit-for-elementor": [
|
||||
"cpe:2.3:a:themesgrove:all-in-one_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18697,6 +18917,9 @@
|
||||
"woo-tranzila-gateway": [
|
||||
"cpe:2.3:a:antonbond:woocommerce_tranzila_payment_gateway:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woo-ups-pickup": [
|
||||
"cpe:2.3:a:ashamil:opsi_israel_domestic_shipments:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woo-variation-gallery": [
|
||||
"cpe:2.3:a:getwooplugins:additional_variation_images_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18739,6 +18962,9 @@
|
||||
"woocommerce-amazon-affiliates-light-version": [
|
||||
"cpe:2.3:a:wzone_project:wzone:*:*:*:*:lite:wordpress:*:*"
|
||||
],
|
||||
"woocommerce-and-wp-emember-integration": [
|
||||
"cpe:2.3:a:tipsandtricks-hq:wp_emember:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woocommerce-catalog-enquiry": [
|
||||
"cpe:2.3:a:multivendorx:product_catalog_mode_for_woocommerce:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wc-marketplace:wc_catalog_enquiry:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -19022,6 +19248,9 @@
|
||||
"wp-ban": [
|
||||
"cpe:2.3:a:wp-ban_project:wp-ban:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-base-booking-of-appointments-services-and-events": [
|
||||
"cpe:2.3:a:wp-base:wp_base_booking_of_appointments\\,_services_and_events:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-best-quiz": [
|
||||
"cpe:2.3:a:subina:wp_best_quiz:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19155,6 +19384,9 @@
|
||||
"wp-commentnavi": [
|
||||
"cpe:2.3:a:wp-commentnavi_project:wp-commentnavi:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-compress-image-optimizer": [
|
||||
"cpe:2.3:a:wpcompress:wp_compress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-concours": [
|
||||
"cpe:2.3:a:olyos:wp-concours:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19327,6 +19559,9 @@
|
||||
"wp-education": [
|
||||
"cpe:2.3:a:hasthemes:wp_education:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-eggdrop": [
|
||||
"cpe:2.3:a:backie:wp-eggdrop:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-email": [
|
||||
"cpe:2.3:a:wp-email_project:wp-email:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19554,6 +19789,9 @@
|
||||
"wp-imageflow2": [
|
||||
"cpe:2.3:a:spiffyplugins:wp_flow_plus:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-imagezoom": [
|
||||
"cpe:2.3:a:aueda:wp-imagezoom:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-import-export-lite": [
|
||||
"cpe:2.3:a:vjinfotech:wp_import_export_lite:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19971,6 +20209,9 @@
|
||||
"wp-seo-redirect-301": [
|
||||
"cpe:2.3:a:wp_seo_redirect_301_project:wp_seo_redirect_301:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-seo-structured-data-schema": [
|
||||
"cpe:2.3:a:wpsemplugins:wp_seo_structured_data_schema:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"wp-seo-tags": [
|
||||
"cpe:2.3:a:wp_seo_tags_project:wp_seo_tags:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20155,7 +20396,7 @@
|
||||
"cpe:2.3:a:wp-t-wap_project:wp-t-wap:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-table-builder": [
|
||||
"cpe:2.3:a:wptablebuilder:wp_table_builder:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:dotcamp:wp_table_builder:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-table-reloaded": [
|
||||
"cpe:2.3:a:wp-table_reloaded_project:wp-table_reloaded:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -20284,6 +20525,9 @@
|
||||
"wp-webauthn": [
|
||||
"cpe:2.3:a:axton:wp-webauthn:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-webinarsystem": [
|
||||
"cpe:2.3:a:webinarpress:webinarpress:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-whatsapp": [
|
||||
"cpe:2.3:a:ninjateam:wp_chat_app:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20305,6 +20549,9 @@
|
||||
"wp-worthy": [
|
||||
"cpe:2.3:a:tiggerswelt:worthy:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-yadisk-files": [
|
||||
"cpe:2.3:a:antongorodezkiy:yadisk_files:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-yelp-review-slider": [
|
||||
"cpe:2.3:a:ljapps:wp_yelp_review_slider:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20411,7 +20658,8 @@
|
||||
"cpe:2.3:a:wpforms:wpforms:*:*:*:*:pro:wordpress:*:*"
|
||||
],
|
||||
"wpforms-lite": [
|
||||
"cpe:2.3:a:wpforms:contact_form:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:wpforms:contact_form:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpforms:wpforms:*:*:*:*:lite:wordpress:*:*"
|
||||
],
|
||||
"wpforo": [
|
||||
"cpe:2.3:a:gvectors:wpforo:*:*:*:*:*:wordpress:*:*",
|
||||
@@ -20470,6 +20718,9 @@
|
||||
"wpmarketplace": [
|
||||
"cpe:2.3:a:wpmarketplace_project:wpmarketplace:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpmovielibrary": [
|
||||
"cpe:2.3:a:caercam:wpmovielibrary:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpo365-login": [
|
||||
"cpe:2.3:a:wpo365:wordpress_\\+_azure_ad_\\/_microsoft_office_365:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -20669,6 +20920,10 @@
|
||||
"yith-maintenance-mode": [
|
||||
"cpe:2.3:a:yithemes:yith_maintenance_mode:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"yith-woocommerce-ajax-search": [
|
||||
"cpe:2.3:a:yithemes:yith_woocommerce_ajax_search:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:yithemes:yith_woocommerce_ajax_search:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"yith-woocommerce-gift-cards": [
|
||||
"cpe:2.3:a:yithemes:yith_woocommerce_gift_cards:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:yithemes:yith_woocommerce_gift_cards:*:*:*:*:free:wordpress:*:*"
|
||||
@@ -20750,6 +21005,9 @@
|
||||
"z-companion": [
|
||||
"cpe:2.3:a:wpzita:z_companion:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"z-downloads": [
|
||||
"cpe:2.3:a:urbanbase:z-downloads:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"z-url-preview": [
|
||||
"cpe:2.3:a:z-url_preview_project:z-url_preview:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,736 @@
|
||||
package pe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/scylladb/go-set/u32set"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
const peMaxAllowedDirectoryEntries = 0x1000
|
||||
|
||||
var imageDirectoryEntryIndexes = []int{
|
||||
pe.IMAGE_DIRECTORY_ENTRY_RESOURCE, // where version resources are stored
|
||||
pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // where info about the CLR is stored
|
||||
}
|
||||
|
||||
// File does not directly represent a binary shape to be parsed, instead it represents the
|
||||
// information of interest extracted from a PE file.
|
||||
type File struct {
|
||||
// Location is where the PE file was found
|
||||
Location file.Location
|
||||
|
||||
// CLR is the information about the CLR (common language runtime) version found in the PE file which helps
|
||||
// understand if this executable is even a .NET application.
|
||||
CLR *CLREvidence
|
||||
|
||||
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
|
||||
VersionResources map[string]string
|
||||
}
|
||||
|
||||
// CLREvidence is basic info about the CLR (common language runtime) version from the COM descriptor.
|
||||
// This is not a complete representation of the CLR version, but rather a subset of the information that is
|
||||
// useful to us.
|
||||
type CLREvidence struct {
|
||||
// HasClrResourceNames is true if there are CLR resource names found in the PE file (e.g. "CLRDEBUGINFO").
|
||||
HasClrResourceNames bool
|
||||
|
||||
// MajorVersion is the minimum supported major version of the CLR.
|
||||
MajorVersion uint16
|
||||
|
||||
// MinorVersion is the minimum supported minor version of the CLR.
|
||||
MinorVersion uint16
|
||||
}
|
||||
|
||||
// HasEvidenceOfCLR returns true if the PE file has evidence of a CLR (common language runtime) version.
|
||||
func (c *CLREvidence) HasEvidenceOfCLR() bool {
|
||||
return c != nil && (c.MajorVersion != 0 && c.MinorVersion != 0 || c.HasClrResourceNames)
|
||||
}
|
||||
|
||||
type peDosHeader struct {
|
||||
Magic [2]byte // "MZ"
|
||||
Unused [58]byte
|
||||
AddressOfNewEXEHeader uint32 // offset to PE header
|
||||
}
|
||||
|
||||
// peImageCore20 represents the .NET Core 2.0 header structure.
|
||||
// Source: https://github.com/dotnet/msbuild/blob/9fa9d800dabce3bfcf8365f651f3a713e01f8a85/src/Tasks/NativeMethods.cs#L761-L775
|
||||
type peImageCore20 struct {
|
||||
Cb uint32
|
||||
MajorRuntimeVersion uint16
|
||||
MinorRuntimeVersion uint16
|
||||
}
|
||||
|
||||
// peImageResourceDirectory represents the resource directory structure.
|
||||
type peImageResourceDirectory struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
NumberOfNamedEntries uint16
|
||||
NumberOfIDEntries uint16
|
||||
}
|
||||
|
||||
// peImageResourceDirectoryEntry represents an entry in the resource directory entries.
|
||||
type peImageResourceDirectoryEntry struct {
|
||||
Name uint32
|
||||
OffsetToData uint32
|
||||
}
|
||||
|
||||
// peImageResourceDataEntry is the unit of raw data in the Resource Data area.
|
||||
type peImageResourceDataEntry struct {
|
||||
OffsetToData uint32
|
||||
Size uint32
|
||||
CodePage uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
// peVsFixedFileInfo represents the fixed file information structure.
|
||||
type peVsFixedFileInfo struct {
|
||||
Signature uint32
|
||||
StructVersion uint32
|
||||
FileVersionMS uint32
|
||||
FileVersionLS uint32
|
||||
ProductVersionMS uint32
|
||||
ProductVersionLS uint32
|
||||
FileFlagsMask uint32
|
||||
FileFlags uint32
|
||||
FileOS uint32
|
||||
FileType uint32
|
||||
FileSubtype uint32
|
||||
FileDateMS uint32
|
||||
FileDateLS uint32
|
||||
}
|
||||
|
||||
type peVsVersionInfo peLenValLenType
|
||||
|
||||
type peStringFileInfo peLenValLenType
|
||||
|
||||
type peStringTable peLenValLenType
|
||||
|
||||
type peString peLenValLenType
|
||||
|
||||
type peLenValLenType struct {
|
||||
Length uint16
|
||||
ValueLength uint16
|
||||
Type uint16
|
||||
}
|
||||
|
||||
type extractedSection struct {
|
||||
RVA uint32
|
||||
BaseRVA uint32
|
||||
Size uint32
|
||||
Reader *bytes.Reader
|
||||
}
|
||||
|
||||
func (s extractedSection) exists() bool {
|
||||
return s.RVA != 0 && s.Size != 0
|
||||
}
|
||||
|
||||
func directoryName(i int) string {
|
||||
switch i {
|
||||
case pe.IMAGE_DIRECTORY_ENTRY_RESOURCE:
|
||||
return "Resource"
|
||||
case pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
|
||||
return "COM Descriptor"
|
||||
}
|
||||
return fmt.Sprintf("Unknown (%d)", i)
|
||||
}
|
||||
|
||||
func Read(f file.LocationReadCloser) (*File, error) {
|
||||
r, err := unionreader.GetUnionReader(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sections, _, err := parsePEFile(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
|
||||
}
|
||||
|
||||
dirs := u32set.New() // keep track of the RVAs we have already parsed (prevent infinite recursion edge cases)
|
||||
versionResources := make(map[string]string) // map of version resource keys to their values
|
||||
resourceNames := strset.New() // set of resource names found in the PE file
|
||||
err = parseResourceDirectory(sections[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE], dirs, versionResources, resourceNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := parseCLR(sections[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR], resourceNames)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
|
||||
}
|
||||
|
||||
return &File{
|
||||
Location: f.Location,
|
||||
CLR: c,
|
||||
VersionResources: versionResources,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parsePEFile creates readers for targeted sections of the binary used by downstream processing.
|
||||
func parsePEFile(file unionreader.UnionReader) (map[int]*extractedSection, []pe.SectionHeader32, error) {
|
||||
fileHeader, magic, err := parsePEHeader(file)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing PE header: %w", err)
|
||||
}
|
||||
|
||||
soi, headers, err := parseSectionHeaders(file, magic, fileHeader.NumberOfSections)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing section headers: %w", err)
|
||||
}
|
||||
|
||||
for i, sec := range soi {
|
||||
if !sec.exists() {
|
||||
continue
|
||||
}
|
||||
data, err := readDataFromRVA(file, sec.RVA, sec.Size, headers)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading %q section data: %w", directoryName(i), err)
|
||||
}
|
||||
sec.Reader = data
|
||||
}
|
||||
|
||||
return soi, headers, nil
|
||||
}
|
||||
|
||||
// parsePEHeader reads the beginning of a PE formatted file, returning the file header and "magic" indicator
|
||||
// for downstream logic to determine 32/64 bit parsing.
|
||||
func parsePEHeader(file unionreader.UnionReader) (*pe.FileHeader, uint16, error) {
|
||||
var dosHeader peDosHeader
|
||||
if err := binary.Read(file, binary.LittleEndian, &dosHeader); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading DOS header: %w", err)
|
||||
}
|
||||
if string(dosHeader.Magic[:]) != "MZ" {
|
||||
return nil, 0, fmt.Errorf("invalid DOS header magic")
|
||||
}
|
||||
|
||||
peOffset := int64(dosHeader.AddressOfNewEXEHeader)
|
||||
if _, err := file.Seek(peOffset, io.SeekStart); err != nil {
|
||||
return nil, 0, fmt.Errorf("error seeking to PE header: %w", err)
|
||||
}
|
||||
|
||||
var signature [4]byte
|
||||
if err := binary.Read(file, binary.LittleEndian, &signature); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading PE signature: %w", err)
|
||||
}
|
||||
if !bytes.Equal(signature[:], []byte("PE\x00\x00")) {
|
||||
return nil, 0, fmt.Errorf("invalid PE signature")
|
||||
}
|
||||
|
||||
var fileHeader pe.FileHeader
|
||||
if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading file header: %w", err)
|
||||
}
|
||||
|
||||
var magic uint16
|
||||
if err := binary.Read(file, binary.LittleEndian, &magic); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading optional header magic: %w", err)
|
||||
}
|
||||
|
||||
// seek back to before reading magic (since that value is in the header)
|
||||
if _, err := file.Seek(-2, io.SeekCurrent); err != nil {
|
||||
return nil, 0, fmt.Errorf("error seeking back to before reading magic: %w", err)
|
||||
}
|
||||
|
||||
return &fileHeader, magic, nil
|
||||
}
|
||||
|
||||
// parseSectionHeaders reads the section headers from the PE file and extracts the virtual addresses + section size
|
||||
// information for the sections of interest. Additionally, all section headers are returned to aid in downstream processing.
|
||||
func parseSectionHeaders(file unionreader.UnionReader, magic uint16, numberOfSections uint16) (map[int]*extractedSection, []pe.SectionHeader32, error) {
|
||||
soi := make(map[int]*extractedSection)
|
||||
switch magic {
|
||||
case 0x10B: // PE32
|
||||
var optHeader pe.OptionalHeader32
|
||||
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading optional header (PE32): %w", err)
|
||||
}
|
||||
|
||||
for _, i := range imageDirectoryEntryIndexes {
|
||||
sectionHeader := optHeader.DataDirectory[i]
|
||||
if sectionHeader.Size == 0 {
|
||||
continue
|
||||
}
|
||||
soi[i] = &extractedSection{
|
||||
RVA: sectionHeader.VirtualAddress,
|
||||
Size: sectionHeader.Size,
|
||||
}
|
||||
}
|
||||
case 0x20B: // PE32+ (64 bit)
|
||||
var optHeader pe.OptionalHeader64
|
||||
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading optional header (PE32+): %w", err)
|
||||
}
|
||||
|
||||
for _, i := range imageDirectoryEntryIndexes {
|
||||
sectionHeader := optHeader.DataDirectory[i]
|
||||
if sectionHeader.Size == 0 {
|
||||
continue
|
||||
}
|
||||
soi[i] = &extractedSection{
|
||||
RVA: sectionHeader.VirtualAddress,
|
||||
Size: sectionHeader.Size,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown optional header magic: 0x%x", magic)
|
||||
}
|
||||
|
||||
// read section headers
|
||||
headers := make([]pe.SectionHeader32, numberOfSections)
|
||||
for i := 0; i < int(numberOfSections); i++ {
|
||||
if err := binary.Read(file, binary.LittleEndian, &headers[i]); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading section header: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return soi, headers, nil
|
||||
}
|
||||
|
||||
// parseCLR extracts the CLR (common language runtime) version information from the COM descriptor and makes
|
||||
// present/not-present determination based on the presence of CLR resource names.
|
||||
func parseCLR(sec *extractedSection, resourceNames *strset.Set) (*CLREvidence, error) {
|
||||
hasCLRDebugResourceNames := resourceNames.HasAny("CLRDEBUGINFO")
|
||||
if sec == nil || sec.Reader == nil {
|
||||
return &CLREvidence{
|
||||
HasClrResourceNames: hasCLRDebugResourceNames,
|
||||
}, nil
|
||||
}
|
||||
|
||||
reader := sec.Reader
|
||||
var c peImageCore20
|
||||
if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
|
||||
return nil, fmt.Errorf("error reading CLR header: %w", err)
|
||||
}
|
||||
|
||||
return &CLREvidence{
|
||||
HasClrResourceNames: hasCLRDebugResourceNames,
|
||||
MajorVersion: c.MajorRuntimeVersion,
|
||||
MinorVersion: c.MinorRuntimeVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// rvaToFileOffset is a helper function to convert RVA to file offset using section headers
|
||||
func rvaToFileOffset(rva uint32, sections []pe.SectionHeader32) (uint32, error) {
|
||||
for _, section := range sections {
|
||||
if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize {
|
||||
return section.PointerToRawData + (rva - section.VirtualAddress), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("RVA 0x%x not found in any section", rva)
|
||||
}
|
||||
|
||||
// readDataFromRVA will read data from a specific RVA in the PE file
|
||||
func readDataFromRVA(file io.ReadSeeker, rva, size uint32, sections []pe.SectionHeader32) (*bytes.Reader, error) {
|
||||
if size == 0 {
|
||||
return nil, fmt.Errorf("zero size specified")
|
||||
}
|
||||
|
||||
offset, err := rvaToFileOffset(rva, sections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := file.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("error seeking to data: %w", err)
|
||||
}
|
||||
|
||||
data := make([]byte, size)
|
||||
if _, err := io.ReadFull(file, data); err != nil {
|
||||
return nil, fmt.Errorf("error reading data: %w", err)
|
||||
}
|
||||
|
||||
return bytes.NewReader(data), nil
|
||||
}
|
||||
|
||||
// parseResourceDirectory recursively parses a PE resource directory. This takes a relative virtual address (offset of
|
||||
// a piece of data or code relative to the base address), the size of the resource directory, the set of RVAs already
|
||||
// parsed, and the map to populate discovered version resource values.
|
||||
//
|
||||
// .rsrc Section
|
||||
// +------------------------------+
|
||||
// | Resource Directory Table |
|
||||
// +------------------------------+
|
||||
// | Resource Directory Entries |
|
||||
// | +------------------------+ |
|
||||
// | | Subdirectory or Data | |
|
||||
// | +------------------------+ |
|
||||
// +------------------------------+
|
||||
// | Resource Data Entries |
|
||||
// | +------------------------+ |
|
||||
// | | Resource Data | |
|
||||
// | +------------------------+ |
|
||||
// +------------------------------+
|
||||
// | Actual Resource Data |
|
||||
// +------------------------------+
|
||||
//
|
||||
// sources:
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section
|
||||
// - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)#pe-file-resources
|
||||
func parseResourceDirectory(sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
|
||||
if sec == nil || sec.Size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sec.Reader == nil {
|
||||
return errors.New("resource section not found")
|
||||
}
|
||||
|
||||
baseRVA := sec.BaseRVA
|
||||
if baseRVA == 0 {
|
||||
baseRVA = sec.RVA
|
||||
}
|
||||
|
||||
offset := int64(sec.RVA - baseRVA)
|
||||
if _, err := sec.Reader.Seek(offset, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to directory offset: %w", err)
|
||||
}
|
||||
|
||||
var directoryHeader peImageResourceDirectory
|
||||
if err := readIntoStruct(sec.Reader, &directoryHeader); err != nil {
|
||||
return fmt.Errorf("error reading directory header: %w", err)
|
||||
}
|
||||
|
||||
numEntries := int(directoryHeader.NumberOfNamedEntries + directoryHeader.NumberOfIDEntries)
|
||||
switch {
|
||||
case numEntries > peMaxAllowedDirectoryEntries:
|
||||
return fmt.Errorf("too many entries in resource directory: %d", numEntries)
|
||||
case numEntries == 0:
|
||||
return fmt.Errorf("no entries in resource directory")
|
||||
case numEntries < 0:
|
||||
return fmt.Errorf("invalid number of entries in resource directory: %d", numEntries)
|
||||
}
|
||||
|
||||
for i := 0; i < numEntries; i++ {
|
||||
var entry peImageResourceDirectoryEntry
|
||||
|
||||
entryOffset := offset + int64(binary.Size(directoryHeader)) + int64(i*binary.Size(entry))
|
||||
if _, err := sec.Reader.Seek(entryOffset, io.SeekStart); err != nil {
|
||||
log.Tracef("error seeking to PE entry offset: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := readIntoStruct(sec.Reader, &entry); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := processResourceEntry(entry, baseRVA, sec, dirs, fields, names); err != nil {
|
||||
log.Tracef("error processing resource entry: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processResourceEntry(entry peImageResourceDirectoryEntry, baseRVA uint32, sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
|
||||
// if the high bit is set, this is a directory entry, otherwise it is a data entry
|
||||
isDirectory := entry.OffsetToData&0x80000000 != 0
|
||||
|
||||
// note: the offset is relative to the beginning of the resource section, not an RVA
|
||||
entryOffsetToData := entry.OffsetToData & 0x7FFFFFFF
|
||||
|
||||
nameIsString := entry.Name&0x80000000 != 0
|
||||
nameOffset := entry.Name & 0x7FFFFFFF
|
||||
|
||||
// read the string name of the resource directory
|
||||
if nameIsString {
|
||||
currentPos, err := sec.Reader.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting current reader position: %w", err)
|
||||
}
|
||||
|
||||
if _, err := sec.Reader.Seek(int64(nameOffset), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error restoring reader position: %w", err)
|
||||
}
|
||||
|
||||
name, err := readUTF16WithLength(sec.Reader)
|
||||
if err == nil {
|
||||
names.Add(name)
|
||||
}
|
||||
|
||||
if _, err := sec.Reader.Seek(currentPos, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error restoring reader position: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if isDirectory {
|
||||
subRVA := baseRVA + entryOffsetToData
|
||||
if dirs.Has(subRVA) {
|
||||
// some malware uses recursive PE references to evade analysis
|
||||
return fmt.Errorf("recursive PE reference detected; skipping directory at baseRVA=0x%x subRVA=0x%x", baseRVA, subRVA)
|
||||
}
|
||||
|
||||
dirs.Add(subRVA)
|
||||
err := parseResourceDirectory(
|
||||
&extractedSection{
|
||||
RVA: subRVA,
|
||||
BaseRVA: baseRVA,
|
||||
Size: sec.Size - (sec.RVA - baseRVA),
|
||||
Reader: sec.Reader,
|
||||
},
|
||||
dirs, fields, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return parseResourceDataEntry(sec.Reader, baseRVA, baseRVA+entryOffsetToData, sec.Size, fields)
|
||||
}
|
||||
|
||||
func parseResourceDataEntry(reader *bytes.Reader, baseRVA, rva, remainingSize uint32, fields map[string]string) error {
|
||||
var dataEntry peImageResourceDataEntry
|
||||
offset := int64(rva - baseRVA)
|
||||
|
||||
if _, err := reader.Seek(offset, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to data entry offset: %w", err)
|
||||
}
|
||||
|
||||
if err := readIntoStruct(reader, &dataEntry); err != nil {
|
||||
return fmt.Errorf("error reading resource data entry: %w", err)
|
||||
}
|
||||
|
||||
if remainingSize < dataEntry.Size {
|
||||
return fmt.Errorf("resource data entry size exceeds remaining size")
|
||||
}
|
||||
|
||||
data := make([]byte, dataEntry.Size)
|
||||
if _, err := reader.Seek(int64(dataEntry.OffsetToData-baseRVA), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to resource data: %w", err)
|
||||
}
|
||||
|
||||
if _, err := reader.Read(data); err != nil {
|
||||
return fmt.Errorf("error reading resource data: %w", err)
|
||||
}
|
||||
|
||||
return parseVersionResourceSection(bytes.NewReader(data), fields)
|
||||
}
|
||||
|
||||
// parseVersionResourceSection parses a PE version resource section from within a resource directory.
|
||||
//
|
||||
// "The main structure in a version resource is the VS_FIXEDFILEINFO structure. Additional structures include the
|
||||
// VarFileInfo structure to store language information data, and StringFileInfo for user-defined string information.
|
||||
// All strings in a version resource are in Unicode format. Each block of information is aligned on a DWORD boundary."
|
||||
//
|
||||
// "VS_VERSIONINFO" (utf16)
|
||||
// +---------------------------------------------------+
|
||||
// | wLength (2 bytes) |
|
||||
// | wValueLength (2 bytes) |
|
||||
// | wType (2 bytes) |
|
||||
// | szKey ("VS_VERSION_INFO") (utf16) |
|
||||
// | Padding (to DWORD) |
|
||||
// +---------------------------------------------------+
|
||||
// | VS_FIXEDFILEINFO (52 bytes) |
|
||||
// +---------------------------------------------------+
|
||||
// | "StringFileInfo" (utf16) |
|
||||
// +---------------------------------------------------+
|
||||
// | wLength (2 bytes) |
|
||||
// | wValueLength (2 bytes) |
|
||||
// | wType (2 bytes) |
|
||||
// | szKey ("StringFileInfo") (utf16) |
|
||||
// | Padding (to DWORD) |
|
||||
// | StringTable |
|
||||
// | +--------------------------------------------+ |
|
||||
// | | wLength (2 bytes) | |
|
||||
// | | wValueLength (2 bytes) | |
|
||||
// | | wType (2 bytes) | |
|
||||
// | | szKey ("040904b0") | |
|
||||
// | | Padding (to DWORD) | |
|
||||
// | | String | |
|
||||
// | | +--------------------------------------+ | |
|
||||
// | | | wLength (2 bytes) | | |
|
||||
// | | | wValueLength (2 bytes) | | |
|
||||
// | | | wType (2 bytes) | | |
|
||||
// | | | szKey ("FileVersion") | | |
|
||||
// | | | Padding (to DWORD) | | |
|
||||
// | | | szValue ("15.00.0913.015") | | |
|
||||
// | | | Padding (to DWORD) | | |
|
||||
// | +--------------------------------------------+ |
|
||||
// +---------------------------------------------------+
|
||||
// | VarFileInfo (utf16) |
|
||||
// +---------------------------------------------------+
|
||||
// | (skip!) |
|
||||
// +---------------------------------------------------+
|
||||
//
|
||||
// sources:
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable
|
||||
func parseVersionResourceSection(reader *bytes.Reader, fields map[string]string) error {
|
||||
offset := 0
|
||||
|
||||
var info peVsVersionInfo
|
||||
if szKey, err := readIntoStructAndSzKey(reader, &info, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE version info: %v", err)
|
||||
} else if szKey != "VS_VERSION_INFO" {
|
||||
// this is a resource section, but not the version resources
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := alignAndSeek(reader, &offset); err != nil {
|
||||
return fmt.Errorf("error aligning past PE version info: %w", err)
|
||||
}
|
||||
|
||||
var fixedFileInfo peVsFixedFileInfo
|
||||
if err := readIntoStruct(reader, &fixedFileInfo, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE FixedFileInfo: %v", err)
|
||||
}
|
||||
|
||||
for reader.Len() > 0 {
|
||||
if err := alignAndSeek(reader, &offset); err != nil {
|
||||
return fmt.Errorf("error seeking to PE StringFileInfo: %w", err)
|
||||
}
|
||||
|
||||
var sfiHeader peStringFileInfo
|
||||
if szKey, err := readIntoStructAndSzKey(reader, &sfiHeader, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE string file info header: %v", err)
|
||||
} else if szKey != "StringFileInfo" {
|
||||
// we only care about extracting strings from any string tables, skip this
|
||||
offset += int(sfiHeader.ValueLength)
|
||||
continue
|
||||
}
|
||||
|
||||
var stOffset int
|
||||
|
||||
// note: the szKey for the prStringTable is the language
|
||||
var stHeader peStringTable
|
||||
if _, err := readIntoStructAndSzKey(reader, &stHeader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error reading PE string table header: %v", err)
|
||||
}
|
||||
|
||||
for stOffset < int(stHeader.Length) {
|
||||
var stringHeader peString
|
||||
if err := readIntoStruct(reader, &stringHeader, &offset, &stOffset); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
key := readUTF16(reader, &offset, &stOffset)
|
||||
|
||||
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error aligning to next PE string table value: %w", err)
|
||||
}
|
||||
|
||||
var value string
|
||||
if stringHeader.ValueLength > 0 {
|
||||
value = readUTF16(reader, &offset, &stOffset)
|
||||
}
|
||||
|
||||
fields[key] = value
|
||||
|
||||
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error aligning to next PE string table key: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fields["FileVersion"] == "" {
|
||||
// we can derive the file version from the fixed file info if it is not already specified as a string entry... neat!
|
||||
fields["FileVersion"] = fmt.Sprintf("%d.%d.%d.%d",
|
||||
fixedFileInfo.FileVersionMS>>16, fixedFileInfo.FileVersionMS&0xFFFF,
|
||||
fixedFileInfo.FileVersionLS>>16, fixedFileInfo.FileVersionLS&0xFFFF)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readIntoStructAndSzKey reads a struct from the reader and updates the offsets if provided, returning the szKey value.
|
||||
// This is only useful in the context of the resource directory parsing in narrow cases (this is invalid to use outside of that context).
|
||||
func readIntoStructAndSzKey[T any](reader *bytes.Reader, data *T, offsets ...*int) (string, error) {
|
||||
if err := readIntoStruct(reader, data, offsets...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return readUTF16(reader, offsets...), nil
|
||||
}
|
||||
|
||||
// readIntoStruct reads a struct from the reader and updates the offsets if provided.
|
||||
func readIntoStruct[T any](reader io.Reader, data *T, offsets ...*int) error {
|
||||
if err := binary.Read(reader, binary.LittleEndian, data); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range offsets {
|
||||
*offsets[i] += binary.Size(*data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// alignAndSeek aligns the reader to the next DWORD boundary and seeks to the new offset (updating any provided trackOffsets).
|
||||
func alignAndSeek(reader io.Seeker, offset *int, trackOffsets ...*int) error {
|
||||
ogOffset := *offset
|
||||
*offset = alignToDWORD(*offset)
|
||||
diff := *offset - ogOffset
|
||||
for i := range trackOffsets {
|
||||
*trackOffsets[i] += diff
|
||||
}
|
||||
_, err := reader.Seek(int64(*offset), io.SeekStart)
|
||||
return err
|
||||
}
|
||||
|
||||
// alignToDWORD aligns the offset to the next DWORD boundary (4 byte boundary)
|
||||
func alignToDWORD(offset int) int {
|
||||
return (offset + 3) & ^3
|
||||
}
|
||||
|
||||
// readUTF16 is a helper function to read a null-terminated UTF16 string
|
||||
func readUTF16(reader *bytes.Reader, offsets ...*int) string {
|
||||
startPos, err := reader.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var result []rune
|
||||
for {
|
||||
var char uint16
|
||||
err := binary.Read(reader, binary.LittleEndian, &char)
|
||||
if err != nil || char == 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, rune(char))
|
||||
}
|
||||
|
||||
// calculate how many bytes we've actually read (including null terminator)
|
||||
endPos, _ := reader.Seek(0, io.SeekCurrent)
|
||||
bytesRead := int(endPos - startPos)
|
||||
|
||||
for i := range offsets {
|
||||
*offsets[i] += bytesRead
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// readUTF16WithLength reads a length-prefixed UTF-16 string from reader.
|
||||
// The first 2 bytes represent the number of UTF-16 code units.
|
||||
func readUTF16WithLength(reader *bytes.Reader) (string, error) {
|
||||
var length uint16
|
||||
if err := binary.Read(reader, binary.LittleEndian, &length); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if length == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// read length UTF-16 code units.
|
||||
codes := make([]uint16, length)
|
||||
if err := binary.Read(reader, binary.LittleEndian, &codes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(utf16.Decode(codes)), nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dotnet
|
||||
package pe
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
func Test_getLogicalDotnetPE(t *testing.T) {
|
||||
func Test_Read_DotNetDetection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
@@ -126,7 +126,7 @@ func Test_getLogicalDotnetPE(t *testing.T) {
|
||||
|
||||
reader := fixtureFile(t, tt.fixture, tt.path)
|
||||
|
||||
got, err := getLogicalDotnetPE(reader)
|
||||
got, err := Read(reader)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -136,7 +136,7 @@ func Test_getLogicalDotnetPE(t *testing.T) {
|
||||
t.Errorf("unexpected version resources (-want +got): %s", d)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantCLR, got.CLR.hasEvidenceOfCLR())
|
||||
assert.Equal(t, tt.wantCLR, got.CLR.HasEvidenceOfCLR())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../dotnet/test-fixtures/image-net8-app
|
||||
@@ -0,0 +1 @@
|
||||
../../../dotnet/test-fixtures/image-net8-app-single-file
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/filesource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
@@ -96,6 +97,10 @@ func (p *CatalogTester) WithContext(ctx context.Context) *CatalogTester {
|
||||
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
if path == "" {
|
||||
return p
|
||||
}
|
||||
|
||||
s, err := directorysource.NewFromPath(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -106,9 +111,25 @@ func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *CatalogTester) FromFileSource(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
s, err := filesource.NewFromPath(path)
|
||||
require.NoError(t, err)
|
||||
resolver, err := s.FileResolver(source.AllLayersScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
p.resolver = resolver
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
if path == "" {
|
||||
return p
|
||||
}
|
||||
|
||||
fixture, err := os.Open(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -157,6 +178,11 @@ func (p *CatalogTester) WithResolver(r file.Resolver) *CatalogTester {
|
||||
|
||||
func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
if fixtureName == "" {
|
||||
return p
|
||||
}
|
||||
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName)
|
||||
|
||||
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
@@ -327,6 +353,11 @@ func TestCataloger(t *testing.T, fixtureDir string, cataloger pkg.Cataloger, exp
|
||||
NewCatalogTester().FromDirectory(t, fixtureDir).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
|
||||
}
|
||||
|
||||
func TestCatalogerFromFileSource(t *testing.T, fixturePath string, cataloger pkg.Cataloger, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
|
||||
t.Helper()
|
||||
NewCatalogTester().FromFileSource(t, fixturePath).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
|
||||
}
|
||||
|
||||
func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Parser, env *generic.Environment, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -215,3 +215,111 @@ func TestJvmDistributionCataloger(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJvmDistributionCatalogerFromFile(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "valid 1.8.0",
|
||||
fixture: "test-fixtures/jvm-installs/oracle-jdk-se-8/usr/lib/jvm/jdk-1.8-oracle-x64/release",
|
||||
expected: pkg.Package{
|
||||
Name: "jdk",
|
||||
Version: "1.8.0_411-b25",
|
||||
FoundBy: "java-jvm-cataloger",
|
||||
Locations: file.NewLocationSet(file.NewLocation("/release")),
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Type: pkg.BinaryPkg,
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:oracle:java_se:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
|
||||
cpe.Must("cpe:2.3:a:oracle:jre:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
|
||||
cpe.Must("cpe:2.3:a:oracle:jdk:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
|
||||
},
|
||||
PURL: "pkg:generic/oracle/jdk@1.8.0_411-b25",
|
||||
Metadata: pkg.JavaVMInstallation{
|
||||
Release: pkg.JavaVMRelease{
|
||||
JavaRuntimeVersion: "1.8.0_411-b25",
|
||||
JavaVersion: "1.8.0_411",
|
||||
OsArch: "amd64",
|
||||
OsName: "Linux",
|
||||
OsVersion: "2.6",
|
||||
Source: ".:git:71ec2089cf8c+",
|
||||
BuildType: "commercial",
|
||||
},
|
||||
Files: []string{
|
||||
"/release",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid post-jep223",
|
||||
fixture: "test-fixtures/jvm-installs/valid-post-jep223/jvm/openjdk/release",
|
||||
expected: pkg.Package{
|
||||
Name: "openjdk",
|
||||
Version: "21.0.4+7-LTS",
|
||||
FoundBy: "java-jvm-cataloger",
|
||||
Locations: file.NewLocationSet(file.NewLocation("/release")),
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Type: pkg.BinaryPkg,
|
||||
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:oracle:openjdk:21.0.4:*:*:*:*:*:*:*", cpe.DeclaredSource)},
|
||||
PURL: "pkg:generic/oracle/openjdk@21.0.4%2B7-LTS?repository_url=https%3A%2F%2Fgithub.com%2Fadoptium%2Fjdk21u.git",
|
||||
Metadata: pkg.JavaVMInstallation{
|
||||
Release: pkg.JavaVMRelease{
|
||||
Implementor: "Eclipse Adoptium",
|
||||
ImplementorVersion: "Temurin-21.0.4+7",
|
||||
JavaRuntimeVersion: "21.0.4+7-LTS",
|
||||
JavaVersion: "21.0.4",
|
||||
JavaVersionDate: "2024-07-16",
|
||||
Libc: "gnu",
|
||||
Modules: []string{
|
||||
"java.base", "java.compiler", "java.datatransfer", "java.xml", "java.prefs",
|
||||
"java.desktop", "java.instrument", "java.logging", "java.management",
|
||||
"java.security.sasl", "java.naming", "java.rmi", "java.management.rmi",
|
||||
"java.net.http", "java.scripting", "java.security.jgss",
|
||||
"java.transaction.xa", "java.sql", "java.sql.rowset", "java.xml.crypto", "java.se",
|
||||
"java.smartcardio", "jdk.accessibility", "jdk.internal.jvmstat", "jdk.attach",
|
||||
"jdk.charsets", "jdk.internal.opt", "jdk.zipfs", "jdk.compiler", "jdk.crypto.ec",
|
||||
"jdk.crypto.cryptoki", "jdk.dynalink", "jdk.internal.ed", "jdk.editpad", "jdk.hotspot.agent",
|
||||
"jdk.httpserver", "jdk.incubator.vector", "jdk.internal.le", "jdk.internal.vm.ci",
|
||||
"jdk.internal.vm.compiler", "jdk.internal.vm.compiler.management", "jdk.jartool",
|
||||
"jdk.javadoc", "jdk.jcmd", "jdk.management", "jdk.management.agent", "jdk.jconsole",
|
||||
"jdk.jdeps", "jdk.jdwp.agent", "jdk.jdi", "jdk.jfr", "jdk.jlink", "jdk.jpackage", "jdk.jshell",
|
||||
"jdk.jsobject", "jdk.jstatd", "jdk.localedata", "jdk.management.jfr", "jdk.naming.dns",
|
||||
"jdk.naming.rmi", "jdk.net", "jdk.nio.mapmode", "jdk.random", "jdk.sctp", "jdk.security.auth",
|
||||
"jdk.security.jgss", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom",
|
||||
},
|
||||
OsArch: "aarch64",
|
||||
OsName: "Linux",
|
||||
Source: ".:git:13710926b798",
|
||||
BuildSource: "git:1271f10a26c47e1489a814dd2731f936a588d621",
|
||||
BuildSourceRepo: "https://github.com/adoptium/temurin-build.git",
|
||||
SourceRepo: "https://github.com/adoptium/jdk21u.git",
|
||||
FullVersion: "21.0.4+7-LTS",
|
||||
SemanticVersion: "21.0.4+7",
|
||||
BuildInfo: "OS: Linux Version: 5.4.0-150-generic",
|
||||
JvmVariant: "Hotspot",
|
||||
JvmVersion: "21.0.4+7-LTS",
|
||||
ImageType: "JDK",
|
||||
},
|
||||
Files: []string{
|
||||
"/release",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := tt.expected
|
||||
p.SetID()
|
||||
|
||||
pkgtest.TestCatalogerFromFileSource(t, tt.fixture, NewJvmDistributionCataloger(), []pkg.Package{p}, nil)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -88,7 +88,10 @@ func parseJVMRelease(_ context.Context, resolver file.Resolver, _ *generic.Envir
|
||||
installDir := path.Dir(reader.Path())
|
||||
files, hasJdk := findJvmFiles(resolver, installDir)
|
||||
|
||||
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, reader.Path(), ri.ImageType, hasJdk)
|
||||
// the reason we use the reference to get the real path is in cases where the file cataloger is involved
|
||||
// (thus the real path is not available except for in the original file reference). This is important
|
||||
// since the path is critical for distinguishing between different JVM vendors.
|
||||
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, string(reader.Reference().RealPath), ri.ImageType, hasJdk)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: product,
|
||||
|
||||
@@ -11,5 +11,5 @@ import (
|
||||
// NewOpamPackageManagerCataloger returns a new cataloger object for OCaml opam.
|
||||
func NewOpamPackageManagerCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("opam-cataloger").
|
||||
WithParserByGlobs(parseOpamPackage, "*opam")
|
||||
WithParserByGlobs(parseOpamPackage, "**/*opam")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
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(match, p.name),
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package php
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_InterpreterCataloger(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expectedPkgs []string
|
||||
expectedRels []string
|
||||
}{
|
||||
{
|
||||
name: "native installation with extensions",
|
||||
fixture: "image-extensions",
|
||||
expectedPkgs: []string{
|
||||
// interpreters
|
||||
"php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
|
||||
// extensions
|
||||
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so)",
|
||||
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so)",
|
||||
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so)",
|
||||
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so)",
|
||||
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so)",
|
||||
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so)",
|
||||
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so)",
|
||||
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so)",
|
||||
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so)",
|
||||
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so)",
|
||||
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so)",
|
||||
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so)",
|
||||
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so)",
|
||||
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so)",
|
||||
},
|
||||
expectedRels: []string{
|
||||
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"bcmath @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/bcmath.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"exif @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/exif.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"ftp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ftp.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"gd @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gd.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"gmp @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/gmp.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"intl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"ldap @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/ldap.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"opcache @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/opcache.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"pcntl @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pcntl.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"pdo_mysql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_mysql.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"pdo_pgsql @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/pdo_pgsql.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"sodium @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sodium.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"sysvsem @ 8.3.21 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/sysvsem.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so) [dependency-of] php-cli @ 8.3.21 (/usr/local/bin/php)",
|
||||
"zip @ 1.22.3 (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/zip.so) [dependency-of] php-fpm @ 8.3.21 (/usr/local/sbin/php-fpm)",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apache installation with libphp and extensions",
|
||||
fixture: "image-apache",
|
||||
expectedPkgs: []string{
|
||||
// interpreters
|
||||
"libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
|
||||
// extensions
|
||||
"calendar @ 8.2.28 (/usr/lib/php/20220829/calendar.so)",
|
||||
"ctype @ 8.2.28 (/usr/lib/php/20220829/ctype.so)",
|
||||
"exif @ 8.2.28 (/usr/lib/php/20220829/exif.so)",
|
||||
"ffi @ 8.2.28 (/usr/lib/php/20220829/ffi.so)",
|
||||
"fileinfo @ 8.2.28 (/usr/lib/php/20220829/fileinfo.so)",
|
||||
"ftp @ 8.2.28 (/usr/lib/php/20220829/ftp.so)",
|
||||
"gettext @ 8.2.28 (/usr/lib/php/20220829/gettext.so)",
|
||||
"iconv @ 8.2.28 (/usr/lib/php/20220829/iconv.so)",
|
||||
"mysqli @ 8.2.28 (/usr/lib/php/20220829/mysqli.so)",
|
||||
"opcache @ 8.2.28 (/usr/lib/php/20220829/opcache.so)",
|
||||
"pdo @ 8.2.28 (/usr/lib/php/20220829/pdo.so)",
|
||||
"pdo_mysql @ 8.2.28 (/usr/lib/php/20220829/pdo_mysql.so)",
|
||||
"phar @ 8.2.28 (/usr/lib/php/20220829/phar.so)",
|
||||
"posix @ 8.2.28 (/usr/lib/php/20220829/posix.so)",
|
||||
"readline @ 8.2.28 (/usr/lib/php/20220829/readline.so)",
|
||||
"shmop @ 8.2.28 (/usr/lib/php/20220829/shmop.so)",
|
||||
"simplexml @ 8.2.28 (/usr/lib/php/20220829/simplexml.so)",
|
||||
"sockets @ 8.2.28 (/usr/lib/php/20220829/sockets.so)",
|
||||
"sysvmsg @ 8.2.28 (/usr/lib/php/20220829/sysvmsg.so)",
|
||||
"sysvsem @ 8.2.28 (/usr/lib/php/20220829/sysvsem.so)",
|
||||
"sysvshm @ 8.2.28 (/usr/lib/php/20220829/sysvshm.so)",
|
||||
"tokenizer @ 8.2.28 (/usr/lib/php/20220829/tokenizer.so)",
|
||||
"xml @ 8.2.28 (/usr/lib/php/20220829/xml.so)",
|
||||
"xmlreader @ 8.2.28 (/usr/lib/php/20220829/xmlreader.so)",
|
||||
"xmlwriter @ 8.2.28 (/usr/lib/php/20220829/xmlwriter.so)",
|
||||
"xsl @ 8.2.28 (/usr/lib/php/20220829/xsl.so)",
|
||||
},
|
||||
expectedRels: []string{
|
||||
"calendar @ 8.2.28 (/usr/lib/php/20220829/calendar.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ctype @ 8.2.28 (/usr/lib/php/20220829/ctype.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"exif @ 8.2.28 (/usr/lib/php/20220829/exif.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ffi @ 8.2.28 (/usr/lib/php/20220829/ffi.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"fileinfo @ 8.2.28 (/usr/lib/php/20220829/fileinfo.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"ftp @ 8.2.28 (/usr/lib/php/20220829/ftp.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"gettext @ 8.2.28 (/usr/lib/php/20220829/gettext.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"iconv @ 8.2.28 (/usr/lib/php/20220829/iconv.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"mysqli @ 8.2.28 (/usr/lib/php/20220829/mysqli.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"opcache @ 8.2.28 (/usr/lib/php/20220829/opcache.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"pdo @ 8.2.28 (/usr/lib/php/20220829/pdo.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"pdo_mysql @ 8.2.28 (/usr/lib/php/20220829/pdo_mysql.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"phar @ 8.2.28 (/usr/lib/php/20220829/phar.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"posix @ 8.2.28 (/usr/lib/php/20220829/posix.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"readline @ 8.2.28 (/usr/lib/php/20220829/readline.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"shmop @ 8.2.28 (/usr/lib/php/20220829/shmop.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"simplexml @ 8.2.28 (/usr/lib/php/20220829/simplexml.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sockets @ 8.2.28 (/usr/lib/php/20220829/sockets.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvmsg @ 8.2.28 (/usr/lib/php/20220829/sysvmsg.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvsem @ 8.2.28 (/usr/lib/php/20220829/sysvsem.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"sysvshm @ 8.2.28 (/usr/lib/php/20220829/sysvshm.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"tokenizer @ 8.2.28 (/usr/lib/php/20220829/tokenizer.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xml @ 8.2.28 (/usr/lib/php/20220829/xml.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xmlreader @ 8.2.28 (/usr/lib/php/20220829/xmlreader.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xmlwriter @ 8.2.28 (/usr/lib/php/20220829/xmlwriter.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
"xsl @ 8.2.28 (/usr/lib/php/20220829/xsl.so) [dependency-of] libphp @ 8.2.28 (/usr/lib/apache2/modules/libphp8.2.so)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := NewInterpreterCataloger()
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, tt.fixture).
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
//Expects(tt.expected, nil).
|
||||
ExpectsPackageStrings(tt.expectedPkgs).
|
||||
ExpectsRelationshipStrings(tt.expectedRels).
|
||||
TestCataloger(t, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM --platform=linux/amd64 httpd:2.4.63-bookworm AS builder
|
||||
|
||||
RUN apt update -y && apt install -y libapache2-mod-php php8.2-memcache php8.2-memcache php8.2-xml php8.2-mysqli php8.2-opcache
|
||||
|
||||
FROM busybox:latest
|
||||
|
||||
# phplib.so
|
||||
COPY --from=builder /usr/lib/apache2/ /usr/lib/apache2/
|
||||
|
||||
# php extensions
|
||||
COPY --from=builder /usr/lib/php/ /usr/lib/php/
|
||||
@@ -0,0 +1,105 @@
|
||||
# source https://github.com/nextcloud/docker/blob/master/30/fpm-alpine/Dockerfile#L1
|
||||
FROM --platform=linux/amd64 php:8.3-fpm-alpine3.21 AS builder
|
||||
|
||||
# entrypoint.sh and cron.sh dependencies
|
||||
RUN set -ex; \
|
||||
\
|
||||
apk add --no-cache \
|
||||
imagemagick \
|
||||
imagemagick-pdf \
|
||||
imagemagick-jpeg \
|
||||
imagemagick-raw \
|
||||
imagemagick-tiff \
|
||||
imagemagick-heic \
|
||||
imagemagick-webp \
|
||||
imagemagick-svg \
|
||||
rsync \
|
||||
; \
|
||||
\
|
||||
rm /var/spool/cron/crontabs/root; \
|
||||
echo '*/5 * * * * php -f /var/www/html/cron.php' > /var/spool/cron/crontabs/www-data
|
||||
|
||||
# install the PHP extensions we need
|
||||
# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html
|
||||
RUN set -ex; \
|
||||
\
|
||||
apk add --no-cache --virtual .build-deps \
|
||||
$PHPIZE_DEPS \
|
||||
autoconf \
|
||||
freetype-dev \
|
||||
gmp-dev \
|
||||
icu-dev \
|
||||
imagemagick-dev \
|
||||
libevent-dev \
|
||||
libjpeg-turbo-dev \
|
||||
libmcrypt-dev \
|
||||
libmemcached-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
libxml2-dev \
|
||||
libzip-dev \
|
||||
openldap-dev \
|
||||
pcre-dev \
|
||||
postgresql-dev \
|
||||
; \
|
||||
\
|
||||
docker-php-ext-configure ftp --with-openssl-dir=/usr; \
|
||||
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \
|
||||
docker-php-ext-configure ldap; \
|
||||
docker-php-ext-install -j "$(nproc)" \
|
||||
bcmath \
|
||||
exif \
|
||||
ftp \
|
||||
gd \
|
||||
gmp \
|
||||
intl \
|
||||
ldap \
|
||||
opcache \
|
||||
pcntl \
|
||||
pdo_mysql \
|
||||
pdo_pgsql \
|
||||
sysvsem \
|
||||
zip \
|
||||
; \
|
||||
\
|
||||
# pecl will claim success even if one install fails, so we need to perform each install separately
|
||||
pecl install APCu-5.1.24; \
|
||||
pecl install igbinary-3.2.16; \
|
||||
pecl install imagick-3.8.0; \
|
||||
pecl install memcached-3.3.0 \
|
||||
--configureoptions 'enable-memcached-igbinary="yes"'; \
|
||||
pecl install redis-6.2.0 \
|
||||
--configureoptions 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"'; \
|
||||
\
|
||||
docker-php-ext-enable \
|
||||
apcu \
|
||||
igbinary \
|
||||
imagick \
|
||||
memcached \
|
||||
redis \
|
||||
; \
|
||||
rm -r /tmp/pear; \
|
||||
\
|
||||
runDeps="$( \
|
||||
scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \
|
||||
| tr ',' '\n' \
|
||||
| sort -u \
|
||||
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
|
||||
)"; \
|
||||
apk add --no-network --virtual .nextcloud-phpext-rundeps $runDeps; \
|
||||
apk del --no-network .build-deps
|
||||
|
||||
FROM busybox:latest
|
||||
|
||||
# interpreters + process manager
|
||||
COPY --from=builder /usr/local/sbin/php-fpm /usr/local/sbin/php-fpm
|
||||
COPY --from=builder /usr/local/bin/php /usr/local/bin/php
|
||||
|
||||
# extensions
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
|
||||
# configs
|
||||
COPY --from=builder /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.conf
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php-fpm.d /usr/local/etc/php-fpm.d
|
||||
COPY --from=builder /usr/local/etc/php-fpm.conf /usr/local/etc/php-fpm.conf
|
||||
@@ -716,10 +716,15 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
|
||||
"certifi @ 2020.12.5 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"certifi @ 2020.12.5 (/usr/local/lib/python3.9/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
|
||||
"chardet @ 3.0.4 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"distlib @ 0.3.9 (/usr/local/lib/python3.9/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"filelock @ 3.18.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"idna @ 2.10 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"idna @ 2.10 (/usr/local/lib/python3.9/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
|
||||
"platformdirs @ 4.3.8 (/usr/local/lib/python3.9/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"six @ 1.16.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"soupsieve @ 2.2.1 (/usr/local/lib/python3.9/dist-packages) [dependency-of] beautifulsoup4 @ 4.9.3 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"urllib3 @ 1.26.18 (/usr/local/lib/python3.9/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.9/dist-packages)",
|
||||
"virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages) [dependency-of] filelock @ 3.18.0 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "testing", but we are installing it
|
||||
"wcwidth @ 0.2.13 (/usr/local/lib/python3.9/dist-packages) [dependency-of] blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages)",
|
||||
|
||||
// purely python 3.8 dist-packages
|
||||
@@ -736,9 +741,14 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
|
||||
"certifi @ 2020.12.5 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"certifi @ 2020.12.5 (/usr/local/lib/python3.8/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.8/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
|
||||
"chardet @ 3.0.4 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"distlib @ 0.3.9 (/usr/local/lib/python3.8/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"filelock @ 3.16.1 (/usr/local/lib/python3.8/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"idna @ 2.10 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"idna @ 2.10 (/usr/local/lib/python3.8/dist-packages) [dependency-of] urllib3 @ 1.26.18 (/usr/local/lib/python3.8/dist-packages)", // available when extra == "secure", but another dependency is primarily installing it
|
||||
"platformdirs @ 4.3.6 (/usr/local/lib/python3.8/dist-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"soupsieve @ 2.2 (/usr/local/lib/python3.8/dist-packages) [dependency-of] beautifulsoup4 @ 4.9.2 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"urllib3 @ 1.26.18 (/usr/local/lib/python3.8/dist-packages) [dependency-of] requests @ 2.25.0 (/usr/local/lib/python3.8/dist-packages)",
|
||||
"virtualenv @ 20.31.2 (/usr/local/lib/python3.8/dist-packages) [dependency-of] filelock @ 3.16.1 (/usr/local/lib/python3.8/dist-packages)", // available when extra == "testing", but we are installing it
|
||||
"xmod @ 1.8.1 (/usr/local/lib/python3.8/dist-packages) [dependency-of] runs @ 1.2.2 (/usr/local/lib/python3.8/dist-packages)",
|
||||
|
||||
// project 1 virtual env
|
||||
@@ -758,7 +768,8 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
|
||||
// Note: we'll only see new relationships, so any relationship where there is at least one new player (in FROM or TO)
|
||||
"blessed @ 1.20.0 (/usr/local/lib/python3.9/dist-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)", // note: depends on global site package!
|
||||
"python-editor @ 1.0.4 (/usr/local/lib/python3.9/dist-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)", // note: depends on global site package!
|
||||
"readchar @ 4.2.0 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)",
|
||||
"readchar @ 4.2.1 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] inquirer @ 3.0.0 (/app/project1/venv/lib/python3.9/site-packages)",
|
||||
"setuptools @ 44.0.0 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] virtualenv @ 20.31.2 (/usr/local/lib/python3.9/dist-packages)", // available when extra == "test", but we are installing it
|
||||
"soupsieve @ 2.3 (/app/project1/venv/lib/python3.9/site-packages) [dependency-of] beautifulsoup4 @ 4.10.0 (/app/project1/venv/lib/python3.9/site-packages)",
|
||||
|
||||
// project 2 virtual env
|
||||
|
||||
@@ -115,14 +115,16 @@ func wheelEggDependencySpecifier(p pkg.Package) dependency.Specification {
|
||||
}
|
||||
}
|
||||
|
||||
// extractPackageName removes any extras or version constraints from a given Requires-Dist field value (and
|
||||
// extractPackageName removes any extras, version constraints or environment markers from a given Requires-Dist field value (and
|
||||
// semantically similar fields), leaving only the package name.
|
||||
func extractPackageName(s string) string {
|
||||
// examples:
|
||||
// html5lib ; extra == 'html5lib' --> html5lib
|
||||
// soupsieve (>1.2) --> soupsieve
|
||||
// requests [security,tests] --> requests
|
||||
// requests >= 2.8.1 --> requests
|
||||
// requests (>= 2.8.1) --> requests
|
||||
// requests ; python_version < "2.7" --> requests
|
||||
|
||||
return strings.TrimSpace(internal.SplitAny(s, "(;")[0])
|
||||
return strings.TrimSpace(internal.SplitAny(s, "[(<!=>~;")[0])
|
||||
}
|
||||
func extractPackageNames(ss []string) []string {
|
||||
var names []string
|
||||
|
||||
@@ -13,10 +13,10 @@ RUN python3.9 -m pip install click==8.0.3 beautifulsoup4==4.9.3 soupsieve==2.2.1
|
||||
RUN python3.9 -m pip install six==1.16.0 wcwidth==0.2.13 blessed==1.20.0 python-editor==1.0.4 # total dependencies for inquirer in project1 (which is linked)
|
||||
RUN python3.9 -m pip install requests==2.25.0 certifi==2020.12.5 chardet==3.0.4 idna==2.10 urllib3==1.26.18 # total dependencies for requests
|
||||
|
||||
RUN python3.8 -m pip install readchar==4.2.1
|
||||
RUN python3.8 -m pip install click==8.0.2 beautifulsoup4==4.9.2 soupsieve==2.2.0 requests==2.25.0
|
||||
RUN python3.8 -m pip install runs==1.2.2 xmod==1.8.1 # partial dependencies for inquirer in project2 (which is a red herring)
|
||||
RUN python3.8 -m pip install requests==2.25.0 certifi==2020.12.5 chardet==3.0.4 idna==2.10 urllib3==1.26.18 # total dependencies for requests
|
||||
RUN python3.8 -m pip install readchar==4.2.0
|
||||
|
||||
# create directories for the two projects
|
||||
RUN mkdir -p /app/project1 /app/project2
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -29,14 +30,15 @@ func Test_DBCataloger(t *testing.T) {
|
||||
FoundBy: "rpm-db-cataloger",
|
||||
PURL: "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm",
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Name: "basesystem",
|
||||
Version: "11",
|
||||
Arch: "noarch",
|
||||
Release: "13.el9",
|
||||
SourceRpm: "basesystem-11-13.el9.src.rpm",
|
||||
Size: 0,
|
||||
Vendor: "Rocky Enterprise Software Foundation",
|
||||
Provides: []string{"basesystem"},
|
||||
Name: "basesystem",
|
||||
Version: "11",
|
||||
Arch: "noarch",
|
||||
Release: "13.el9",
|
||||
SourceRpm: "basesystem-11-13.el9.src.rpm",
|
||||
Size: 0,
|
||||
Vendor: "Rocky Enterprise Software Foundation",
|
||||
Signatures: mustParseSignatures(t, "RSA/SHA256, Wed May 11 11:12:32 2022, Key ID 702d426d350d275d"),
|
||||
Provides: []string{"basesystem"},
|
||||
Requires: []string{
|
||||
"filesystem",
|
||||
"rpmlib(CompressedFileNames)",
|
||||
@@ -65,6 +67,7 @@ func Test_DBCataloger(t *testing.T) {
|
||||
Release: "6.el9_1",
|
||||
SourceRpm: "bash-5.1.8-6.el9_1.src.rpm",
|
||||
Size: 7738634,
|
||||
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon Jan 23 22:49:22 2023, Key ID 702d426d350d275d"),
|
||||
ModularityLabel: strRef(""),
|
||||
Vendor: "Rocky Enterprise Software Foundation",
|
||||
Provides: []string{
|
||||
@@ -117,6 +120,7 @@ func Test_DBCataloger(t *testing.T) {
|
||||
Release: "2.el9",
|
||||
SourceRpm: "filesystem-3.16-2.el9.src.rpm",
|
||||
Size: 106,
|
||||
Signatures: mustParseSignatures(t, "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"),
|
||||
ModularityLabel: strRef(""),
|
||||
Vendor: "Rocky Enterprise Software Foundation",
|
||||
Provides: []string{
|
||||
@@ -334,3 +338,9 @@ func Test_denySelfReferences(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseSignatures(t testing.TB, sigs ...string) []pkg.RpmSignature {
|
||||
signatures, err := parseSignatures(sigs...)
|
||||
require.NoError(t, err)
|
||||
return signatures
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package redhat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
||||
"github.com/sassoftware/go-rpmutils"
|
||||
|
||||
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -15,6 +20,42 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
type pgpSig struct {
|
||||
_ [3]byte
|
||||
Date int32
|
||||
KeyID [8]byte
|
||||
PubKeyAlgo uint8
|
||||
HashAlgo uint8
|
||||
}
|
||||
|
||||
type textSig struct {
|
||||
_ [2]byte
|
||||
PubKeyAlgo uint8
|
||||
HashAlgo uint8
|
||||
_ [4]byte
|
||||
Date int32
|
||||
_ [4]byte
|
||||
KeyID [8]byte
|
||||
}
|
||||
|
||||
type pgp4Sig struct {
|
||||
_ [2]byte
|
||||
PubKeyAlgo uint8
|
||||
HashAlgo uint8
|
||||
_ [17]byte
|
||||
KeyID [8]byte
|
||||
_ [2]byte
|
||||
Date int32
|
||||
}
|
||||
|
||||
var pubKeyLookup = map[uint8]string{
|
||||
0x01: "RSA",
|
||||
}
|
||||
var hashLookup = map[uint8]string{
|
||||
0x02: "SHA1",
|
||||
0x08: "SHA256",
|
||||
}
|
||||
|
||||
// parseRpmArchive parses a single RPM
|
||||
func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
rpm, err := rpmutils.ReadRpm(reader)
|
||||
@@ -44,21 +85,140 @@ func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen
|
||||
files, err := rpm.Header.GetFiles()
|
||||
logRpmArchiveErr(reader.Location, "files", err)
|
||||
|
||||
rsa, err := rpm.Header.GetBytes(rpmutils.SIG_RSA)
|
||||
logRpmArchiveErr(reader.Location, "rsa signature", err)
|
||||
|
||||
pgp, err := rpm.Header.GetBytes(rpmutils.SIG_PGP)
|
||||
logRpmArchiveErr(reader.Location, "pgp signature", err)
|
||||
|
||||
var allSigs [][]byte
|
||||
allSigs = append(allSigs, rsa)
|
||||
allSigs = append(allSigs, pgp)
|
||||
sigs, err := parseSignatureHeaders(allSigs)
|
||||
logRpmArchiveErr(reader.Location, "signature", err)
|
||||
|
||||
metadata := pkg.RpmArchive{
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
Epoch: parseEpoch(nevra.Epoch),
|
||||
Arch: nevra.Arch,
|
||||
Release: nevra.Release,
|
||||
SourceRpm: sourceRpm,
|
||||
Vendor: vendor,
|
||||
Size: int(size),
|
||||
Files: mapFiles(files, digestAlgorithm),
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
Epoch: parseEpoch(nevra.Epoch),
|
||||
Arch: nevra.Arch,
|
||||
Release: nevra.Release,
|
||||
SourceRpm: sourceRpm,
|
||||
Signatures: sigs,
|
||||
Vendor: vendor,
|
||||
Size: int(size),
|
||||
Files: mapFiles(files, digestAlgorithm),
|
||||
}
|
||||
|
||||
return []pkg.Package{newArchivePackage(ctx, reader.Location, metadata, licenses)}, nil, nil
|
||||
}
|
||||
|
||||
func parseSignatureHeaders(data [][]byte) ([]pkg.RpmSignature, error) {
|
||||
sigMap := make(map[string]pkg.RpmSignature)
|
||||
var keys []string
|
||||
for _, sig := range data {
|
||||
if len(sig) == 0 {
|
||||
continue
|
||||
}
|
||||
s, err := parsePGP(sig)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to parse RPM archive signature")
|
||||
return nil, err
|
||||
}
|
||||
k := s.String()
|
||||
if _, ok := sigMap[k]; ok {
|
||||
// if we have a duplicate signature, just skip it
|
||||
continue
|
||||
}
|
||||
sigMap[k] = *s
|
||||
keys = append(keys, k)
|
||||
}
|
||||
var signatures []pkg.RpmSignature
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
signatures = append(signatures, sigMap[k])
|
||||
}
|
||||
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
func parsePGP(data []byte) (*pkg.RpmSignature, error) {
|
||||
var tag, signatureType, version uint8
|
||||
|
||||
r := bytes.NewReader(data)
|
||||
err := binary.Read(r, binary.BigEndian, &tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(r, binary.BigEndian, &signatureType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(r, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch signatureType {
|
||||
case 0x01:
|
||||
switch version {
|
||||
case 0x1c:
|
||||
sig := textSig{}
|
||||
err = binary.Read(r, binary.BigEndian, &sig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
|
||||
}
|
||||
return &pkg.RpmSignature{
|
||||
PublicKeyAlgorithm: pubKeyLookup[sig.PubKeyAlgo],
|
||||
HashAlgorithm: hashLookup[sig.HashAlgo],
|
||||
Created: time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006"),
|
||||
IssuerKeyID: fmt.Sprintf("%x", sig.KeyID),
|
||||
}, nil
|
||||
default:
|
||||
return decodePGPSig(version, r)
|
||||
}
|
||||
case 0x02:
|
||||
return decodePGPSig(version, r)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown signature type: %d", signatureType)
|
||||
}
|
||||
|
||||
func decodePGPSig(version uint8, r io.Reader) (*pkg.RpmSignature, error) {
|
||||
var pubKeyAlgo, hashAlgo, pkgDate string
|
||||
var keyID [8]byte
|
||||
|
||||
switch {
|
||||
case version > 0x15:
|
||||
sig := pgp4Sig{}
|
||||
err := binary.Read(r, binary.BigEndian, &sig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PGP v4 signature on decode: %w", err)
|
||||
}
|
||||
pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
|
||||
hashAlgo = hashLookup[sig.HashAlgo]
|
||||
pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
|
||||
keyID = sig.KeyID
|
||||
|
||||
default:
|
||||
sig := pgpSig{}
|
||||
err := binary.Read(r, binary.BigEndian, &sig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
|
||||
}
|
||||
pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
|
||||
hashAlgo = hashLookup[sig.HashAlgo]
|
||||
pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
|
||||
keyID = sig.KeyID
|
||||
}
|
||||
return &pkg.RpmSignature{
|
||||
PublicKeyAlgorithm: pubKeyAlgo,
|
||||
HashAlgorithm: hashAlgo,
|
||||
Created: pkgDate,
|
||||
IssuerKeyID: fmt.Sprintf("%x", keyID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getDigestAlgorithm(location file.Location, header *rpmutils.RpmHeader) string {
|
||||
digestAlgorithm, err := header.GetString(rpmutils.FILEDIGESTALGO)
|
||||
logRpmArchiveErr(location, "file digest algo", err)
|
||||
@@ -109,6 +269,6 @@ func parseEpoch(epoch string) *int {
|
||||
|
||||
func logRpmArchiveErr(location file.Location, operation string, err error) {
|
||||
if err != nil {
|
||||
log.Debugf("ERROR in parse_rpm_archive %s file: %s: %v", operation, location.RealPath, err)
|
||||
log.WithFields("error", err, "operation", operation, "path", location.RealPath).Trace("unable to parse RPM archive")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,14 @@ package redhat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
@@ -14,11 +20,15 @@ func TestParseRpmFiles(t *testing.T) {
|
||||
abcRpmLocation := file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm")
|
||||
zorkRpmLocation := file.NewLocation("zork-1.0.3-1.el7.x86_64.rpm")
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
name string
|
||||
fixtureDir string
|
||||
fixtureImage string
|
||||
skipFiles bool
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/rpms",
|
||||
name: "go case",
|
||||
fixtureDir: "test-fixtures/rpms",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "abc",
|
||||
@@ -37,8 +47,16 @@ func TestParseRpmFiles(t *testing.T) {
|
||||
Release: "9.hg20160905.el7",
|
||||
Version: "1.01",
|
||||
SourceRpm: "abc-1.01-9.hg20160905.el7.src.rpm",
|
||||
Size: 17396,
|
||||
Vendor: "Fedora Project",
|
||||
Signatures: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Wed Sep 21 07:09:44 2016",
|
||||
IssuerKeyID: "6a2faea2352c64e5",
|
||||
},
|
||||
},
|
||||
Size: 17396,
|
||||
Vendor: "Fedora Project",
|
||||
Files: []pkg.RpmFileRecord{
|
||||
{"/usr/bin/abc", 33261, 7120, file.Digest{"sha256", "8f8495a65c66762b60afa0c3949d81b275ca6fa0601696caba5af762f455d0b9"}, "root", "root", ""},
|
||||
{"/usr/share/doc/abc-1.01", 16877, 4096, file.Digest{}, "root", "root", ""},
|
||||
@@ -66,7 +84,15 @@ func TestParseRpmFiles(t *testing.T) {
|
||||
Version: "1.0.3",
|
||||
SourceRpm: "zork-1.0.3-1.el7.src.rpm",
|
||||
Size: 262367,
|
||||
Vendor: "Fedora Project",
|
||||
Signatures: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Tue Mar 2 17:32:21 2021",
|
||||
IssuerKeyID: "6a2faea2352c64e5",
|
||||
},
|
||||
},
|
||||
Vendor: "Fedora Project",
|
||||
Files: []pkg.RpmFileRecord{
|
||||
{"/usr/bin/zork", 33261, 115440, file.Digest{"sha256", "31b2ffc20b676a8fff795a45308f584273b9c47e8f7e196b4f36220b2734b472"}, "root", "root", ""},
|
||||
{"/usr/share/doc/zork-1.0.3", 16877, 38, file.Digest{}, "root", "root", ""},
|
||||
@@ -83,20 +109,128 @@ func TestParseRpmFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/bad",
|
||||
name: "bad rpms",
|
||||
fixtureDir: "test-fixtures/bad",
|
||||
},
|
||||
{
|
||||
name: "rpms with signatures from RSA header",
|
||||
fixtureImage: "image-rpm-archive",
|
||||
skipFiles: true,
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "postgresql14-server",
|
||||
Version: "0:14.10-1PGDG.rhel9",
|
||||
PURL: "pkg:rpm/postgresql14-server@14.10-1PGDG.rhel9?arch=x86_64&epoch=0&upstream=postgresql14-14.10-1PGDG.rhel9.src.rpm",
|
||||
Locations: file.NewLocationSet(file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm")),
|
||||
FoundBy: "rpm-archive-cataloger",
|
||||
Type: pkg.RpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("PostgreSQL", file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm"))),
|
||||
Language: "",
|
||||
CPEs: nil,
|
||||
Metadata: pkg.RpmArchive{
|
||||
Name: "postgresql14-server",
|
||||
Version: "14.10",
|
||||
Epoch: ref(0),
|
||||
Arch: "x86_64",
|
||||
Release: "1PGDG.rhel9",
|
||||
SourceRpm: "postgresql14-14.10-1PGDG.rhel9.src.rpm",
|
||||
Size: 24521699,
|
||||
Signatures: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Tue Jan 2 16:45:56 2024",
|
||||
IssuerKeyID: "40bca2b408b40d20",
|
||||
},
|
||||
},
|
||||
Vendor: "PostgreSQL Global Development Group",
|
||||
// note: files are not asserted in this test
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var opts []cmp.Option
|
||||
if test.skipFiles {
|
||||
opts = append(opts, cmpopts.IgnoreFields(pkg.RpmArchive{}, "Files"))
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
WithCompareOptions(opts...).
|
||||
FromDirectory(t, test.fixtureDir).
|
||||
WithImageResolver(t, test.fixtureImage).
|
||||
IgnoreLocationLayer().
|
||||
Expects(test.expected, nil).
|
||||
TestCataloger(t, NewArchiveCataloger())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseRSA(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
want *pkg.RpmSignature
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "older RSA header",
|
||||
data: "89021503050058d3e39b0946fca2c105b9de0102b12a1000a2b3d347b51142e83b2de5e03ba9096f6330b72c140e46200d662b01c78534d14fab2ad4f07325119386830dd590219f27a22e420680283c500c40e6fba95404884b0a0abca8f198030ddc03653b7db2883b8230687e9e73d43eb5a24dbabfa48bbb3d1151ed264744e5e8ca169b0c4673a1440a9b99e53e693c9722f6423833cd7795e3044227fb922e21b7c007f03e923fae3f04d1ac2e8581e68c6790115b6dccfc02c8cb41681ed84785df086d6e26008c257d088a524ba2e7a7a5f41ad26b106c67b87fe48118b69662db612c23d2140059286f1ba7764627def6867ad0e11fe3a01fb1422dabe6f5cdf4cd876dc4fadfd2364bc3ba3758db94aaf3b82368cba65cf762287f713eb7ddc773acf93b083c739577a7eaf1f99e7dcbb8db1da050490e9fb67c838448db060a9e619d318c96f03e4363808d84ce29e8c102c290cc2bfab5746f3d9ddc9eb8b428f3ad2678abb2d46e846ddca7fc41322d76a97be6d416b4750f23320ec725e082be4496483b4cd3a3d2c515b3c8a6e27541139d809245140303877b84842ed2dd0454a78b2dfb7d6213784697077a8167942ebda5995a28d8256957e33e301706c35944ae05c7a54a4dd89be654d26cefa5cf0f616bbeaf317138371b09c5bbd5531f716020e553354ce5dbce3d9bb72f21e1857408dfd5a35250ff40f61ae1e25409ae9d21db76b8878341f4762a22be2189",
|
||||
// RSA/SHA1, Thu Mar 23 15:02:51 2017, Key ID 0946fca2c105b9de
|
||||
want: &pkg.RpmSignature{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA1",
|
||||
Created: "Thu Mar 23 15:02:51 2017",
|
||||
IssuerKeyID: "0946fca2c105b9de",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newer RSA header",
|
||||
data: "89024a04000108003416210421cb256ae16fc54c6e652949702d426d350d275d050262804369161c72656c656e6740726f636b796c696e75782e6f7267000a0910702d426d350d275dc8910ffd14f0f80297481fea648e7ba5a74bce10c5faccc2bbe588caece04be34d304a6a445538afc97a7033d43c983d27cc8f5ee515b2dd92f3e03354c413e55372a4d19386eb0f2354f9a26ee5fc2e56dfda49555e4a58b49279b70cd2036b04f28125f85942f640f2984e29e079f26bf6f76831d83d95983aa084a3e7b6327be2e23d0d799c4b4d1cfb36147ddfb782bf9df7b331d97f4f46b38f968b6130d87b0ef6bb0d424390fe34e38092babed37440569a93f55f50a2bdb58be0259f35badf7e728bd49824ed47f69fa53b6e26736bde4d8358d959b090e88054c3e179745dc7377e41b54b4e10223f4859e88162c7c5ec64b78d36cf8a914c1c2deb8c4f19a70d406e70756a89195d6aee488a9b40b9dbb76b2c38e528eb88d08ec35774a48ed9ce4e0dfac45cb7613ad5921f54c61d3aae5d7b3ab0e2e6ff867ac8f395b37af78b5c01022a4a4e62f7a99425fccb7439880cd6b393a3050b2e9512693bc36f6fe9de2921dda59710a1508965065244cf9f0f8cfc5bd554777f1a84d2249339234d62f2441249f617ad7df4fb01367a91d3a880e86fdb84bc6d03a127b44a28c6ceadef89e438db9640aa59b8a3f460b07272511f8187a5f3b163c8fd1caa61667401bce2ccdb1c176c46be10ef8033903132cca5889fa3661b2fba590c41fa1c104c08426677bdbf745a52ccd28f581960cf9d7e4ede3b9584aacb2f20ef93",
|
||||
// RSA/SHA256, Sun May 15 00:03:53 2022, Key ID 702d426d350d275d
|
||||
want: &pkg.RpmSignature{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Sun May 15 00:03:53 2022",
|
||||
IssuerKeyID: "702d426d350d275d",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "example from rocky9",
|
||||
data: "8901b304000108001d162104d4bf08ae67a0b4c7a1dbccd240bca2b408b40d20050265943dc4000a091040bca2b408b40d203b270bff71678ffeb190833a19a82112f59eee64cba186ab454d4526e0b3c8797e723f6916daff1b1f18cbf53c0da5d398a3a42065e79e5ca939f721652f38400dd4cac1107a902b1dae880649437ad0242444f3f07115172cae0a207b7cf8340af2f4a94976325f1dc165d5c2a564be322c4e130adb6217e7138b689f08898c407b223aa1ff8f8d592f31eba2256c02fae70ce4022d688a487972646b8bf1b518b5d6549c1e60fd812134422d9fdb41cf799f5eab80e48b4ab7cff84362dc867ed1af1416dd78e92bcc59217de7064b9a015d94a5097788689b9b6fbdeea679cfe4a6947f73dc3a6c810f2cb999d279b01564422d1500fc1bd8bd1eefa2d60660127ffef24067354660f93c0faf81f4edd599dd7e4b77fe4bff6c7a0ea83530c817c38d1f2364175883c6ef7b6dec86ad282bdd5138b8597567db96810c4ed6454a4ab1d98f0425dcd8892a5d46ed9289cb3ae3e1f1e2663d3e8188e873428f6cf7163563ed3860edc4fee81522389508847e692e2d13310eb4b40f7fdd7eb364a0b2dc",
|
||||
// RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20
|
||||
want: &pkg.RpmSignature{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Tue Jan 2 16:45:56 2024",
|
||||
IssuerKeyID: "40bca2b408b40d20",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = assert.NoError
|
||||
}
|
||||
|
||||
data, err := hex.DecodeString(tt.data)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := parsePGP(data)
|
||||
if !tt.wantErr(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ref[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func Test_corruptRpmArchive(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/bad/bad.rpm").
|
||||
|
||||
@@ -2,12 +2,13 @@ package redhat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
||||
|
||||
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -69,6 +70,14 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
|
||||
files, err := extractRpmFileRecords(resolver, *entry)
|
||||
errs = unknown.Join(errs, err)
|
||||
|
||||
// there is a period of time when RPM DB entries contain both PGP and RSA signatures that are the same.
|
||||
// This appears to be a holdover, where nowadays only the RSA Header is used.
|
||||
sigs, err := parseSignatures(strings.TrimSpace(entry.PGP), strings.TrimSpace(entry.RSAHeader))
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)).Trace("unable to parse signatures for package %s", entry.Name)
|
||||
sigs = nil
|
||||
}
|
||||
|
||||
metadata := pkg.RpmDBEntry{
|
||||
Name: entry.Name,
|
||||
Version: entry.Version,
|
||||
@@ -76,6 +85,7 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
|
||||
Arch: entry.Arch,
|
||||
Release: entry.Release,
|
||||
SourceRpm: entry.SourceRpm,
|
||||
Signatures: sigs,
|
||||
Vendor: entry.Vendor,
|
||||
Size: entry.Size,
|
||||
ModularityLabel: &entry.Modularitylabel,
|
||||
@@ -110,6 +120,70 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro
|
||||
return allPkgs, nil, errs
|
||||
}
|
||||
|
||||
func parseSignatures(sigs ...string) ([]pkg.RpmSignature, error) {
|
||||
var parsedSigs []pkg.RpmSignature
|
||||
var errs error
|
||||
for _, sig := range sigs {
|
||||
if sig == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(sig, ",")
|
||||
if len(parts) != 3 {
|
||||
errs = errors.Join(fmt.Errorf("invalid signature format: %s", sig))
|
||||
continue
|
||||
}
|
||||
|
||||
methodParts := strings.SplitN(strings.TrimSpace(parts[0]), "/", 2)
|
||||
if len(methodParts) != 2 {
|
||||
errs = errors.Join(fmt.Errorf("invalid signature method format: %s", parts[0]))
|
||||
continue
|
||||
}
|
||||
|
||||
pka := strings.TrimSpace(methodParts[0])
|
||||
hash := strings.TrimSpace(methodParts[1])
|
||||
|
||||
if pka == "" || hash == "" {
|
||||
errs = errors.Join(fmt.Errorf("invalid signature method values: public-key=%q hash=%q", pka, hash))
|
||||
continue
|
||||
}
|
||||
|
||||
created := strings.TrimSpace(parts[1])
|
||||
if created == "" {
|
||||
errs = errors.Join(fmt.Errorf("invalid signature created value: %q", parts[1]))
|
||||
continue
|
||||
}
|
||||
|
||||
issuerFields := strings.Split(strings.TrimSpace(parts[2]), " ")
|
||||
var issuer string
|
||||
switch len(issuerFields) {
|
||||
case 0:
|
||||
errs = errors.Join(fmt.Errorf("no signature issuer value: %q", parts[2]))
|
||||
case 1:
|
||||
issuer = issuerFields[0]
|
||||
default:
|
||||
issuer = issuerFields[len(issuerFields)-1]
|
||||
if issuer == "" {
|
||||
errs = errors.Join(fmt.Errorf("invalid signature issuer value: %q", parts[2]))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(issuer) < 5 {
|
||||
errs = errors.Join(fmt.Errorf("invalid signature issuer length: %q", parts[2]))
|
||||
continue
|
||||
}
|
||||
|
||||
parsedSig := pkg.RpmSignature{
|
||||
PublicKeyAlgorithm: pka,
|
||||
HashAlgorithm: hash,
|
||||
Created: created,
|
||||
IssuerKeyID: issuer,
|
||||
}
|
||||
parsedSigs = append(parsedSigs, parsedSig)
|
||||
}
|
||||
return parsedSigs, errs
|
||||
}
|
||||
|
||||
// The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version].
|
||||
// RPM version comparison depends on comparing at least the version and release fields together as a subset of the
|
||||
// naming scheme. This toELVersion function takes a RPM DB package information and converts it into a minimally comparable
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -222,6 +223,132 @@ func Test_corruptRpmDbEntry(t *testing.T) {
|
||||
TestParser(t, parseRpmDB)
|
||||
}
|
||||
|
||||
func TestParseSignatures(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sigs []string
|
||||
expected []pkg.RpmSignature
|
||||
expectedError require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "valid signature",
|
||||
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
|
||||
expected: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Mon May 16 12:32:55 2022",
|
||||
IssuerKeyID: "702d426d350d275d",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple valid signatures",
|
||||
sigs: []string{
|
||||
"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
|
||||
"DSA/SHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789",
|
||||
},
|
||||
expected: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Mon May 16 12:32:55 2022",
|
||||
IssuerKeyID: "702d426d350d275d",
|
||||
},
|
||||
{
|
||||
PublicKeyAlgorithm: "DSA",
|
||||
HashAlgorithm: "SHA1",
|
||||
Created: "Tue Jun 14 09:45:12 2023",
|
||||
IssuerKeyID: "123abc456def789",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no signatures",
|
||||
sigs: []string{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "empty signatures",
|
||||
sigs: []string{"", "", ""},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid parts count",
|
||||
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022"},
|
||||
expected: nil,
|
||||
expectedError: require.Error,
|
||||
},
|
||||
{
|
||||
name: "invalid method format",
|
||||
sigs: []string{"RSASHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
|
||||
expected: nil,
|
||||
expectedError: require.Error,
|
||||
},
|
||||
{
|
||||
name: "empty method values",
|
||||
sigs: []string{"/, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"},
|
||||
expected: nil,
|
||||
expectedError: require.Error,
|
||||
},
|
||||
{
|
||||
name: "empty created value",
|
||||
sigs: []string{"RSA/SHA256, , Key ID 702d426d350d275d"},
|
||||
expected: nil,
|
||||
expectedError: require.Error,
|
||||
},
|
||||
{
|
||||
name: "empty issuer value",
|
||||
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID "},
|
||||
expected: nil,
|
||||
expectedError: require.Error,
|
||||
},
|
||||
{
|
||||
name: "issuer without prefix",
|
||||
sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, 702d426d350d275d"},
|
||||
expected: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Mon May 16 12:32:55 2022",
|
||||
IssuerKeyID: "702d426d350d275d",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mixed valid and invalid signatures",
|
||||
sigs: []string{
|
||||
"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d",
|
||||
"DSASHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789",
|
||||
},
|
||||
expected: []pkg.RpmSignature{
|
||||
{
|
||||
PublicKeyAlgorithm: "RSA",
|
||||
HashAlgorithm: "SHA256",
|
||||
Created: "Mon May 16 12:32:55 2022",
|
||||
IssuerKeyID: "702d426d350d275d",
|
||||
},
|
||||
},
|
||||
expectedError: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectedError == nil {
|
||||
tt.expectedError = require.NoError
|
||||
}
|
||||
got, err := parseSignatures(tt.sigs...)
|
||||
tt.expectedError(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func intRef(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
FROM --platform=linux/amd64 rockylinux:9 AS rpm-downloader
|
||||
|
||||
# download a signed RPM (PostgreSQL in this example, known to be signed)
|
||||
# using PostgreSQL official RPM which is signed with their GPG key
|
||||
# $ rpm -Kv postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
|
||||
# postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm:
|
||||
# Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY
|
||||
# Header SHA256 digest: OK
|
||||
# Header SHA1 digest: OK
|
||||
# Payload SHA256 digest: OK
|
||||
# MD5 digest: OK
|
||||
#
|
||||
# $ rpm -ivh --nodeps --force postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
|
||||
# warning: postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm: Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY
|
||||
# Verifying... ################################# [100%]
|
||||
# Preparing... ################################# [100%]
|
||||
#
|
||||
# $ rpm -q --qf '%{NAME}-%{VERSION}-%{RELEASE} %{RSAHEADER:pgpsig}\n' postgresql14-server-14.10-1PGDG.rhel9.x86_64
|
||||
# postgresql14-server-14.10-1PGDG.rhel9 RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20
|
||||
|
||||
RUN curl -O https://download.postgresql.org/pub/repos/yum/14/redhat/rhel-9-x86_64/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=rpm-downloader /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm
|
||||
@@ -59,7 +59,7 @@ func TestTerraformCataloger(t *testing.T) {
|
||||
Metadata: pkg.TerraformLockProviderEntry{
|
||||
URL: "registry.terraform.io/hashicorp/google",
|
||||
Version: "6.8.0",
|
||||
Constraints: "6.8.0",
|
||||
Constraints: "",
|
||||
Hashes: []string{
|
||||
"h1:GlCaVPk6eKMg2ZbRY7C5tUeHGNIABT+qFtMl8+XWZHM=",
|
||||
"zh:1b78f4451f1617092eb6891c9c13eda79671060601c40947feea6794c732157a",
|
||||
|
||||
@@ -26,7 +26,6 @@ provider "registry.terraform.io/hashicorp/aws" {
|
||||
|
||||
provider "registry.terraform.io/hashicorp/google" {
|
||||
version = "6.8.0"
|
||||
constraints = "6.8.0"
|
||||
hashes = [
|
||||
"h1:GlCaVPk6eKMg2ZbRY7C5tUeHGNIABT+qFtMl8+XWZHM=",
|
||||
"zh:1b78f4451f1617092eb6891c9c13eda79671060601c40947feea6794c732157a",
|
||||
|
||||
+1
-1
@@ -365,7 +365,7 @@ func (b *licenseBuilder) licensesFromEvidenceAndContent(evidence []licenses.Evid
|
||||
|
||||
func (b *licenseBuilder) licenseFromContentHash(content string) License {
|
||||
hash := sha256HexFromString(content)
|
||||
value := "LicenseRef-sha256:" + hash
|
||||
value := "sha256:" + hash
|
||||
|
||||
return License{
|
||||
Value: value,
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestLicenseSet_Add(t *testing.T) {
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
{
|
||||
Value: "LicenseRef-sha256:eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
|
||||
Value: "sha256:eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
|
||||
Contents: readFileAsString("../../internal/licenses/test-fixtures/nvidia-software-and-cuda-supplement"),
|
||||
Type: license.Declared,
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user