From 6ef3e45ffc89e2455c0fd00414b442335471c60c Mon Sep 17 00:00:00 2001 From: Jonas Xavier Date: Wed, 16 Mar 2022 17:07:02 -0700 Subject: [PATCH] Use go 1.18 buildinfo to catalog binaries (#827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial working version Signed-off-by: Jonas Galvão Xavier * added build settings to pkg metadata wip - unit tests Signed-off-by: Jonas Galvão Xavier * handle mach-O FatFiles Signed-off-by: Jonas Galvão Xavier * add support to mod replace fixed golang catalger tests trying GH Actions with go 1.18rc1 Signed-off-by: Jonas Galvão Xavier * log error Signed-off-by: Jonas Galvão Xavier * use go-macholibre for extraction Signed-off-by: Jonas Galvão Xavier * cleaner tests Signed-off-by: Jonas Galvão Xavier * add version to main module Signed-off-by: Jonas Galvão Xavier * check macho file with macholibre Signed-off-by: Jonas Galvão Xavier * run golangci in its own workflow Signed-off-by: Jonas Galvão Xavier * wip - golangci workflow Signed-off-by: Jonas Galvão Xavier * fix golangci wf yml Signed-off-by: Jonas Galvão Xavier * fix golangci wf yml Signed-off-by: Jonas Galvão Xavier * wip - golangci wf Signed-off-by: Jonas Galvão Xavier * wip - golangci wf Signed-off-by: Jonas Galvão Xavier * get arch from bin file headers upgrade macholibre Signed-off-by: Jonas Galvão Xavier * go mod tidy Signed-off-by: Jonas Galvão Xavier * test new stereoscope lazy reader interface Signed-off-by: Jonas Galvão Xavier * go mod tidy Signed-off-by: Jonas Galvão Xavier * remove devel version from golang cataloger Signed-off-by: Jonas Galvão Xavier * go mod tidy Signed-off-by: Jonas Galvão Xavier * switch github workflows to go1.18 stable Signed-off-by: Jonas Galvão Xavier * add union reader interface in golang cataloger update stereoscope Signed-off-by: Jonas Galvão Xavier * go mod tidy Signed-off-by: Jonas Galvão Xavier * simpler golangci validation Signed-off-by: Jonas Galvão Xavier * fix makefile Signed-off-by: Jonas Galvão Xavier * get archs refactor Signed-off-by: Jonas Galvão Xavier * nolint for golang version Signed-off-by: Jonas Galvão Xavier * fix go bin tests Signed-off-by: Jonas Galvão Xavier * feedback changes Signed-off-by: Jonas Galvão Xavier * golangci nolint needs a \n before package Signed-off-by: Jonas Galvão Xavier * cleanup Signed-off-by: Jonas Galvão Xavier * move golangci-lint to its own jobs again Signed-off-by: Jonas Galvão Xavier * fix ci yaml Signed-off-by: Jonas Galvão Xavier * add support for xcoff files add arch assets to test bin file types Signed-off-by: Jonas Galvão Xavier * clean up golangci-lint config Signed-off-by: Jonas Galvão Xavier * nolint for xcoff Signed-off-by: Jonas Galvão Xavier * explain nolints Signed-off-by: Jonas Galvão Xavier * remove unused xcoff testdata assets Signed-off-by: Jonas Galvão Xavier * make go bin test-fixtures in docker Signed-off-by: Jonas Galvão Xavier * fix make clean with -f Signed-off-by: Jonas Galvão Xavier * update json output schema Signed-off-by: Jonas Galvão Xavier * update schema version in test fixture Signed-off-by: Jonas Galvão Xavier * feedback changes Signed-off-by: Jonas Galvão Xavier * explain possible empty main module Signed-off-by: Jonas Galvão Xavier --- .github/workflows/release.yaml | 15 +- .github/workflows/validations.yaml | 58 +- Makefile | 13 +- go.mod | 3 +- go.sum | 6 +- internal/constants.go | 2 +- .../snapshot/TestDirectoryEncoder.golden | 4 +- .../TestEncodeFullJSONDocument.golden | 4 +- .../snapshot/TestImageEncoder.golden | 4 +- schema/json/schema-3.1.1.json | 1237 +++++++++++++++++ syft/pkg/cataloger/golang/binary_cataloger.go | 48 +- .../cataloger/golang/binary_cataloger_test.go | 30 + syft/pkg/cataloger/golang/exe.go | 354 ----- syft/pkg/cataloger/golang/exe_test.go | 43 - syft/pkg/cataloger/golang/internal/README.md | 4 + .../cataloger/golang/internal/xcoff/file.go | 688 +++++++++ .../golang/internal/xcoff/file_test.go | 102 ++ .../xcoff/testdata/gcc-ppc32-aix-dwarf2-exec | Bin 0 -> 54694 bytes .../xcoff/testdata/gcc-ppc64-aix-dwarf2-exec | Bin 0 -> 57152 bytes .../cataloger/golang/internal/xcoff/xcoff.go | 373 +++++ syft/pkg/cataloger/golang/parse_go_bin.go | 199 ++- .../pkg/cataloger/golang/parse_go_bin_test.go | 364 +++-- syft/pkg/cataloger/golang/scan_bin.go | 65 + .../golang/test-fixtures/archs/.gitignore | 1 + .../golang/test-fixtures/archs/Makefile | 29 + .../golang/test-fixtures/archs/src/build.sh | 20 + .../golang/test-fixtures/archs/src/go.mod | 3 + .../golang/test-fixtures/archs/src/main.go | 5 + syft/pkg/cataloger/golang/version.go | 226 --- syft/pkg/golang_bin_metadata.go | 7 +- syft/source/image_squash_resolver.go | 2 +- .../regression_go_bin_scanner_arch_test.go | 6 +- 32 files changed, 3110 insertions(+), 805 deletions(-) create mode 100644 schema/json/schema-3.1.1.json create mode 100644 syft/pkg/cataloger/golang/binary_cataloger_test.go delete mode 100644 syft/pkg/cataloger/golang/exe.go delete mode 100644 syft/pkg/cataloger/golang/exe_test.go create mode 100644 syft/pkg/cataloger/golang/internal/README.md create mode 100644 syft/pkg/cataloger/golang/internal/xcoff/file.go create mode 100644 syft/pkg/cataloger/golang/internal/xcoff/file_test.go create mode 100644 syft/pkg/cataloger/golang/internal/xcoff/testdata/gcc-ppc32-aix-dwarf2-exec create mode 100644 syft/pkg/cataloger/golang/internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec create mode 100644 syft/pkg/cataloger/golang/internal/xcoff/xcoff.go create mode 100644 syft/pkg/cataloger/golang/scan_bin.go create mode 100644 syft/pkg/cataloger/golang/test-fixtures/archs/.gitignore create mode 100644 syft/pkg/cataloger/golang/test-fixtures/archs/Makefile create mode 100755 syft/pkg/cataloger/golang/test-fixtures/archs/src/build.sh create mode 100644 syft/pkg/cataloger/golang/test-fixtures/archs/src/go.mod create mode 100644 syft/pkg/cataloger/golang/test-fixtures/archs/src/main.go delete mode 100644 syft/pkg/cataloger/golang/version.go diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index aee646413..a6540f226 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,7 +9,7 @@ on: - "v*" env: - GO_VERSION: "1.17.x" + GO_VERSION: "1.18.x" jobs: quality-gate: @@ -34,6 +34,16 @@ jobs: checkName: "Static analysis" ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Check static analysis results - golangci-lint + uses: fountainhead/action-wait-for-check@v1.0.0 + id: static-analysis-golangci-lint + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/testing.yaml) + checkName: "Static analysis - golangci-lint" + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Check unit test results uses: fountainhead/action-wait-for-check@v1.0.0 id: unit @@ -80,9 +90,10 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Quality gate - if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success' + if: steps.static-analysis.outputs.conclusion != 'success' || steps.static-analysis-golangci-lint.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success' run: | echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}" + echo "Static Analysis Status - Golangci-Lint: ${{ steps.static-analysis-golangci-lint.conclusion }}" echo "Unit Test Status: ${{ steps.unit.outputs.conclusion }}" echo "Integration Test Status: ${{ steps.integration.outputs.conclusion }}" echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}" diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index bf50df0f1..7d45579d0 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -7,9 +7,49 @@ on: pull_request: env: - GO_VERSION: "1.17.x" + GO_VERSION: "1.18.x" + GO_STABLE_VERSION: true jobs: +# TODO: remove this job once golanci-lint is compatible with g01.18+ + Static-Analysis-Golanci-lint: + name: "Static analysis - golangci-lint" + runs-on: ubuntu-20.04 + steps: + - uses: actions/setup-go@v2 + with: + go-version: "1.17" # NOTE: please use GO_VERSION once golangci supports go1.18+ + stable: ${{ env.GO_STABLE_VERSION }} + + - uses: actions/checkout@v2 + + - name: Restore tool cache + id: tool-cache + uses: actions/cache@v2.1.3 + with: + path: ${{ github.workspace }}/.tmp + key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }} + + - name: Restore go cache + id: go-cache + uses: actions/cache@v2.1.3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-${{ env.GO_VERSION }}- + + - name: (cache-miss) Bootstrap all project dependencies + if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true' + run: make bootstrap + + - name: Bootstrap CI environment dependencies + run: make ci-bootstrap + + - name: Run static analysis + run: make static-analysis-golanci-lint + + Static-Analysis: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline @@ -19,6 +59,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} + stable: ${{ env.GO_STABLE_VERSION }} - uses: actions/checkout@v2 @@ -56,6 +97,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} + stable: ${{ env.GO_STABLE_VERSION }} - uses: actions/checkout@v2 @@ -92,6 +134,16 @@ jobs: path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }} + - name: Build cache key for go binary test-fixture blobs (for unit tests) + run: make go-binaries-fingerprint + + - name: Restore Go binary test-fixture cache + id: unit-go-binary-cache + uses: actions/cache@v2.1.3 + with: + path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries + key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }} + - name: Run unit tests run: make unit @@ -108,6 +160,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} + stable: ${{ env.GO_STABLE_VERSION }} - uses: actions/checkout@v2 @@ -159,6 +212,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} + stable: ${{ env.GO_STABLE_VERSION }} - uses: actions/checkout@v2 @@ -231,6 +285,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} + stable: ${{ env.GO_STABLE_VERSION }} - uses: actions/checkout@v2 @@ -338,6 +393,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.GO_VERSION }} + stable: ${{ env.GO_STABLE_VERSION }} - uses: actions/checkout@v2 diff --git a/Makefile b/Makefile index 530673722..d73028949 100644 --- a/Makefile +++ b/Makefile @@ -122,7 +122,12 @@ bootstrap: $(RESULTSDIR) bootstrap-go bootstrap-tools ## Download and install al $(call title,Bootstrapping dependencies) .PHONY: static-analysis -static-analysis: lint check-go-mod-tidy check-licenses +static-analysis: check-go-mod-tidy check-licenses + + # NOTE: isolating golanci-lint so it runs over go1.17 in CI, since it is not compatible with + # go1.18+ +.PHONY: static-analysis-golanci-lint +static-analysis-golanci-lint: lint .PHONY: lint lint: ## Run gofmt + golangci lint checks @@ -214,6 +219,12 @@ java-packages-fingerprint: cd syft/pkg/cataloger/java/test-fixtures/java-builds && \ make packages.fingerprint +.PHONY: go-binaries-fingerprint +go-binaries-fingerprint: + $(call title,Go binaries test fixture fingerprint) + cd syft/pkg/cataloger/golang/test-fixtures/archs && \ + make binaries.fingerprint + .PHONY: fixtures fixtures: $(call title,Generating test fixtures) diff --git a/go.mod b/go.mod index 23dd6aafa..054d57cdf 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,12 @@ require ( github.com/adrg/xdg v0.2.1 github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074 github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf + github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63 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.20220314153042-1bcd40e5206b - github.com/anchore/stereoscope v0.0.0-20220307154759-8a5a70c227d3 + github.com/anchore/stereoscope v0.0.0-20220315185520-25183ec78f40 github.com/antihax/optional v1.0.0 github.com/bmatcuk/doublestar/v4 v4.0.2 github.com/dustin/go-humanize v1.0.0 diff --git a/go.sum b/go.sum index 953177d73..459671027 100644 --- a/go.sum +++ b/go.sum @@ -276,6 +276,8 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:C github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg= github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf/go.mod h1:FaODhIA06mxO1E6R32JE0TL1JWZZkmjRIAd4ULvHUKk= +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-20210914181456-a9c52348da63 h1:C9W/LAydEz/qdUhx1MdjO9l8NEcFKYknkxDVyo9LAoM= github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63/go.mod h1:6qH8c6U/3CBVvDDDBZnPSTbTINq3cIdADUYTaVf75EM= github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8= @@ -284,8 +286,8 @@ 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.20220314153042-1bcd40e5206b h1:YJWYt/6KQXR9JR46lLHrTTYi8rcye42tKcyjREA/hvA= github.com/anchore/packageurl-go v0.1.1-0.20220314153042-1bcd40e5206b/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20220307154759-8a5a70c227d3 h1:Kx2jlMdENAf4cVjYGYLI+fiavVhzhtmU89GUYDITJ1w= -github.com/anchore/stereoscope v0.0.0-20220307154759-8a5a70c227d3/go.mod h1:XESZQTgFETDBatmyoet6XZ0zVknoIMDSAhj2INj2a5w= +github.com/anchore/stereoscope v0.0.0-20220315185520-25183ec78f40 h1:OSyFMZgEwQW0wFyv10kEi9kmB52FQFRUQmc2H6d8LTY= +github.com/anchore/stereoscope v0.0.0-20220315185520-25183ec78f40/go.mod h1:OVVJ4y/L26pspeeHNvNa7GeQfoLPWxJe13iODl9x5l4= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= diff --git a/internal/constants.go b/internal/constants.go index 2d1b40024..143aafccc 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -6,5 +6,5 @@ const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "3.1.0" + JSONSchemaVersion = "3.1.1" ) diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden index 1689c4c56..9503585be 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden @@ -88,7 +88,7 @@ } }, "schema": { - "version": "3.1.0", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json" + "version": "3.1.1", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.1.json" } } diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 96a208e04..1f0540fb2 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -184,7 +184,7 @@ } }, "schema": { - "version": "3.1.0", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json" + "version": "3.1.1", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.1.json" } } diff --git a/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden b/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden index ba6f66d7f..ff3fb05a5 100644 --- a/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden +++ b/internal/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden @@ -111,7 +111,7 @@ } }, "schema": { - "version": "3.1.0", - "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json" + "version": "3.1.1", + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.1.json" } } diff --git a/schema/json/schema-3.1.1.json b/schema/json/schema-3.1.1.json new file mode 100644 index 000000000..e23a86a0c --- /dev/null +++ b/schema/json/schema-3.1.1.json @@ -0,0 +1,1237 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Document", + "definitions": { + "ApkFileRecord": { + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Digest" + } + }, + "additionalProperties": true, + "type": "object" + }, + "ApkMetadata": { + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "license", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ], + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "type": "string" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/ApkFileRecord" + }, + "type": "array" + } + }, + "additionalProperties": true, + "type": "object" + }, + "CargoPackageMetadata": { + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Classification": { + "required": [ + "class", + "metadata" + ], + "properties": { + "class": { + "type": "string" + }, + "metadata": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Coordinates": { + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Descriptor": { + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": { + "additionalProperties": true + } + }, + "additionalProperties": true, + "type": "object" + }, + "Digest": { + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Document": { + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ], + "properties": { + "artifacts": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/File" + }, + "type": "array" + }, + "secrets": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Secrets" + }, + "type": "array" + }, + "source": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Source" + }, + "distro": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/LinuxRelease" + }, + "descriptor": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Descriptor" + }, + "schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Schema" + } + }, + "additionalProperties": true, + "type": "object" + }, + "DpkgFileRecord": { + "required": [ + "path", + "isConfigFile" + ], + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/definitions/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "additionalProperties": true, + "type": "object" + }, + "DpkgMetadata": { + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ], + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/DpkgFileRecord" + }, + "type": "array" + } + }, + "additionalProperties": true, + "type": "object" + }, + "File": { + "required": [ + "id", + "location" + ], + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/definitions/Coordinates" + }, + "metadata": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Digest" + }, + "type": "array" + }, + "classifications": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Classification" + }, + "type": "array" + } + }, + "additionalProperties": true, + "type": "object" + }, + "FileMetadataEntry": { + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType" + ], + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "GemMetadata": { + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "GolangBinMetadata": { + "required": [ + "goCompiledVersion", + "architecture", + "h1Digest" + ], + "properties": { + "goBuildSettings": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "JavaManifest": { + "properties": { + "main": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "namedSections": { + "patternProperties": { + ".*": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "additionalProperties": true, + "type": "object" + }, + "JavaMetadata": { + "required": [ + "virtualPath" + ], + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/JavaManifest" + }, + "pomProperties": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PomProperties" + }, + "pomProject": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PomProject" + } + }, + "additionalProperties": true, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "items": { + "type": "string" + }, + "type": "array" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "NpmPackageJSONMetadata": { + "required": [ + "name", + "version", + "author", + "licenses", + "homepage", + "description", + "url" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "author": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Package": { + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Coordinates" + }, + "type": "array" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/definitions/ApkMetadata" + }, + { + "$ref": "#/definitions/CargoPackageMetadata" + }, + { + "$ref": "#/definitions/DpkgMetadata" + }, + { + "$ref": "#/definitions/GemMetadata" + }, + { + "$ref": "#/definitions/GolangBinMetadata" + }, + { + "$ref": "#/definitions/JavaMetadata" + }, + { + "$ref": "#/definitions/NpmPackageJSONMetadata" + }, + { + "$ref": "#/definitions/PhpComposerJSONMetadata" + }, + { + "$ref": "#/definitions/PythonPackageMetadata" + }, + { + "$ref": "#/definitions/RpmdbMetadata" + } + ] + } + }, + "additionalProperties": true, + "type": "object" + }, + "PhpComposerAuthors": { + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PhpComposerExternalReference": { + "required": [ + "type", + "url", + "reference" + ], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PhpComposerJSONMetadata": { + "required": [ + "name", + "version", + "source", + "dist" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/definitions/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PomParent": { + "required": [ + "groupId", + "artifactId", + "version" + ], + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PomProject": { + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ], + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PomProperties": { + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version", + "extraFields" + ], + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PythonDirectURLOriginInfo": { + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PythonFileDigest": { + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PythonFileRecord": { + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "PythonPackageMetadata": { + "required": [ + "name", + "version", + "license", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/PythonDirectURLOriginInfo" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Relationship": { + "required": [ + "parent", + "child", + "type" + ], + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": { + "additionalProperties": true + } + }, + "additionalProperties": true, + "type": "object" + }, + "RpmdbFileRecord": { + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ], + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/definitions/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "RpmdbMetadata": { + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "license", + "vendor", + "files" + ], + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "license": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "files": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/RpmdbFileRecord" + }, + "type": "array" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Schema": { + "required": [ + "version", + "url" + ], + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "SearchResult": { + "required": [ + "classification", + "lineNumber", + "lineOffset", + "seekPosition", + "length" + ], + "properties": { + "classification": { + "type": "string" + }, + "lineNumber": { + "type": "integer" + }, + "lineOffset": { + "type": "integer" + }, + "seekPosition": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Secrets": { + "required": [ + "location", + "secrets" + ], + "properties": { + "location": { + "$ref": "#/definitions/Coordinates" + }, + "secrets": { + "items": { + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/SearchResult" + }, + "type": "array" + } + }, + "additionalProperties": true, + "type": "object" + }, + "Source": { + "required": [ + "type", + "target" + ], + "properties": { + "type": { + "type": "string" + }, + "target": { + "additionalProperties": true + } + }, + "additionalProperties": true, + "type": "object" + } + } +} diff --git a/syft/pkg/cataloger/golang/binary_cataloger.go b/syft/pkg/cataloger/golang/binary_cataloger.go index 2ddf7d3b1..7d51f63ce 100644 --- a/syft/pkg/cataloger/golang/binary_cataloger.go +++ b/syft/pkg/cataloger/golang/binary_cataloger.go @@ -4,10 +4,12 @@ Package golang provides a concrete Cataloger implementation for go.mod files. package golang import ( + "bytes" "fmt" + "io" + "io/ioutil" "github.com/anchore/syft/internal" - "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" @@ -38,19 +40,51 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti } for _, location := range fileMatches { - r, err := resolver.FileContentsByLocation(location) + readerCloser, err := resolver.FileContentsByLocation(location) if err != nil { - return pkgs, nil, fmt.Errorf("failed to resolve file contents by location=%q: %w", location.RealPath, err) + log.Warnf("golang cataloger: opening file: %v", err) + continue } - goPkgs, err := parseGoBin(location, r, openExe) + reader, err := getUnionReader(readerCloser) if err != nil { - log.Warnf("could not parse possible go binary at %q: %+v", location.RealPath, err) + return nil, nil, err } - internal.CloseAndLogError(r, location.RealPath) - pkgs = append(pkgs, goPkgs...) + mods, archs := scanFile(reader, location.RealPath) + internal.CloseAndLogError(readerCloser, location.RealPath) + + for i, mod := range mods { + pkgs = append(pkgs, buildGoPkgInfo(location, mod, archs[i])...) + } } return pkgs, nil, nil } + +func getUnionReader(readerCloser io.ReadCloser) (unionReader, error) { + reader, ok := readerCloser.(unionReader) + if ok { + return reader, nil + } + log.Debugf("golang cataloger: unable to use stereoscope file, reading entire contents") + + b, err := ioutil.ReadAll(readerCloser) + if err != nil { + return nil, fmt.Errorf("unable to read contents from go binary: %w", err) + } + + bytesReader := bytes.NewReader(b) + + reader = struct { + io.ReadCloser + io.ReaderAt + io.Seeker + }{ + ReadCloser: io.NopCloser(bytesReader), + ReaderAt: bytesReader, + Seeker: bytesReader, + } + + return reader, nil +} diff --git a/syft/pkg/cataloger/golang/binary_cataloger_test.go b/syft/pkg/cataloger/golang/binary_cataloger_test.go new file mode 100644 index 000000000..41ad714df --- /dev/null +++ b/syft/pkg/cataloger/golang/binary_cataloger_test.go @@ -0,0 +1,30 @@ +package golang + +import ( + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_getUnionReader_notUnionReader(t *testing.T) { + expectedContents := "this is a test" + reader := io.NopCloser(strings.NewReader(expectedContents)) + + // make certain that the test fixture does not implement the union reader + _, ok := reader.(unionReader) + require.False(t, ok) + + actual, err := getUnionReader(reader) + require.NoError(t, err) + + _, ok = actual.(unionReader) + require.True(t, ok) + + b, err := io.ReadAll(actual) + require.NoError(t, err) + + assert.Equal(t, expectedContents, string(b)) +} diff --git a/syft/pkg/cataloger/golang/exe.go b/syft/pkg/cataloger/golang/exe.go deleted file mode 100644 index 838d91f01..000000000 --- a/syft/pkg/cataloger/golang/exe.go +++ /dev/null @@ -1,354 +0,0 @@ -// This code was copied from the Go std library. -// https://github.com/golang/go/blob/master/src/cmd/go/internal/version/exe.go -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//nolint -package golang - -import ( - "bytes" - "debug/elf" - "debug/macho" - "debug/pe" - "fmt" - "io" - "strings" -) - -// An exe is a generic interface to an OS executable (ELF, Mach-O, PE, XCOFF). -type exe interface { - // Close closes the underlying file. - Close() error - - // ReadData reads and returns up to size byte starting at virtual address addr. - ReadData(addr, size uint64) ([]byte, error) - - // ArchName returns a string that represents the CPU architecture of the executable. - ArchName() string - - // DataStart returns the writable data segment start address. - DataStart() uint64 -} - -// openExe opens file and returns it as an exe. -// we changed this signature from accpeting a string -// to a ReadCloser so we could adapt the code to the -// stereoscope api. We removed the file open methods. -func openExe(file io.ReadCloser) ([]exe, error) { - /* - f, err := os.Open(file) - if err != nil { - return nil, err - } - data := make([]byte, 16) - if _, err := io.ReadFull(f, data); err != nil { - return nil, err - } - f.Seek(0, 0) - */ - data, err := io.ReadAll(file) - if err != nil { - return nil, err - } - - r := bytes.NewReader(data) - f := io.NewSectionReader(r, 0, int64(len(data))) - if bytes.HasPrefix(data, []byte("\x7FELF")) { - e, err := elf.NewFile(f) - if err != nil { - return nil, err - } - - return []exe{&elfExe{file, e}}, nil - } - - if bytes.HasPrefix(data, []byte("MZ")) { - e, err := pe.NewFile(f) - if err != nil { - return nil, err - } - return []exe{&peExe{file, e}}, nil - } - - if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) { - e, err := macho.NewFile(f) - if err != nil { - return nil, err - } - return []exe{&machoExe{file, e}}, nil - } - - // adding macho multi-architecture support (both for 64bit and 32 bit)... this case is not in the stdlib yet - if bytes.HasPrefix(data, []byte("\xCA\xFE\xBA\xBE")) || bytes.HasPrefix(data, []byte("\xCA\xFE\xBA\xBF")) { - fatExe, err := macho.NewFatFile(f) - if err != nil { - return nil, err - } - var exes []exe - for _, arch := range fatExe.Arches { - exes = append(exes, &machoExe{file, arch.File}) - } - return exes, nil - } - - return nil, fmt.Errorf("unrecognized executable format") -} - -// elfExe is the ELF implementation of the exe interface. -// updated os to be io.ReadCloser to interopt with stereoscope -type elfExe struct { - os io.ReadCloser - f *elf.File -} - -func (x *elfExe) Close() error { - return x.os.Close() -} - -func (x *elfExe) ArchName() string { - return cleanElfArch(x.f.Machine) -} - -func cleanElfArch(machine elf.Machine) string { - return strings.TrimPrefix(strings.ToLower(machine.String()), "em_") -} - -func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) { - for _, prog := range x.f.Progs { - if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 { - n := prog.Vaddr + prog.Filesz - addr - if n > size { - n = size - } - data := make([]byte, n) - _, err := prog.ReadAt(data, int64(addr-prog.Vaddr)) - if err != nil { - return nil, err - } - return data, nil - } - } - return nil, fmt.Errorf("address not mapped") -} - -func (x *elfExe) DataStart() uint64 { - for _, s := range x.f.Sections { - if s.Name == ".go.buildinfo" { - return s.Addr - } - } - for _, p := range x.f.Progs { - if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W { - return p.Vaddr - } - } - return 0 -} - -// peExe is the PE (Windows Portable Executable) implementation of the exe interface. -type peExe struct { - os io.ReadCloser - f *pe.File -} - -func (x *peExe) Close() error { - return x.os.Close() -} - -func (x *peExe) ArchName() string { - // from: debug/pe/pe.go - switch x.f.Machine { - case pe.IMAGE_FILE_MACHINE_AM33: - return "amd33" - case pe.IMAGE_FILE_MACHINE_AMD64: - return "amd64" - case pe.IMAGE_FILE_MACHINE_ARM: - return "arm" - case pe.IMAGE_FILE_MACHINE_ARMNT: - return "armnt" - case pe.IMAGE_FILE_MACHINE_ARM64: - return "arm64" - case pe.IMAGE_FILE_MACHINE_EBC: - return "ebc" - case pe.IMAGE_FILE_MACHINE_I386: - return "i386" - case pe.IMAGE_FILE_MACHINE_IA64: - return "ia64" - case pe.IMAGE_FILE_MACHINE_M32R: - return "m32r" - case pe.IMAGE_FILE_MACHINE_MIPS16: - return "mips16" - case pe.IMAGE_FILE_MACHINE_MIPSFPU: - return "mipsfpu" - case pe.IMAGE_FILE_MACHINE_MIPSFPU16: - return "mipsfpu16" - case pe.IMAGE_FILE_MACHINE_POWERPC: - return "ppc" - case pe.IMAGE_FILE_MACHINE_POWERPCFP: - return "ppcfp" - case pe.IMAGE_FILE_MACHINE_R4000: - return "r4000" - case pe.IMAGE_FILE_MACHINE_SH3: - return "sh3" - case pe.IMAGE_FILE_MACHINE_SH3DSP: - return "sh3dsp" - case pe.IMAGE_FILE_MACHINE_SH4: - return "sh4" - case pe.IMAGE_FILE_MACHINE_SH5: - return "sh5" - case pe.IMAGE_FILE_MACHINE_THUMB: - return "thumb" - case pe.IMAGE_FILE_MACHINE_WCEMIPSV2: - return "wcemipsv2" - default: - return fmt.Sprintf("unknown-pe-machine-%d", x.f.Machine) - } -} - -func (x *peExe) imageBase() uint64 { - switch oh := x.f.OptionalHeader.(type) { - case *pe.OptionalHeader32: - return uint64(oh.ImageBase) - case *pe.OptionalHeader64: - return oh.ImageBase - } - return 0 -} - -func (x *peExe) ReadData(addr, size uint64) ([]byte, error) { - addr -= x.imageBase() - for _, sect := range x.f.Sections { - if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) { - n := uint64(sect.VirtualAddress+sect.Size) - addr - if n > size { - n = size - } - data := make([]byte, n) - _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress))) - if err != nil { - return nil, err - } - return data, nil - } - } - return nil, fmt.Errorf("address not mapped") -} - -func (x *peExe) DataStart() uint64 { - // Assume data is first writable section. - const ( - IMAGE_SCN_CNT_CODE = 0x00000020 - IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 - IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 - IMAGE_SCN_MEM_EXECUTE = 0x20000000 - IMAGE_SCN_MEM_READ = 0x40000000 - IMAGE_SCN_MEM_WRITE = 0x80000000 - IMAGE_SCN_MEM_DISCARDABLE = 0x2000000 - IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000 - IMAGE_SCN_ALIGN_32BYTES = 0x600000 - ) - for _, sect := range x.f.Sections { - if sect.VirtualAddress != 0 && sect.Size != 0 && - sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE { - return uint64(sect.VirtualAddress) + x.imageBase() - } - } - return 0 -} - -// machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface. -type machoExe struct { - os io.ReadCloser - f *macho.File -} - -func (x *machoExe) Close() error { - return x.os.Close() -} - -func (x *machoExe) ArchName() string { - return cleanMachoArch(x.f.Cpu) -} - -func cleanMachoArch(cpu macho.Cpu) string { - return strings.TrimPrefix(strings.ToLower(cpu.String()), "cpu") -} - -func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) { - for _, load := range x.f.Loads { - seg, ok := load.(*macho.Segment) - if !ok { - continue - } - if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 { - if seg.Name == "__PAGEZERO" { - continue - } - n := seg.Addr + seg.Filesz - addr - if n > size { - n = size - } - data := make([]byte, n) - _, err := seg.ReadAt(data, int64(addr-seg.Addr)) - if err != nil { - return nil, err - } - return data, nil - } - } - return nil, fmt.Errorf("address not mapped") -} - -func (x *machoExe) DataStart() uint64 { - // Look for section named "__go_buildinfo". - for _, sec := range x.f.Sections { - if sec.Name == "__go_buildinfo" { - return sec.Addr - } - } - // Try the first non-empty writable segment. - const RW = 3 - for _, load := range x.f.Loads { - seg, ok := load.(*macho.Segment) - if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW { - return seg.Addr - } - } - return 0 -} - -/* -// xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface. -type xcoffExe struct { - os *os.File - f *xcoff.File -} - -func (x *xcoffExe) Close() error { - return x.os.Close() -} - -func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) { - for _, sect := range x.f.Sections { - if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) { - n := uint64(sect.VirtualAddress+sect.Size) - addr - if n > size { - n = size - } - data := make([]byte, n) - _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress))) - if err != nil { - return nil, err - } - return data, nil - } - } - return nil, fmt.Errorf("address not mapped") -} - -func (x *xcoffExe) DataStart() uint64 { - return x.f.SectionByType(xcoff.STYP_DATA).VirtualAddress -} -*/ diff --git a/syft/pkg/cataloger/golang/exe_test.go b/syft/pkg/cataloger/golang/exe_test.go deleted file mode 100644 index 5a5a6d39c..000000000 --- a/syft/pkg/cataloger/golang/exe_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package golang - -import ( - "debug/elf" - "debug/macho" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_cleanElfArch(t *testing.T) { - tests := []struct { - machine elf.Machine - want string - }{ - { - machine: elf.EM_X86_64, - want: "x86_64", - }, - } - for _, test := range tests { - t.Run(test.machine.String(), func(t *testing.T) { - assert.Equalf(t, test.want, cleanElfArch(test.machine), "cleanElfArch(%v)", test.machine) - }) - } -} - -func Test_cleanMachoArch(t *testing.T) { - tests := []struct { - cpu macho.Cpu - want string - }{ - { - cpu: macho.CpuAmd64, - want: "amd64", - }, - } - for _, test := range tests { - t.Run(test.cpu.String(), func(t *testing.T) { - assert.Equalf(t, test.want, cleanMachoArch(test.cpu), "cleanMachoArch(%v)", test.cpu) - }) - } -} diff --git a/syft/pkg/cataloger/golang/internal/README.md b/syft/pkg/cataloger/golang/internal/README.md new file mode 100644 index 000000000..58cdf17ce --- /dev/null +++ b/syft/pkg/cataloger/golang/internal/README.md @@ -0,0 +1,4 @@ +xcoff +----- + +The code in this package comes from: https://github.com/golang/go/tree/master/src/internal/xcoff -- it was copied over to add support for [xcoff](https://en.wikipedia.org/wiki/XCOFF) binaries. Golang keeps this package as internal, forbidding its external use. diff --git a/syft/pkg/cataloger/golang/internal/xcoff/file.go b/syft/pkg/cataloger/golang/internal/xcoff/file.go new file mode 100644 index 000000000..76a39a34f --- /dev/null +++ b/syft/pkg/cataloger/golang/internal/xcoff/file.go @@ -0,0 +1,688 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xcoff implements access to XCOFF (Extended Common Object File Format) files. + +//nolint //this is an internal golang lib +package xcoff + +import ( + "encoding/binary" + "fmt" + "io" + "os" + "strings" +) + +// SectionHeader holds information about an XCOFF section header. +type SectionHeader struct { + Name string + VirtualAddress uint64 + Size uint64 + Type uint32 + Relptr uint64 + Nreloc uint32 +} + +type Section struct { + SectionHeader + Relocs []Reloc + io.ReaderAt + sr *io.SectionReader +} + +// AuxiliaryCSect holds information about an XCOFF symbol in an AUX_CSECT entry. +type AuxiliaryCSect struct { + Length int64 + StorageMappingClass int + SymbolType int +} + +// AuxiliaryFcn holds information about an XCOFF symbol in an AUX_FCN entry. +type AuxiliaryFcn struct { + Size int64 +} + +type Symbol struct { + Name string + Value uint64 + SectionNumber int + StorageClass int + AuxFcn AuxiliaryFcn + AuxCSect AuxiliaryCSect +} + +type Reloc struct { + VirtualAddress uint64 + Symbol *Symbol + Signed bool + InstructionFixed bool + Length uint8 + Type uint8 +} + +// ImportedSymbol holds information about an imported XCOFF symbol. +type ImportedSymbol struct { + Name string + Library string +} + +// FileHeader holds information about an XCOFF file header. +type FileHeader struct { + TargetMachine uint16 +} + +// A File represents an open XCOFF file. +type File struct { + FileHeader + Sections []*Section + Symbols []*Symbol + StringTable []byte + LibraryPaths []string + + closer io.Closer +} + +// Open opens the named file using os.Open and prepares it for use as an XCOFF binary. +func Open(name string) (*File, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + ff, err := NewFile(f) + if err != nil { + f.Close() + return nil, err + } + ff.closer = f + return ff, nil +} + +// Close closes the File. +// If the File was created using NewFile directly instead of Open, +// Close has no effect. +func (f *File) Close() error { + var err error + if f.closer != nil { + err = f.closer.Close() + f.closer = nil + } + return err +} + +// Section returns the first section with the given name, or nil if no such +// section exists. +// Xcoff have section's name limited to 8 bytes. Some sections like .gosymtab +// can be trunked but this method will still find them. +func (f *File) Section(name string) *Section { + for _, s := range f.Sections { + if s.Name == name || (len(name) > 8 && s.Name == name[:8]) { + return s + } + } + return nil +} + +// SectionByType returns the first section in f with the +// given type, or nil if there is no such section. +func (f *File) SectionByType(typ uint32) *Section { + for _, s := range f.Sections { + if s.Type == typ { + return s + } + } + return nil +} + +// cstring converts ASCII byte sequence b to string. +// It stops once it finds 0 or reaches end of b. +func cstring(b []byte) string { + var i int + for i = 0; i < len(b) && b[i] != 0; i++ { + } + return string(b[:i]) +} + +// getString extracts a string from an XCOFF string table. +func getString(st []byte, offset uint32) (string, bool) { + if offset < 4 || int(offset) >= len(st) { + return "", false + } + return cstring(st[offset:]), true +} + +// NewFile creates a new File for accessing an XCOFF binary in an underlying reader. +func NewFile(r io.ReaderAt) (*File, error) { + sr := io.NewSectionReader(r, 0, 1<<63-1) + // Read XCOFF target machine + var magic uint16 + if err := binary.Read(sr, binary.BigEndian, &magic); err != nil { + return nil, err + } + if magic != U802TOCMAGIC && magic != U64_TOCMAGIC { + return nil, fmt.Errorf("unrecognised XCOFF magic: 0x%x", magic) + } + + f := new(File) + f.TargetMachine = magic + + // Read XCOFF file header + if _, err := sr.Seek(0, os.SEEK_SET); err != nil { + return nil, err + } + var nscns uint16 + var symptr uint64 + var nsyms int32 + var opthdr uint16 + var hdrsz int + switch f.TargetMachine { + case U802TOCMAGIC: + fhdr := new(FileHeader32) + if err := binary.Read(sr, binary.BigEndian, fhdr); err != nil { + return nil, err + } + nscns = fhdr.Fnscns + symptr = uint64(fhdr.Fsymptr) + nsyms = fhdr.Fnsyms + opthdr = fhdr.Fopthdr + hdrsz = FILHSZ_32 + case U64_TOCMAGIC: + fhdr := new(FileHeader64) + if err := binary.Read(sr, binary.BigEndian, fhdr); err != nil { + return nil, err + } + nscns = fhdr.Fnscns + symptr = fhdr.Fsymptr + nsyms = fhdr.Fnsyms + opthdr = fhdr.Fopthdr + hdrsz = FILHSZ_64 + } + + if symptr == 0 || nsyms <= 0 { + return nil, fmt.Errorf("no symbol table") + } + + // Read string table (located right after symbol table). + offset := symptr + uint64(nsyms)*SYMESZ + if _, err := sr.Seek(int64(offset), os.SEEK_SET); err != nil { + return nil, err + } + // The first 4 bytes contain the length (in bytes). + var l uint32 + if err := binary.Read(sr, binary.BigEndian, &l); err != nil { + return nil, err + } + if l > 4 { + if _, err := sr.Seek(int64(offset), os.SEEK_SET); err != nil { + return nil, err + } + f.StringTable = make([]byte, l) + if _, err := io.ReadFull(sr, f.StringTable); err != nil { + return nil, err + } + } + + // Read section headers + if _, err := sr.Seek(int64(hdrsz)+int64(opthdr), os.SEEK_SET); err != nil { + return nil, err + } + f.Sections = make([]*Section, nscns) + for i := 0; i < int(nscns); i++ { + var scnptr uint64 + s := new(Section) + switch f.TargetMachine { + case U802TOCMAGIC: + shdr := new(SectionHeader32) + if err := binary.Read(sr, binary.BigEndian, shdr); err != nil { + return nil, err + } + s.Name = cstring(shdr.Sname[:]) + s.VirtualAddress = uint64(shdr.Svaddr) + s.Size = uint64(shdr.Ssize) + scnptr = uint64(shdr.Sscnptr) + s.Type = shdr.Sflags + s.Relptr = uint64(shdr.Srelptr) + s.Nreloc = uint32(shdr.Snreloc) + case U64_TOCMAGIC: + shdr := new(SectionHeader64) + if err := binary.Read(sr, binary.BigEndian, shdr); err != nil { + return nil, err + } + s.Name = cstring(shdr.Sname[:]) + s.VirtualAddress = shdr.Svaddr + s.Size = shdr.Ssize + scnptr = shdr.Sscnptr + s.Type = shdr.Sflags + s.Relptr = shdr.Srelptr + s.Nreloc = shdr.Snreloc + } + r2 := r + if scnptr == 0 { // .bss must have all 0s + r2 = zeroReaderAt{} + } + s.sr = io.NewSectionReader(r2, int64(scnptr), int64(s.Size)) + s.ReaderAt = s.sr + f.Sections[i] = s + } + + // Symbol map needed by relocation + var idxToSym = make(map[int]*Symbol) + + // Read symbol table + if _, err := sr.Seek(int64(symptr), os.SEEK_SET); err != nil { + return nil, err + } + f.Symbols = make([]*Symbol, 0) + for i := 0; i < int(nsyms); i++ { + var numaux int + var ok, needAuxFcn bool + sym := new(Symbol) + switch f.TargetMachine { + case U802TOCMAGIC: + se := new(SymEnt32) + if err := binary.Read(sr, binary.BigEndian, se); err != nil { + return nil, err + } + numaux = int(se.Nnumaux) + sym.SectionNumber = int(se.Nscnum) + sym.StorageClass = int(se.Nsclass) + sym.Value = uint64(se.Nvalue) + needAuxFcn = se.Ntype&SYM_TYPE_FUNC != 0 && numaux > 1 + zeroes := binary.BigEndian.Uint32(se.Nname[:4]) + if zeroes != 0 { + sym.Name = cstring(se.Nname[:]) + } else { + offset := binary.BigEndian.Uint32(se.Nname[4:]) + sym.Name, ok = getString(f.StringTable, offset) + if !ok { + goto skip + } + } + case U64_TOCMAGIC: + se := new(SymEnt64) + if err := binary.Read(sr, binary.BigEndian, se); err != nil { + return nil, err + } + numaux = int(se.Nnumaux) + sym.SectionNumber = int(se.Nscnum) + sym.StorageClass = int(se.Nsclass) + sym.Value = se.Nvalue + needAuxFcn = se.Ntype&SYM_TYPE_FUNC != 0 && numaux > 1 + sym.Name, ok = getString(f.StringTable, se.Noffset) + if !ok { + goto skip + } + } + if sym.StorageClass != C_EXT && sym.StorageClass != C_WEAKEXT && sym.StorageClass != C_HIDEXT { + goto skip + } + // Must have at least one csect auxiliary entry. + if numaux < 1 || i+numaux >= int(nsyms) { + goto skip + } + + if sym.SectionNumber > int(nscns) { + goto skip + } + if sym.SectionNumber == 0 { + sym.Value = 0 + } else { + sym.Value -= f.Sections[sym.SectionNumber-1].VirtualAddress + } + + idxToSym[i] = sym + + // If this symbol is a function, it must retrieve its size from + // its AUX_FCN entry. + // It can happen that a function symbol doesn't have any AUX_FCN. + // In this case, needAuxFcn is false and their size will be set to 0. + if needAuxFcn { + switch f.TargetMachine { + case U802TOCMAGIC: + aux := new(AuxFcn32) + if err := binary.Read(sr, binary.BigEndian, aux); err != nil { + return nil, err + } + sym.AuxFcn.Size = int64(aux.Xfsize) + case U64_TOCMAGIC: + aux := new(AuxFcn64) + if err := binary.Read(sr, binary.BigEndian, aux); err != nil { + return nil, err + } + sym.AuxFcn.Size = int64(aux.Xfsize) + } + } + + // Read csect auxiliary entry (by convention, it is the last). + if !needAuxFcn { + if _, err := sr.Seek(int64(numaux-1)*SYMESZ, os.SEEK_CUR); err != nil { + return nil, err + } + } + i += numaux + numaux = 0 + switch f.TargetMachine { + case U802TOCMAGIC: + aux := new(AuxCSect32) + if err := binary.Read(sr, binary.BigEndian, aux); err != nil { + return nil, err + } + sym.AuxCSect.SymbolType = int(aux.Xsmtyp & 0x7) + sym.AuxCSect.StorageMappingClass = int(aux.Xsmclas) + sym.AuxCSect.Length = int64(aux.Xscnlen) + case U64_TOCMAGIC: + aux := new(AuxCSect64) + if err := binary.Read(sr, binary.BigEndian, aux); err != nil { + return nil, err + } + sym.AuxCSect.SymbolType = int(aux.Xsmtyp & 0x7) + sym.AuxCSect.StorageMappingClass = int(aux.Xsmclas) + sym.AuxCSect.Length = int64(aux.Xscnlenhi)<<32 | int64(aux.Xscnlenlo) + } + f.Symbols = append(f.Symbols, sym) + skip: + i += numaux // Skip auxiliary entries + if _, err := sr.Seek(int64(numaux)*SYMESZ, os.SEEK_CUR); err != nil { + return nil, err + } + } + + // Read relocations + // Only for .data or .text section + for _, sect := range f.Sections { + if sect.Type != STYP_TEXT && sect.Type != STYP_DATA { + continue + } + sect.Relocs = make([]Reloc, sect.Nreloc) + if sect.Relptr == 0 { + continue + } + if _, err := sr.Seek(int64(sect.Relptr), os.SEEK_SET); err != nil { + return nil, err + } + for i := uint32(0); i < sect.Nreloc; i++ { + switch f.TargetMachine { + case U802TOCMAGIC: + rel := new(Reloc32) + if err := binary.Read(sr, binary.BigEndian, rel); err != nil { + return nil, err + } + sect.Relocs[i].VirtualAddress = uint64(rel.Rvaddr) + sect.Relocs[i].Symbol = idxToSym[int(rel.Rsymndx)] + sect.Relocs[i].Type = rel.Rtype + sect.Relocs[i].Length = rel.Rsize&0x3F + 1 + + if rel.Rsize&0x80 != 0 { + sect.Relocs[i].Signed = true + } + if rel.Rsize&0x40 != 0 { + sect.Relocs[i].InstructionFixed = true + } + + case U64_TOCMAGIC: + rel := new(Reloc64) + if err := binary.Read(sr, binary.BigEndian, rel); err != nil { + return nil, err + } + sect.Relocs[i].VirtualAddress = rel.Rvaddr + sect.Relocs[i].Symbol = idxToSym[int(rel.Rsymndx)] + sect.Relocs[i].Type = rel.Rtype + sect.Relocs[i].Length = rel.Rsize&0x3F + 1 + if rel.Rsize&0x80 != 0 { + sect.Relocs[i].Signed = true + } + if rel.Rsize&0x40 != 0 { + sect.Relocs[i].InstructionFixed = true + } + } + } + } + + return f, nil +} + +// zeroReaderAt is ReaderAt that reads 0s. +type zeroReaderAt struct{} + +// ReadAt writes len(p) 0s into p. +func (w zeroReaderAt) ReadAt(p []byte, off int64) (n int, err error) { + for i := range p { + p[i] = 0 + } + return len(p), nil +} + +// Data reads and returns the contents of the XCOFF section s. +func (s *Section) Data() ([]byte, error) { + dat := make([]byte, s.sr.Size()) + n, err := s.sr.ReadAt(dat, 0) + if n == len(dat) { + err = nil + } + return dat[:n], err +} + +// CSect reads and returns the contents of a csect. +// func (f *File) CSect(name string) []byte { +// for _, sym := range f.Symbols { +// if sym.Name == name && sym.AuxCSect.SymbolType == XTY_SD { +// if i := sym.SectionNumber - 1; 0 <= i && i < len(f.Sections) { +// s := f.Sections[i] +// if sym.Value+uint64(sym.AuxCSect.Length) <= s.Size { +// dat := make([]byte, sym.AuxCSect.Length) +// _, err := s.sr.ReadAt(dat, int64(sym.Value)) +// if err != nil { +// return nil +// } +// return dat +// } +// } +// break +// } +// } +// return nil +// } + +// func (f *File) DWARF() (*dwarf.Data, error) { +// // There are many other DWARF sections, but these +// // are the ones the debug/dwarf package uses. +// // Don't bother loading others. +// var subtypes = [...]uint32{SSUBTYP_DWABREV, SSUBTYP_DWINFO, SSUBTYP_DWLINE, SSUBTYP_DWRNGES, SSUBTYP_DWSTR} +// var dat [len(subtypes)][]byte +// for i, subtype := range subtypes { +// s := f.SectionByType(STYP_DWARF | subtype) +// if s != nil { +// b, err := s.Data() +// if err != nil && uint64(len(b)) < s.Size { +// return nil, err +// } +// dat[i] = b +// } +// } + +// abbrev, info, line, ranges, str := dat[0], dat[1], dat[2], dat[3], dat[4] +// return dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str) +// } + +// readImportID returns the import file IDs stored inside the .loader section. +// Library name pattern is either path/base/member or base/member +func (f *File) readImportIDs(s *Section) ([]string, error) { + // Read loader header + if _, err := s.sr.Seek(0, os.SEEK_SET); err != nil { + return nil, err + } + var istlen uint32 + var nimpid int32 + var impoff uint64 + switch f.TargetMachine { + case U802TOCMAGIC: + lhdr := new(LoaderHeader32) + if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil { + return nil, err + } + istlen = lhdr.Listlen + nimpid = lhdr.Lnimpid + impoff = uint64(lhdr.Limpoff) + case U64_TOCMAGIC: + lhdr := new(LoaderHeader64) + if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil { + return nil, err + } + istlen = lhdr.Listlen + nimpid = lhdr.Lnimpid + impoff = lhdr.Limpoff + } + + // Read loader import file ID table + if _, err := s.sr.Seek(int64(impoff), os.SEEK_SET); err != nil { + return nil, err + } + table := make([]byte, istlen) + if _, err := io.ReadFull(s.sr, table); err != nil { + return nil, err + } + + offset := 0 + // First import file ID is the default LIBPATH value + libpath := cstring(table[offset:]) + f.LibraryPaths = strings.Split(libpath, ":") + offset += len(libpath) + 3 // 3 null bytes + all := make([]string, 0) + for i := 1; i < int(nimpid); i++ { + impidpath := cstring(table[offset:]) + offset += len(impidpath) + 1 + impidbase := cstring(table[offset:]) + offset += len(impidbase) + 1 + impidmem := cstring(table[offset:]) + offset += len(impidmem) + 1 + var path string + if len(impidpath) > 0 { + path = impidpath + "/" + impidbase + "/" + impidmem + } else { + path = impidbase + "/" + impidmem + } + all = append(all, path) + } + + return all, nil +} + +// ImportedSymbols returns the names of all symbols +// referred to by the binary f that are expected to be +// satisfied by other libraries at dynamic load time. +// It does not return weak symbols. +func (f *File) ImportedSymbols() ([]ImportedSymbol, error) { + s := f.SectionByType(STYP_LOADER) + if s == nil { + return nil, nil + } + // Read loader header + if _, err := s.sr.Seek(0, os.SEEK_SET); err != nil { + return nil, err + } + var stlen uint32 + var stoff uint64 + var nsyms int32 + var symoff uint64 + switch f.TargetMachine { + case U802TOCMAGIC: + lhdr := new(LoaderHeader32) + if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil { + return nil, err + } + stlen = lhdr.Lstlen + stoff = uint64(lhdr.Lstoff) + nsyms = lhdr.Lnsyms + symoff = LDHDRSZ_32 + case U64_TOCMAGIC: + lhdr := new(LoaderHeader64) + if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil { + return nil, err + } + stlen = lhdr.Lstlen + stoff = lhdr.Lstoff + nsyms = lhdr.Lnsyms + symoff = lhdr.Lsymoff + } + + // Read loader section string table + if _, err := s.sr.Seek(int64(stoff), os.SEEK_SET); err != nil { + return nil, err + } + st := make([]byte, stlen) + if _, err := io.ReadFull(s.sr, st); err != nil { + return nil, err + } + + // Read imported libraries + libs, err := f.readImportIDs(s) + if err != nil { + return nil, err + } + + // Read loader symbol table + if _, err := s.sr.Seek(int64(symoff), os.SEEK_SET); err != nil { + return nil, err + } + all := make([]ImportedSymbol, 0) + for i := 0; i < int(nsyms); i++ { + var name string + var ifile int32 + var ok bool + switch f.TargetMachine { + case U802TOCMAGIC: + ldsym := new(LoaderSymbol32) + if err := binary.Read(s.sr, binary.BigEndian, ldsym); err != nil { + return nil, err + } + if ldsym.Lsmtype&0x40 == 0 { + continue // Imported symbols only + } + zeroes := binary.BigEndian.Uint32(ldsym.Lname[:4]) + if zeroes != 0 { + name = cstring(ldsym.Lname[:]) + } else { + offset := binary.BigEndian.Uint32(ldsym.Lname[4:]) + name, ok = getString(st, offset) + if !ok { + continue + } + } + ifile = ldsym.Lifile + case U64_TOCMAGIC: + ldsym := new(LoaderSymbol64) + if err := binary.Read(s.sr, binary.BigEndian, ldsym); err != nil { + return nil, err + } + if ldsym.Lsmtype&0x40 == 0 { + continue // Imported symbols only + } + name, ok = getString(st, ldsym.Loffset) + if !ok { + continue + } + ifile = ldsym.Lifile + } + var sym ImportedSymbol + sym.Name = name + if ifile >= 1 && int(ifile) <= len(libs) { + sym.Library = libs[ifile-1] + } + all = append(all, sym) + } + + return all, nil +} + +// ImportedLibraries returns the names of all libraries +// referred to by the binary f that are expected to be +// linked with the binary at dynamic link time. +func (f *File) ImportedLibraries() ([]string, error) { + s := f.SectionByType(STYP_LOADER) + if s == nil { + return nil, nil + } + all, err := f.readImportIDs(s) + return all, err +} diff --git a/syft/pkg/cataloger/golang/internal/xcoff/file_test.go b/syft/pkg/cataloger/golang/internal/xcoff/file_test.go new file mode 100644 index 000000000..a6722e945 --- /dev/null +++ b/syft/pkg/cataloger/golang/internal/xcoff/file_test.go @@ -0,0 +1,102 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xcoff + +import ( + "reflect" + "testing" +) + +type fileTest struct { + file string + hdr FileHeader + sections []*SectionHeader + needed []string +} + +var fileTests = []fileTest{ + { + "testdata/gcc-ppc32-aix-dwarf2-exec", + FileHeader{U802TOCMAGIC}, + []*SectionHeader{ + {".text", 0x10000290, 0x00000bbd, STYP_TEXT, 0x7ae6, 0x36}, + {".data", 0x20000e4d, 0x00000437, STYP_DATA, 0x7d02, 0x2b}, + {".bss", 0x20001284, 0x0000021c, STYP_BSS, 0, 0}, + {".loader", 0x00000000, 0x000004b3, STYP_LOADER, 0, 0}, + {".dwline", 0x00000000, 0x000000df, STYP_DWARF | SSUBTYP_DWLINE, 0x7eb0, 0x7}, + {".dwinfo", 0x00000000, 0x00000314, STYP_DWARF | SSUBTYP_DWINFO, 0x7ef6, 0xa}, + {".dwabrev", 0x00000000, 0x000000d6, STYP_DWARF | SSUBTYP_DWABREV, 0, 0}, + {".dwarnge", 0x00000000, 0x00000020, STYP_DWARF | SSUBTYP_DWARNGE, 0x7f5a, 0x2}, + {".dwloc", 0x00000000, 0x00000074, STYP_DWARF | SSUBTYP_DWLOC, 0, 0}, + {".debug", 0x00000000, 0x00005e4f, STYP_DEBUG, 0, 0}, + }, + []string{"libc.a/shr.o"}, + }, + { + "testdata/gcc-ppc64-aix-dwarf2-exec", + FileHeader{U64_TOCMAGIC}, + []*SectionHeader{ + {".text", 0x10000480, 0x00000afd, STYP_TEXT, 0x8322, 0x34}, + {".data", 0x20000f7d, 0x000002f3, STYP_DATA, 0x85fa, 0x25}, + {".bss", 0x20001270, 0x00000428, STYP_BSS, 0, 0}, + {".loader", 0x00000000, 0x00000535, STYP_LOADER, 0, 0}, + {".dwline", 0x00000000, 0x000000b4, STYP_DWARF | SSUBTYP_DWLINE, 0x8800, 0x4}, + {".dwinfo", 0x00000000, 0x0000036a, STYP_DWARF | SSUBTYP_DWINFO, 0x8838, 0x7}, + {".dwabrev", 0x00000000, 0x000000b5, STYP_DWARF | SSUBTYP_DWABREV, 0, 0}, + {".dwarnge", 0x00000000, 0x00000040, STYP_DWARF | SSUBTYP_DWARNGE, 0x889a, 0x2}, + {".dwloc", 0x00000000, 0x00000062, STYP_DWARF | SSUBTYP_DWLOC, 0, 0}, + {".debug", 0x00000000, 0x00006605, STYP_DEBUG, 0, 0}, + }, + []string{"libc.a/shr_64.o"}, + }, +} + +func TestOpen(t *testing.T) { + for i := range fileTests { + tt := &fileTests[i] + + f, err := Open(tt.file) + if err != nil { + t.Error(err) + continue + } + if !reflect.DeepEqual(f.FileHeader, tt.hdr) { + t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr) + continue + } + + for i, sh := range f.Sections { + if i >= len(tt.sections) { + break + } + have := &sh.SectionHeader + want := tt.sections[i] + if !reflect.DeepEqual(have, want) { + t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want) + } + } + tn := len(tt.sections) + fn := len(f.Sections) + if tn != fn { + t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn) + } + tl := tt.needed + fl, err := f.ImportedLibraries() + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(tl, fl) { + t.Errorf("open %s: loader import = %v, want %v", tt.file, tl, fl) + } + } +} + +func TestOpenFailure(t *testing.T) { + filename := "file.go" // not an XCOFF object file + _, err := Open(filename) // don't crash + if err == nil { + t.Errorf("open %s: succeeded unexpectedly", filename) + } +} diff --git a/syft/pkg/cataloger/golang/internal/xcoff/testdata/gcc-ppc32-aix-dwarf2-exec b/syft/pkg/cataloger/golang/internal/xcoff/testdata/gcc-ppc32-aix-dwarf2-exec new file mode 100644 index 0000000000000000000000000000000000000000..810e21a0dfc78b29f2dfc5afe2bc588763177ea6 GIT binary patch literal 54694 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 z zc90l1tu-Aq{LewdS`Hd*deDdyZZrJhZF9l0#y)KzF9ea>a|U9JJ8xVJ(!9~&{HAb_ z=8&>S-w1F72iNmiA(i}E=O3Sr4qenYy z?v7yy?RDZl?Dh>)Zo$48SA3yA?mS2-iY4A9`X_j?13Qn*N0fN96C6osdij0l5qw`w zxgHR8KGV*48zHAee@XLci_u=B;=?ghGoKcmd2Ac^%PbznUgTa#xrZs2m-)C5p|e4m znU5}uXAaF}-T7WzUnOOxllBMsq`i2JM2h;_t(VMw=g0BAQquV9^^C7xZ1K%ywcLm^ z6%f*E3-Ji*^*HJ^&6La6>$A8&9;_Gh(e=8axg5H>^N;cID(clgF~LkAA?+`VqHJaP zi94a+#V&ctrQx#&il(V_AD=DD^7$P2cx5e5^F&iwe8ObevOIatP2-g{%LINWKsLrSA@=$rNOO;-q0<`pWd#56$ql4@r$=5Sx4>n!N}9mwky~+k zglU*hRq^4mS*G#%7M!Jd#63}__-t&Vx$~cJX8Dn(n!XF?njX|AaKbtl&o(vZmdB85 z6uI0J3xu59KzwEtk%!Fs5u6(g&@kdZjj%H6>TpqJWmm7b;0u~*9xE@MMj)+n*hA{+ z3+gAl-9U`kFMO9lQ!|j02J*0heAGbTMVs3z{1pTFYXkXy5V>?3!QaXeV^$KmC#bQA zF$;)bENO0wh|w08P9ymByaGL|S!0(@Blz`m=`@008<$Qa__cBAG=g6nmrf)2wQ=b* zf?pe#P9ylW(fO~zG56AG1iyYRoks9$`j+WBltCDeIux!OQ#Y1`nhx( z!LN-=rxEVC{u7VDjA9m4q( z`IJJsT1I1!`Et3tw4wjX)cCl7#@u~;JdeuLe230W-@ckYaZVby7I=!4$7ieN6ME|W zwKzwvFYEjlsD)R${{)4*Z5%r5Z+GlicoKRvpYzugPu}uqc=uFG;%VbNG@d+`k58B8 z5qh$ztOXyY34V-g$S<}$j z1L-~BH;|^k;>5zjLcfDl;5W^yZ2lBMD#(i#2WVb5IM}x<|7x4UzmxvF` z+$w4Gb7_bVXr@5($^~dDI8(v$EXQXG&*L~EHTEX;R3-JOEIe`INw+PwsM1%;@BRP{ zYlF`fLRv-R<9xWOEn?uzXJELM1DqxQh)ctq4@q*_C$AnkC+7eSbDmnB^Ai2>bFt+8$m_xR?727x zX_)g&GiUUo-Slj%dIJxH#-sA_kh$4vk>9LNNq({)9(G)+;}uYN&~2$`(_{JtJ&2Cq zwhkii@xiztqv59>JGd~On=AH?-nw)5{)69H6T> z^k98|PPpWz(%ej8k7T(o_M@UTNBpavi}RiTv6S>TG^c~NhO{{v6+R3!ia literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/golang/internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec b/syft/pkg/cataloger/golang/internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec new file mode 100644 index 0000000000000000000000000000000000000000..707d01ebd43487081a620250a213dcdba7a80379 GIT binary patch literal 57152 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 S7C_XD%~zkaY^b%gYX1+A-c`&1 literal 0 HcmV?d00001 diff --git a/syft/pkg/cataloger/golang/internal/xcoff/xcoff.go b/syft/pkg/cataloger/golang/internal/xcoff/xcoff.go new file mode 100644 index 000000000..96cf01d25 --- /dev/null +++ b/syft/pkg/cataloger/golang/internal/xcoff/xcoff.go @@ -0,0 +1,373 @@ +// The code in this package comes from: +// https://github.com/golang/go/tree/master/src/internal/xcoff +// it was copied over to add support for xcoff binaries. +// Golang keeps this package as internal, forbidding its external use. + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//nolint // this is an internal golang lib +package xcoff + +// File Header. +type FileHeader32 struct { + Fmagic uint16 // Target machine + Fnscns uint16 // Number of sections + Ftimedat int32 // Time and date of file creation + Fsymptr uint32 // Byte offset to symbol table start + Fnsyms int32 // Number of entries in symbol table + Fopthdr uint16 // Number of bytes in optional header + Fflags uint16 // Flags +} + +type FileHeader64 struct { + Fmagic uint16 // Target machine + Fnscns uint16 // Number of sections + Ftimedat int32 // Time and date of file creation + Fsymptr uint64 // Byte offset to symbol table start + Fopthdr uint16 // Number of bytes in optional header + Fflags uint16 // Flags + Fnsyms int32 // Number of entries in symbol table +} + +const ( + FILHSZ_32 = 20 + FILHSZ_64 = 24 +) +const ( + U802TOCMAGIC = 0737 // AIX 32-bit XCOFF + U64_TOCMAGIC = 0767 // AIX 64-bit XCOFF +) + +// Flags that describe the type of the object file. +const ( + F_RELFLG = 0x0001 + F_EXEC = 0x0002 + F_LNNO = 0x0004 + F_FDPR_PROF = 0x0010 + F_FDPR_OPTI = 0x0020 + F_DSA = 0x0040 + F_VARPG = 0x0100 + F_DYNLOAD = 0x1000 + F_SHROBJ = 0x2000 + F_LOADONLY = 0x4000 +) + +// Section Header. +type SectionHeader32 struct { + Sname [8]byte // Section name + Spaddr uint32 // Physical address + Svaddr uint32 // Virtual address + Ssize uint32 // Section size + Sscnptr uint32 // Offset in file to raw data for section + Srelptr uint32 // Offset in file to relocation entries for section + Slnnoptr uint32 // Offset in file to line number entries for section + Snreloc uint16 // Number of relocation entries + Snlnno uint16 // Number of line number entries + Sflags uint32 // Flags to define the section type +} + +type SectionHeader64 struct { + Sname [8]byte // Section name + Spaddr uint64 // Physical address + Svaddr uint64 // Virtual address + Ssize uint64 // Section size + Sscnptr uint64 // Offset in file to raw data for section + Srelptr uint64 // Offset in file to relocation entries for section + Slnnoptr uint64 // Offset in file to line number entries for section + Snreloc uint32 // Number of relocation entries + Snlnno uint32 // Number of line number entries + Sflags uint32 // Flags to define the section type + Spad uint32 // Needs to be 72 bytes long +} + +// Flags defining the section type. +const ( + STYP_DWARF = 0x0010 + STYP_TEXT = 0x0020 + STYP_DATA = 0x0040 + STYP_BSS = 0x0080 + STYP_EXCEPT = 0x0100 + STYP_INFO = 0x0200 + STYP_TDATA = 0x0400 + STYP_TBSS = 0x0800 + STYP_LOADER = 0x1000 + STYP_DEBUG = 0x2000 + STYP_TYPCHK = 0x4000 + STYP_OVRFLO = 0x8000 +) +const ( + SSUBTYP_DWINFO = 0x10000 // DWARF info section + SSUBTYP_DWLINE = 0x20000 // DWARF line-number section + SSUBTYP_DWPBNMS = 0x30000 // DWARF public names section + SSUBTYP_DWPBTYP = 0x40000 // DWARF public types section + SSUBTYP_DWARNGE = 0x50000 // DWARF aranges section + SSUBTYP_DWABREV = 0x60000 // DWARF abbreviation section + SSUBTYP_DWSTR = 0x70000 // DWARF strings section + SSUBTYP_DWRNGES = 0x80000 // DWARF ranges section + SSUBTYP_DWLOC = 0x90000 // DWARF location lists section + SSUBTYP_DWFRAME = 0xA0000 // DWARF frames section + SSUBTYP_DWMAC = 0xB0000 // DWARF macros section +) + +// Symbol Table Entry. +type SymEnt32 struct { + Nname [8]byte // Symbol name + Nvalue uint32 // Symbol value + Nscnum int16 // Section number of symbol + Ntype uint16 // Basic and derived type specification + Nsclass int8 // Storage class of symbol + Nnumaux int8 // Number of auxiliary entries +} + +type SymEnt64 struct { + Nvalue uint64 // Symbol value + Noffset uint32 // Offset of the name in string table or .debug section + Nscnum int16 // Section number of symbol + Ntype uint16 // Basic and derived type specification + Nsclass int8 // Storage class of symbol + Nnumaux int8 // Number of auxiliary entries +} + +const SYMESZ = 18 + +const ( + // Nscnum + N_DEBUG = -2 + N_ABS = -1 + N_UNDEF = 0 + + //Ntype + SYM_V_INTERNAL = 0x1000 + SYM_V_HIDDEN = 0x2000 + SYM_V_PROTECTED = 0x3000 + SYM_V_EXPORTED = 0x4000 + SYM_TYPE_FUNC = 0x0020 // is function +) + +// Storage Class. +const ( + C_NULL = 0 // Symbol table entry marked for deletion + C_EXT = 2 // External symbol + C_STAT = 3 // Static symbol + C_BLOCK = 100 // Beginning or end of inner block + C_FCN = 101 // Beginning or end of function + C_FILE = 103 // Source file name and compiler information + C_HIDEXT = 107 // Unnamed external symbol + C_BINCL = 108 // Beginning of include file + C_EINCL = 109 // End of include file + C_WEAKEXT = 111 // Weak external symbol + C_DWARF = 112 // DWARF symbol + C_GSYM = 128 // Global variable + C_LSYM = 129 // Automatic variable allocated on stack + C_PSYM = 130 // Argument to subroutine allocated on stack + C_RSYM = 131 // Register variable + C_RPSYM = 132 // Argument to function or procedure stored in register + C_STSYM = 133 // Statically allocated symbol + C_BCOMM = 135 // Beginning of common block + C_ECOML = 136 // Local member of common block + C_ECOMM = 137 // End of common block + C_DECL = 140 // Declaration of object + C_ENTRY = 141 // Alternate entry + C_FUN = 142 // Function or procedure + C_BSTAT = 143 // Beginning of static block + C_ESTAT = 144 // End of static block + C_GTLS = 145 // Global thread-local variable + C_STTLS = 146 // Static thread-local variable +) + +// File Auxiliary Entry +type AuxFile64 struct { + Xfname [8]byte // Name or offset inside string table + Xftype uint8 // Source file string type + Xauxtype uint8 // Type of auxiliary entry +} + +// Function Auxiliary Entry +type AuxFcn32 struct { + Xexptr uint32 // File offset to exception table entry + Xfsize uint32 // Size of function in bytes + Xlnnoptr uint32 // File pointer to line number + Xendndx uint32 // Symbol table index of next entry + Xpad uint16 // Unused +} +type AuxFcn64 struct { + Xlnnoptr uint64 // File pointer to line number + Xfsize uint32 // Size of function in bytes + Xendndx uint32 // Symbol table index of next entry + Xpad uint8 // Unused + Xauxtype uint8 // Type of auxiliary entry +} + +type AuxSect64 struct { + Xscnlen uint64 // section length + Xnreloc uint64 // Num RLDs + pad uint8 + Xauxtype uint8 // Type of auxiliary entry +} + +// csect Auxiliary Entry. +type AuxCSect32 struct { + Xscnlen int32 // Length or symbol table index + Xparmhash uint32 // Offset of parameter type-check string + Xsnhash uint16 // .typchk section number + Xsmtyp uint8 // Symbol alignment and type + Xsmclas uint8 // Storage-mapping class + Xstab uint32 // Reserved + Xsnstab uint16 // Reserved +} + +type AuxCSect64 struct { + Xscnlenlo uint32 // Lower 4 bytes of length or symbol table index + Xparmhash uint32 // Offset of parameter type-check string + Xsnhash uint16 // .typchk section number + Xsmtyp uint8 // Symbol alignment and type + Xsmclas uint8 // Storage-mapping class + Xscnlenhi int32 // Upper 4 bytes of length or symbol table index + Xpad uint8 // Unused + Xauxtype uint8 // Type of auxiliary entry +} + +// Auxiliary type +// const ( +// _AUX_EXCEPT = 255 +// _AUX_FCN = 254 +// _AUX_SYM = 253 +// _AUX_FILE = 252 +// _AUX_CSECT = 251 +// _AUX_SECT = 250 +// ) + +// Symbol type field. +const ( + XTY_ER = 0 // External reference + XTY_SD = 1 // Section definition + XTY_LD = 2 // Label definition + XTY_CM = 3 // Common csect definition +) + +// Defines for File auxiliary definitions: x_ftype field of x_file +const ( + XFT_FN = 0 // Source File Name + XFT_CT = 1 // Compile Time Stamp + XFT_CV = 2 // Compiler Version Number + XFT_CD = 128 // Compiler Defined Information +) + +// Storage-mapping class. +const ( + XMC_PR = 0 // Program code + XMC_RO = 1 // Read-only constant + XMC_DB = 2 // Debug dictionary table + XMC_TC = 3 // TOC entry + XMC_UA = 4 // Unclassified + XMC_RW = 5 // Read/Write data + XMC_GL = 6 // Global linkage + XMC_XO = 7 // Extended operation + XMC_SV = 8 // 32-bit supervisor call descriptor + XMC_BS = 9 // BSS class + XMC_DS = 10 // Function descriptor + XMC_UC = 11 // Unnamed FORTRAN common + XMC_TC0 = 15 // TOC anchor + XMC_TD = 16 // Scalar data entry in the TOC + XMC_SV64 = 17 // 64-bit supervisor call descriptor + XMC_SV3264 = 18 // Supervisor call descriptor for both 32-bit and 64-bit + XMC_TL = 20 // Read/Write thread-local data + XMC_UL = 21 // Read/Write thread-local data (.tbss) + XMC_TE = 22 // TOC entry +) + +// Loader Header. +type LoaderHeader32 struct { + Lversion int32 // Loader section version number + Lnsyms int32 // Number of symbol table entries + Lnreloc int32 // Number of relocation table entries + Listlen uint32 // Length of import file ID string table + Lnimpid int32 // Number of import file IDs + Limpoff uint32 // Offset to start of import file IDs + Lstlen uint32 // Length of string table + Lstoff uint32 // Offset to start of string table +} + +type LoaderHeader64 struct { + Lversion int32 // Loader section version number + Lnsyms int32 // Number of symbol table entries + Lnreloc int32 // Number of relocation table entries + Listlen uint32 // Length of import file ID string table + Lnimpid int32 // Number of import file IDs + Lstlen uint32 // Length of string table + Limpoff uint64 // Offset to start of import file IDs + Lstoff uint64 // Offset to start of string table + Lsymoff uint64 // Offset to start of symbol table + Lrldoff uint64 // Offset to start of relocation entries +} + +const ( + LDHDRSZ_32 = 32 + LDHDRSZ_64 = 56 +) + +// Loader Symbol. +type LoaderSymbol32 struct { + Lname [8]byte // Symbol name or byte offset into string table + Lvalue uint32 // Address field + Lscnum int16 // Section number containing symbol + Lsmtype int8 // Symbol type, export, import flags + Lsmclas int8 // Symbol storage class + Lifile int32 // Import file ID; ordinal of import file IDs + Lparm uint32 // Parameter type-check field +} + +type LoaderSymbol64 struct { + Lvalue uint64 // Address field + Loffset uint32 // Byte offset into string table of symbol name + Lscnum int16 // Section number containing symbol + Lsmtype int8 // Symbol type, export, import flags + Lsmclas int8 // Symbol storage class + Lifile int32 // Import file ID; ordinal of import file IDs + Lparm uint32 // Parameter type-check field +} + +type Reloc32 struct { + Rvaddr uint32 // (virtual) address of reference + Rsymndx uint32 // Index into symbol table + Rsize uint8 // Sign and reloc bit len + Rtype uint8 // Toc relocation type +} + +type Reloc64 struct { + Rvaddr uint64 // (virtual) address of reference + Rsymndx uint32 // Index into symbol table + Rsize uint8 // Sign and reloc bit len + Rtype uint8 // Toc relocation type +} + +const ( + R_POS = 0x00 // A(sym) Positive Relocation + R_NEG = 0x01 // -A(sym) Negative Relocation + R_REL = 0x02 // A(sym-*) Relative to self + R_TOC = 0x03 // A(sym-TOC) Relative to TOC + R_TRL = 0x12 // A(sym-TOC) TOC Relative indirect load. + + R_TRLA = 0x13 // A(sym-TOC) TOC Rel load address. modifiable inst + R_GL = 0x05 // A(external TOC of sym) Global Linkage + R_TCL = 0x06 // A(local TOC of sym) Local object TOC address + R_RL = 0x0C // A(sym) Pos indirect load. modifiable instruction + R_RLA = 0x0D // A(sym) Pos Load Address. modifiable instruction + R_REF = 0x0F // AL0(sym) Non relocating ref. No garbage collect + R_BA = 0x08 // A(sym) Branch absolute. Cannot modify instruction + R_RBA = 0x18 // A(sym) Branch absolute. modifiable instruction + R_BR = 0x0A // A(sym-*) Branch rel to self. non modifiable + R_RBR = 0x1A // A(sym-*) Branch rel to self. modifiable instr + + R_TLS = 0x20 // General-dynamic reference to TLS symbol + R_TLS_IE = 0x21 // Initial-exec reference to TLS symbol + R_TLS_LD = 0x22 // Local-dynamic reference to TLS symbol + R_TLS_LE = 0x23 // Local-exec reference to TLS symbol + R_TLSM = 0x24 // Module reference to TLS symbol + R_TLSML = 0x25 // Module reference to local (own) module + + R_TOCU = 0x30 // Relative to TOC - high order bits + R_TOCL = 0x31 // Relative to TOC - low order bits +) diff --git a/syft/pkg/cataloger/golang/parse_go_bin.go b/syft/pkg/cataloger/golang/parse_go_bin.go index bf18c3ba4..3a1c3f017 100644 --- a/syft/pkg/cataloger/golang/parse_go_bin.go +++ b/syft/pkg/cataloger/golang/parse_go_bin.go @@ -1,26 +1,53 @@ +//nolint package golang import ( - "bufio" + "bytes" + "debug/elf" + "debug/macho" + "debug/pe" + "errors" "fmt" "io" + "runtime/debug" "strings" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/golang/internal/xcoff" "github.com/anchore/syft/syft/source" ) -const ( - packageIdentifier = "dep" - replaceIdentifier = "=>" +const GOARCH = "GOARCH" + +var ( + // errUnrecognizedFormat is returned when a given executable file doesn't + // appear to be in a known format, or it breaks the rules of that format, + // or when there are I/O errors reading the file. + errUnrecognizedFormat = errors.New("unrecognized file format") ) -type exeOpener func(file io.ReadCloser) ([]exe, error) +func makeGoMainPackage(mod *debug.BuildInfo, arch string, location source.Location) pkg.Package { + gbs := getBuildSettings(mod.Settings) + main := newGoBinaryPackage(&mod.Main, mod.GoVersion, arch, location, gbs) + main.Version = "" + + if v, ok := gbs["vcs.revision"]; ok { + main.Version = v + } + + return main +} + +func newGoBinaryPackage(dep *debug.Module, goVersion, architecture string, location source.Location, buildSettings map[string]string) pkg.Package { + if dep.Replace != nil { + dep = dep.Replace + } -func newGoBinaryPackage(name, version, h1Digest, goVersion, architecture string, location source.Location) pkg.Package { p := pkg.Package{ - Name: name, - Version: version, + FoundBy: catalogerName, + Name: dep.Path, + Version: dep.Version, Language: pkg.Go, Type: pkg.GoModulePkg, Locations: []source.Location{ @@ -29,8 +56,9 @@ func newGoBinaryPackage(name, version, h1Digest, goVersion, architecture string, MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ GoCompiledVersion: goVersion, - H1Digest: h1Digest, + H1Digest: dep.Sum, Architecture: architecture, + BuildSettings: buildSettings, }, } @@ -39,57 +67,128 @@ func newGoBinaryPackage(name, version, h1Digest, goVersion, architecture string, return p } -func parseGoBin(location source.Location, reader io.ReadCloser, opener exeOpener) (pkgs []pkg.Package, err error) { - var exes []exe - // it has been found that there are stdlib paths within openExe that can panic. We want to prevent this behavior - // bubbling up and halting execution. For this reason we try to recover from any panic and return an error. - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("recovered from panic while parse go binary at %q: %+v", location.RealPath, r) - } - }() - - // Identify if bin was compiled by go - exes, err = opener(reader) - if err != nil { - return pkgs, err +// getArchs finds a binary architecture by two ways: +// 1) reading build info from binaries compiled by go1.18+ +// 2) reading file headers from binaries compiled by < go1.18 +func getArchs(readers []io.ReaderAt, builds []*debug.BuildInfo) []string { + if len(readers) != len(builds) { + log.Warnf("golang cataloger: bin parsing: number of builds and readers doesn't match") + return nil } - for _, x := range exes { - goVersion, mod := findVers(x) - pkgs = append(pkgs, buildGoPkgInfo(location, mod, goVersion, x.ArchName())...) + if len(readers) == 0 || len(builds) == 0 { + log.Warnf("golang cataloger: bin parsing: %d readers and %d build info items", len(readers), len(builds)) + return nil } - return pkgs, err -} -func buildGoPkgInfo(location source.Location, mod, goVersion, arch string) []pkg.Package { - pkgsSlice := make([]pkg.Package, 0) - scanner := bufio.NewScanner(strings.NewReader(mod)) + archs := make([]string, len(builds)) + for i, build := range builds { + archs[i] = getGOARCH(build.Settings) + } - // filter mod dependencies: [dep, name, version, sha] - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) + // if architecture was found via build settings return + if archs[0] != "" { + return archs + } - // must have dep, name, version - if len(fields) < 3 { + for i, r := range readers { + a, err := getGOARCHFromBin(r) + if err != nil { + log.Warnf("golang cataloger: bin parsing: getting arch from binary: %v", err) continue } - name := fields[1] - version := fields[2] - h1Digest := "" - // if dep is *not* vendored, it'll also have h1digest - if len(fields) >= 4 { - h1Digest = fields[3] - } + archs[i] = a + } + return archs +} - if fields[0] == packageIdentifier { - pkgsSlice = append(pkgsSlice, newGoBinaryPackage(name, version, h1Digest, goVersion, arch, location)) - } - if fields[0] == replaceIdentifier { - // replace the previous entry in the package slice - pkgsSlice[len(pkgsSlice)-1] = newGoBinaryPackage(name, version, h1Digest, goVersion, arch, location) +func getGOARCH(settings []debug.BuildSetting) string { + for _, s := range settings { + if s.Key == GOARCH { + return s.Value } } - return pkgsSlice + + return "" +} + +func getGOARCHFromBin(r io.ReaderAt) (string, error) { + // Read the first bytes of the file to identify the format, then delegate to + // a format-specific function to load segment and section headers. + ident := make([]byte, 16) + if n, err := r.ReadAt(ident, 0); n < len(ident) || err != nil { + return "", fmt.Errorf("unrecognized file format: %w", err) + } + + var arch string + switch { + case bytes.HasPrefix(ident, []byte("\x7FELF")): + f, err := elf.NewFile(r) + if err != nil { + return "", fmt.Errorf("unrecognized file format: %w", err) + } + arch = f.Machine.String() + case bytes.HasPrefix(ident, []byte("MZ")): + f, err := pe.NewFile(r) + if err != nil { + return "", fmt.Errorf("unrecognized file format: %w", err) + } + arch = fmt.Sprintf("%d", f.Machine) + case bytes.HasPrefix(ident, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(ident[1:], []byte("\xFA\xED\xFE")): + f, err := macho.NewFile(r) + if err != nil { + return "", fmt.Errorf("unrecognized file format: %w", err) + } + arch = f.Cpu.String() + case bytes.HasPrefix(ident, []byte{0x01, 0xDF}) || bytes.HasPrefix(ident, []byte{0x01, 0xF7}): + f, err := xcoff.NewFile(r) + if err != nil { + return "", fmt.Errorf("unrecognized file format: %w", err) + } + arch = fmt.Sprintf("%d", f.FileHeader.TargetMachine) + default: + return "", errUnrecognizedFormat + } + + arch = strings.Replace(arch, "EM_", "", 1) + arch = strings.Replace(arch, "Cpu", "", 1) + arch = strings.ToLower(arch) + + return arch, nil +} + +func getBuildSettings(settings []debug.BuildSetting) map[string]string { + m := make(map[string]string) + for _, s := range settings { + m[s.Key] = s.Value + } + return m +} + +func buildGoPkgInfo(location source.Location, mod *debug.BuildInfo, arch string) []pkg.Package { + var pkgs []pkg.Package + if mod == nil { + return pkgs + } + + for _, dep := range mod.Deps { + if dep == nil { + continue + } + + pkgs = append(pkgs, newGoBinaryPackage(dep, mod.GoVersion, arch, location, nil)) + } + + // NOTE(jonasagx): this use happened originally while creating unit tests. It might never + // happen in the wild, but I kept it as a safeguard against empty modules. + var empty debug.Module + if mod.Main == empty { + return pkgs + } + + main := makeGoMainPackage(mod, arch, location) + pkgs = append(pkgs, main) + + return pkgs } diff --git a/syft/pkg/cataloger/golang/parse_go_bin_test.go b/syft/pkg/cataloger/golang/parse_go_bin_test.go index c74b869b1..2cd1cfcbd 100644 --- a/syft/pkg/cataloger/golang/parse_go_bin_test.go +++ b/syft/pkg/cataloger/golang/parse_go_bin_test.go @@ -1,38 +1,254 @@ package golang import ( + "bufio" "io" + "os" + "os/exec" + "path/filepath" + "runtime/debug" + "strconv" + "syscall" "testing" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// make will run the default make target for the given test fixture path +func runMakeTarget(t *testing.T, fixtureName string) { + cwd, err := os.Getwd() + require.NoError(t, err) + fixtureDir := filepath.Join(cwd, "test-fixtures/", fixtureName) + + t.Logf("Generating Fixture in %q", fixtureDir) + + cmd := exec.Command("make") + cmd.Dir = fixtureDir + + stderr, err := cmd.StderrPipe() + require.NoError(t, err) + + stdout, err := cmd.StdoutPipe() + require.NoError(t, err) + + err = cmd.Start() + require.NoError(t, err) + + show := func(label string, reader io.ReadCloser) { + scanner := bufio.NewScanner(reader) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + t.Logf("%s: %s", label, scanner.Text()) + } + } + go show("out", stdout) + go show("err", stderr) + + if err := cmd.Wait(); err != nil { + if exiterr, ok := err.(*exec.ExitError); ok { + // The program has exited with an exit code != 0 + + // This works on both Unix and Windows. Although package + // syscall is generally platform dependent, WaitStatus is + // defined for both Unix and Windows and in both cases has + // an ExitStatus() method with the same signature. + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + if status.ExitStatus() != 0 { + t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus()) + } + } + } else { + t.Fatalf("unable to get generate fixture result: %+v", err) + } + } +} + +func Test_getGOARCHFromBin(t *testing.T) { + runMakeTarget(t, "archs") + + tests := []struct { + name string + filepath string + expected string + }{ + { + name: "pe", + filepath: "test-fixtures/archs/binaries/hello-win-amd64", + // see: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types + expected: strconv.Itoa(0x8664), + }, + { + name: "elf-ppc64", + filepath: "test-fixtures/archs/binaries/hello-linux-ppc64le", + expected: "ppc64", + }, + { + name: "mach-o-arm64", + filepath: "test-fixtures/archs/binaries/hello-mach-o-arm64", + expected: "arm64", + }, + { + name: "linux-arm", + filepath: "test-fixtures/archs/binaries/hello-linux-arm", + expected: "arm", + }, + { + name: "xcoff-32bit", + filepath: "internal/xcoff/testdata/gcc-ppc32-aix-dwarf2-exec", + expected: strconv.Itoa(0x1DF), + }, + { + name: "xcoff-64bit", + filepath: "internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec", + expected: strconv.Itoa(0x1F7), + }, + } + + for _, tt := range tests { + f, err := os.Open(tt.filepath) + require.NoError(t, err) + arch, err := getGOARCHFromBin(f) + require.NoError(t, err, "test name: %s", tt.name) + assert.Equal(t, tt.expected, arch) + } + +} + func TestBuildGoPkgInfo(t *testing.T) { const ( - goCompiledVersion = "1.17" + goCompiledVersion = "1.18" archDetails = "amd64" ) + buildSettings := map[string]string{ + "GOARCH": "amd64", + "GOOS": "darwin", + "GOAMD64": "v1", + } + + expectedMain := pkg.Package{ + Name: "github.com/anchore/syft", + FoundBy: catalogerName, + Language: pkg.Go, + Type: pkg.GoModulePkg, + Locations: []source.Location{ + { + Coordinates: source.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + }, + }, + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: buildSettings, + }, + } + tests := []struct { name string - mod string + mod *debug.BuildInfo + arch string expected []pkg.Package }{ { - name: "buildGoPkgInfo parses a blank mod string and returns no packages", - mod: "", - expected: make([]pkg.Package, 0), + name: "buildGoPkgInfo parses a nil mod", + mod: nil, + expected: []pkg.Package(nil), }, { - name: "buildGoPkgInfo parses a populated mod string and returns packages but no source info", - mod: `path github.com/anchore/syft mod github.com/anchore/syft (devel) - dep github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic= - dep github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg= - dep github.com/anchore/client-go v1.2.3`, + name: "buildGoPkgInfo parses a blank mod and returns no packages", + mod: &debug.BuildInfo{}, + expected: []pkg.Package(nil), + }, + { + name: "buildGoPkgInfo parses a mod without main module", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + }, + Deps: []*debug.Module{ + { + Path: "github.com/adrg/xdg", + Version: "v0.2.1", + Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=", + }, + }, + }, expected: []pkg.Package{ { Name: "github.com/adrg/xdg", + FoundBy: catalogerName, + Version: "v0.2.1", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Locations: []source.Location{ + { + Coordinates: source.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + }, + }, + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + H1Digest: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=", + }, + }, + }, + }, + { + name: "buildGoPkgInfo parses a mod without packages", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + }, + }, + expected: []pkg.Package{expectedMain}, + }, + { + name: "buildGoPkgInfo parses a populated mod string and returns packages but no source info", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + }, + Deps: []*debug.Module{ + { + Path: "github.com/adrg/xdg", + Version: "v0.2.1", + Sum: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=", + }, + { + Path: "github.com/anchore/client-go", + Version: "v0.0.0-20210222170800-9c70f9b80bcf", + Sum: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=", + }, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/adrg/xdg", + FoundBy: catalogerName, Version: "v0.2.1", Language: pkg.Go, Type: pkg.GoModulePkg, @@ -53,6 +269,7 @@ func TestBuildGoPkgInfo(t *testing.T) { }, { Name: "github.com/anchore/client-go", + FoundBy: catalogerName, Version: "v0.0.0-20210222170800-9c70f9b80bcf", Language: pkg.Go, Type: pkg.GoModulePkg, @@ -71,59 +288,42 @@ func TestBuildGoPkgInfo(t *testing.T) { H1Digest: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=", }, }, - { - Name: "github.com/anchore/client-go", - Version: "v1.2.3", - Language: pkg.Go, - Type: pkg.GoModulePkg, - Locations: []source.Location{ - { - Coordinates: source.Coordinates{ - RealPath: "/a-path", - FileSystemID: "layer-id", - }, - }, - }, - MetadataType: pkg.GolangBinMetadataType, - Metadata: pkg.GolangBinMetadata{ - GoCompiledVersion: goCompiledVersion, - Architecture: archDetails, - }, - }, + expectedMain, }, }, { name: "buildGoPkgInfo parses a populated mod string and returns packages when a replace directive exists", - mod: `path github.com/anchore/test - mod github.com/anchore/test (devel) - dep golang.org/x/net v0.0.0-20211006190231-62292e806868 h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k= - dep golang.org/x/sys v0.0.0-20211006194710-c8a6f5223071 h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE= - dep golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 - => golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= - dep github.com/anchore/client-go v1.2.3`, - expected: []pkg.Package{ - { - Name: "golang.org/x/net", - Version: "v0.0.0-20211006190231-62292e806868", - Language: pkg.Go, - Type: pkg.GoModulePkg, - Locations: []source.Location{ - { - Coordinates: source.Coordinates{ - RealPath: "/a-path", - FileSystemID: "layer-id", - }, + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + }, + Deps: []*debug.Module{ + { + Path: "golang.org/x/sys", + Version: "v0.0.0-20211006194710-c8a6f5223071", + Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=", + }, + { + Path: "golang.org/x/term", + Version: "v0.0.0-20210927222741-03fcf44c2211", + Sum: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=", + Replace: &debug.Module{ + Path: "golang.org/x/term", + Version: "v0.0.0-20210916214954-140adaaadfaf", + Sum: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=", }, }, - MetadataType: pkg.GolangBinMetadataType, - Metadata: pkg.GolangBinMetadata{ - GoCompiledVersion: goCompiledVersion, - Architecture: archDetails, - H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=", - }, }, + }, + expected: []pkg.Package{ { Name: "golang.org/x/sys", + FoundBy: catalogerName, Version: "v0.0.0-20211006194710-c8a6f5223071", Language: pkg.Go, Type: pkg.GoModulePkg, @@ -132,18 +332,15 @@ func TestBuildGoPkgInfo(t *testing.T) { Coordinates: source.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", - }, - }, - }, + }}}, MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - H1Digest: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=", - }, - }, + H1Digest: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE="}}, { Name: "golang.org/x/term", + FoundBy: catalogerName, Version: "v0.0.0-20210916214954-140adaaadfaf", Language: pkg.Go, Type: pkg.GoModulePkg, @@ -159,28 +356,9 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinMetadata{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - H1Digest: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=", - }, - }, - { - Name: "github.com/anchore/client-go", - Version: "v1.2.3", - Language: pkg.Go, - Type: pkg.GoModulePkg, - Locations: []source.Location{ - { - Coordinates: source.Coordinates{ - RealPath: "/a-path", - FileSystemID: "layer-id", - }, - }, - }, - MetadataType: pkg.GolangBinMetadataType, - Metadata: pkg.GolangBinMetadata{ - GoCompiledVersion: goCompiledVersion, - Architecture: archDetails, - }, + H1Digest: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI="}, }, + expectedMain, }, }, } @@ -197,30 +375,8 @@ func TestBuildGoPkgInfo(t *testing.T) { FileSystemID: "layer-id", }, } - pkgs := buildGoPkgInfo(location, test.mod, goCompiledVersion, archDetails) + pkgs := buildGoPkgInfo(location, test.mod, test.arch) assert.Equal(t, test.expected, pkgs) }) } } - -func Test_parseGoBin_recoversFromPanic(t *testing.T) { - freakOut := func(file io.ReadCloser) ([]exe, error) { - panic("baaahhh!") - } - tests := []struct { - name string - wantPkgs []pkg.Package - wantErr assert.ErrorAssertionFunc - }{ - { - name: "recovers from panic", - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - pkgs, err := parseGoBin(source.NewLocation("some/path"), nil, freakOut) - assert.Error(t, err) - assert.Nil(t, pkgs) - }) - } -} diff --git a/syft/pkg/cataloger/golang/scan_bin.go b/syft/pkg/cataloger/golang/scan_bin.go new file mode 100644 index 000000000..f7359a568 --- /dev/null +++ b/syft/pkg/cataloger/golang/scan_bin.go @@ -0,0 +1,65 @@ +//nolint +package golang + +import ( + "debug/buildinfo" + "io" + "runtime/debug" + + macho "github.com/anchore/go-macholibre" + "github.com/anchore/syft/internal/log" +) + +// unionReader is a single interface with all reading functions used by golang bin +// cataloger. +type unionReader interface { + io.Reader + io.ReaderAt + io.Seeker + io.Closer +} + +// scanFile scans file to try to report the Go and module versions. +func scanFile(reader unionReader, filename string) ([]*debug.BuildInfo, []string) { + // NOTE: multiple readers are returned to cover universal binaries, which are files + // with more than one binary + readers, err := getReaders(reader) + if err != nil { + log.Warnf("golang cataloger: opening binary: %v", err) + return nil, nil + } + + var builds []*debug.BuildInfo + for _, r := range readers { + bi, err := buildinfo.Read(r) + if err != nil { + log.Warnf("golang cataloger: scanning file %s: %v", filename, err) + return nil, nil + } + builds = append(builds, bi) + } + + archs := getArchs(readers, builds) + + return builds, archs +} + +// getReaders extracts one or more io.ReaderAt objects representing binaries that can be processed (multiple binaries in the case for multi-architecture binaries). +func getReaders(f unionReader) ([]io.ReaderAt, error) { + if macho.IsUniversalMachoBinary(f) { + machoReaders, err := macho.ExtractReaders(f) + if err != nil { + log.Debugf("extracting readers: %v", err) + return nil, err + } + + var readers []io.ReaderAt + for _, e := range machoReaders { + readers = append(readers, e.Reader) + } + + return readers, nil + } + + return []io.ReaderAt{f}, nil +} diff --git a/syft/pkg/cataloger/golang/test-fixtures/archs/.gitignore b/syft/pkg/cataloger/golang/test-fixtures/archs/.gitignore new file mode 100644 index 000000000..3155ad3e0 --- /dev/null +++ b/syft/pkg/cataloger/golang/test-fixtures/archs/.gitignore @@ -0,0 +1 @@ +binaries/ \ No newline at end of file diff --git a/syft/pkg/cataloger/golang/test-fixtures/archs/Makefile b/syft/pkg/cataloger/golang/test-fixtures/archs/Makefile new file mode 100644 index 000000000..60eee7ff9 --- /dev/null +++ b/syft/pkg/cataloger/golang/test-fixtures/archs/Makefile @@ -0,0 +1,29 @@ +DESTINATION=binaries + +all: $(DESTINATION)/hello-mach-o-arm64 $(DESTINATION)/hello-linux-arm $(DESTINATION)/hello-linux-ppc64le $(DESTINATION)/hello-win-amd64 + +$(DESTINATION)/hello-mach-o-arm64: + mkdir -p $(DESTINATION) + GOARCH=arm64 GOOS=darwin ./src/build.sh $(DESTINATION)/hello-mach-o-arm64 + +$(DESTINATION)/hello-linux-arm: + mkdir -p $(DESTINATION) + GOARCH=arm GOOS=linux ./src/build.sh $(DESTINATION)/hello-linux-arm + +$(DESTINATION)/hello-linux-ppc64le: + mkdir -p $(DESTINATION) + GOARCH=ppc64le GOOS=linux ./src/build.sh $(DESTINATION)/hello-linux-ppc64le + +$(DESTINATION)/hello-win-amd64: + mkdir -p $(DESTINATION) + GOARCH=amd64 GOOS=windows ./src/build.sh $(DESTINATION)/hello-win-amd64 + +# we need a way to determine if CI should bust the test cache based on the source material +$(DESTINATION).fingerprint: clean + mkdir -p $(DESTINATION) + find src -type f -exec sha256sum {} \; | sort | tee /dev/stderr | tee $(DESTINATION).fingerprint + sha256sum $(DESTINATION).fingerprint + +.PHONY: clean +clean: + rm -f $(DESTINATION)/* diff --git a/syft/pkg/cataloger/golang/test-fixtures/archs/src/build.sh b/syft/pkg/cataloger/golang/test-fixtures/archs/src/build.sh new file mode 100755 index 000000000..8a3919470 --- /dev/null +++ b/syft/pkg/cataloger/golang/test-fixtures/archs/src/build.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -uxe + +# note: this can be easily done in a 1-liner, however circle CI does NOT allow volume mounts from the host in docker executors (since they are on remote hosts, where the host files are inaccessible) +# note: gocache override is so we can run docker build not as root in a container without permission issues + +BINARY=$1 +CTRID=$(docker create -e GOOS="${GOOS}" -e GOARCH="${GOARCH}" -u "$(id -u):$(id -g)" -e GOCACHE=/tmp -w /src golang:1.17 go build -o main main.go) + +function cleanup() { + docker rm "${CTRID}" +} + +trap cleanup EXIT +set +e + +# note: pwd = parent directory (archs) +docker cp "$(pwd)/src" "${CTRID}:/" +docker start -a "${CTRID}" +docker cp "${CTRID}:/src/main" "$BINARY" diff --git a/syft/pkg/cataloger/golang/test-fixtures/archs/src/go.mod b/syft/pkg/cataloger/golang/test-fixtures/archs/src/go.mod new file mode 100644 index 000000000..d49c920e3 --- /dev/null +++ b/syft/pkg/cataloger/golang/test-fixtures/archs/src/go.mod @@ -0,0 +1,3 @@ +module arch/v1 + +go 1.17 diff --git a/syft/pkg/cataloger/golang/test-fixtures/archs/src/main.go b/syft/pkg/cataloger/golang/test-fixtures/archs/src/main.go new file mode 100644 index 000000000..d592354fb --- /dev/null +++ b/syft/pkg/cataloger/golang/test-fixtures/archs/src/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + println("свобода!") +} diff --git a/syft/pkg/cataloger/golang/version.go b/syft/pkg/cataloger/golang/version.go deleted file mode 100644 index 6306a2da9..000000000 --- a/syft/pkg/cataloger/golang/version.go +++ /dev/null @@ -1,226 +0,0 @@ -// This code was copied from the Go std library. -// https://github.com/golang/go/blob/master/src/cmd/go/internal/version/version.go -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package version implements the ``go version'' command. -package golang - -import ( - "bytes" - "encoding/binary" - // "cmd/go/internal/base" -) - -/* -var CmdVersion = &base.Command{ - UsageLine: "go version [-m] [-v] [file ...]", - Short: "print Go version", - Long: `Version prints the build information for Go executables. - -Go version reports the Go version used to build each of the named -executable files. - -If no files are named on the command line, go version prints its own -version information. - -If a directory is named, go version walks that directory, recursively, -looking for recognized Go binaries and reporting their versions. -By default, go version does not report unrecognized files found -during a directory scan. The -v flag causes it to report unrecognized files. - -The -m flag causes go version to print each executable's embedded -module version information, when available. In the output, the module -information consists of multiple lines following the version line, each -indented by a leading tab character. - -See also: go doc runtime/debug.BuildInfo. -`, -} - -func init() { - CmdVersion.Run = runVersion // break init cycle -} - -var ( - versionM = CmdVersion.Flag.Bool("m", false, "") - versionV = CmdVersion.Flag.Bool("v", false, "") -) - -func runVersion(ctx context.Context, cmd *base.Command, args []string) { - if len(args) == 0 { - // If any of this command's flags were passed explicitly, error - // out, because they only make sense with arguments. - // - // Don't error if the flags came from GOFLAGS, since that can be - // a reasonable use case. For example, imagine GOFLAGS=-v to - // turn "verbose mode" on for all Go commands, which should not - // break "go version". - var argOnlyFlag string - if !base.InGOFLAGS("-m") && *versionM { - argOnlyFlag = "-m" - } else if !base.InGOFLAGS("-v") && *versionV { - argOnlyFlag = "-v" - } - if argOnlyFlag != "" { - fmt.Fprintf(os.Stderr, "go: 'go version' only accepts %s flag with arguments\n", argOnlyFlag) - base.SetExitStatus(2) - return - } - fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) - return - } - - for _, arg := range args { - info, err := os.Stat(arg) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - base.SetExitStatus(1) - continue - } - if info.IsDir() { - scanDir(arg) - } else { - scanFile(arg, info, true) - } - } -} - -// scanDir scans a directory for executables to run scanFile on. -func scanDir(dir string) { - filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { - if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 { - info, err := d.Info() - if err != nil { - if *versionV { - fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) - } - return nil - } - scanFile(path, info, *versionV) - } - return nil - }) -} - -// isExe reports whether the file should be considered executable. -func isExe(file string, info fs.FileInfo) bool { - if runtime.GOOS == "windows" { - return strings.HasSuffix(strings.ToLower(file), ".exe") - } - return info.Mode().IsRegular() && info.Mode()&0111 != 0 -} - -// scanFile scans file to try to report the Go and module versions. -// If mustPrint is true, scanFile will report any error reading file. -// Otherwise (mustPrint is false, because scanFile is being called -// by scanDir) scanFile prints nothing for non-Go executables. -func scanFile(file string, info fs.FileInfo, mustPrint bool) { - if info.Mode()&fs.ModeSymlink != 0 { - // Accept file symlinks only. - i, err := os.Stat(file) - if err != nil || !i.Mode().IsRegular() { - if mustPrint { - fmt.Fprintf(os.Stderr, "%s: symlink\n", file) - } - return - } - info = i - } - - if !isExe(file, info) { - if mustPrint { - fmt.Fprintf(os.Stderr, "%s: not executable file\n", file) - } - return - } - - x, err := openExe(file) - if err != nil { - if mustPrint { - fmt.Fprintf(os.Stderr, "%s: %v\n", file, err) - } - return - } - defer x.Close() - - vers, mod := findVers(x) - if vers == "" { - if mustPrint { - fmt.Fprintf(os.Stderr, "%s: go version not found\n", file) - } - return - } - - fmt.Printf("%s: %s\n", file, vers) - if *versionM && mod != "" { - fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t")) - } -} -*/ - -// The build info blob left by the linker is identified by -// a 16-byte header, consisting of buildInfoMagic (14 bytes), -// the binary's pointer size (1 byte), -// and whether the binary is big endian (1 byte). -var buildInfoMagic = []byte("\xff Go buildinf:") - -// findVers finds and returns the Go version and module version information -// in the executable x. -func findVers(x exe) (vers, mod string) { - // Read the first 64kB of text to find the build info blob. - text := x.DataStart() - data, err := x.ReadData(text, 64*1024) - if err != nil { - return - } - for ; !bytes.HasPrefix(data, buildInfoMagic); data = data[32:] { - if len(data) < 32 { - return - } - } - - // Decode the blob. - ptrSize := int(data[14]) - bigEndian := data[15] != 0 - var bo binary.ByteOrder - if bigEndian { - bo = binary.BigEndian - } else { - bo = binary.LittleEndian - } - var readPtr func([]byte) uint64 - if ptrSize == 4 { - readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) } - } else { - readPtr = bo.Uint64 - } - vers = readString(x, ptrSize, readPtr, readPtr(data[16:])) - if vers == "" { - return - } - mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:])) - if len(mod) >= 33 && mod[len(mod)-17] == '\n' { - // Strip module framing. - mod = mod[16 : len(mod)-16] - } else { - mod = "" - } - return vers, mod -} - -// readString returns the string at address addr in the executable x. -func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string { - hdr, err := x.ReadData(addr, uint64(2*ptrSize)) - if err != nil || len(hdr) < 2*ptrSize { - return "" - } - dataAddr := readPtr(hdr) - dataLen := readPtr(hdr[ptrSize:]) - data, err := x.ReadData(dataAddr, dataLen) - if err != nil || uint64(len(data)) < dataLen { - return "" - } - return string(data) -} diff --git a/syft/pkg/golang_bin_metadata.go b/syft/pkg/golang_bin_metadata.go index e77a4b0f0..b7ba10f8c 100644 --- a/syft/pkg/golang_bin_metadata.go +++ b/syft/pkg/golang_bin_metadata.go @@ -2,7 +2,8 @@ package pkg // GolangBinMetadata represents all captured data for a Golang Binary type GolangBinMetadata struct { - GoCompiledVersion string `json:"goCompiledVersion" cyclonedx:"goCompiledVersion"` - Architecture string `json:"architecture" cyclonedx:"architecture"` - H1Digest string `json:"h1Digest" cyclonedx:"h1Digest"` + BuildSettings map[string]string `json:"goBuildSettings,omitempty" cyclonedx:"goBuildSettings"` + GoCompiledVersion string `json:"goCompiledVersion" cyclonedx:"goCompiledVersion"` + Architecture string `json:"architecture" cyclonedx:"architecture"` + H1Digest string `json:"h1Digest" cyclonedx:"h1Digest"` } diff --git a/syft/source/image_squash_resolver.go b/syft/source/image_squash_resolver.go index ba584897e..8977023a1 100644 --- a/syft/source/image_squash_resolver.go +++ b/syft/source/image_squash_resolver.go @@ -135,7 +135,7 @@ func (r *imageSquashResolver) RelativeFileByPath(_ Location, path string) *Locat return &paths[0] } -// FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer. +// FileContentsByLocation fetches file contents for a single file reference, regardless of the source layer. // If the path does not exist an error is returned. func (r *imageSquashResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { entry, err := r.img.FileCatalog.Get(location.ref) diff --git a/test/integration/regression_go_bin_scanner_arch_test.go b/test/integration/regression_go_bin_scanner_arch_test.go index f5e21727e..5b364d8f9 100644 --- a/test/integration/regression_go_bin_scanner_arch_test.go +++ b/test/integration/regression_go_bin_scanner_arch_test.go @@ -9,9 +9,9 @@ import ( func TestRegressionGoArchDiscovery(t *testing.T) { const ( - expectedELFPkg = 3 - expectedWINPkg = 3 - expectedMACOSPkg = 3 + expectedELFPkg = 4 + expectedWINPkg = 4 + expectedMACOSPkg = 4 ) // This is a regression test to make sure the way we detect go binary packages // stays consistent and reproducible as the tool chain evolves