anchore / syft

CLI tool and library for generating a Software Bill of Materials from container images and filesystems
Apache License 2.0
6.25k stars 574 forks source link

Incorrect crypto version when scanning Go binary #3295

Open learnitall opened 1 month ago

learnitall commented 1 month ago

What happened:

I build a Go binary using the boringcrypto Go Experiment, however a Syft scan showed the experiment as not being used. This happens regardless of if crypto/tls/fipsonly is imported.

What you expected to happen:

The Go standard library version would contain some sort of reference to the boringcrypto experiment being used.

Steps to reproduce the issue:

  1. Create an example binary. I chose to use this one, which I found via https://github.com/golang/go/issues/68588.

  2. Compile the binary using GOEXPERIMENT=1 CGO_ENABLED=1:

$ GOEXPERIMENT=1 CGO_ENABLED=1 go build .
  1. Use go tool nm to verify that the boringcrypto experiment was applied:
$ go tool nm ./boringtest | grep 'BoringCrypto'
  627440 T crypto/internal/boring/sig.BoringCrypto.abi0
$ go tool nm ./boringtest | grep 'StandardCrypto'
$ echo $?
1
  1. Use Syft to scan the binary:
$ syft  ./boringtest
 ✔ Indexed file system                                                 <truncated>/boringtest
 ✔ Cataloged contents                   9433b1793756a85175baf7035c17f2059e6e427d947683adfd7ec014181d0bd8
   ├── ✔ Packages                        [2 packages]  
   └── ✔ Executables                     [1 executables]  
NAME                              VERSION   TYPE        
github.com/learnitall/boringtest  (devel)   go-module    
stdlib                            go1.23.1  go-module 

Anything else we need to know?:

The kind of output I was expecting to see was something like:

stdlib                  go1.23.1 X:boringcrypto  go-module

Environment:

The version of Syft that I used was one that I built today off of commit https://github.com/anchore/syft/commit/01de99b25304ec95197c00b21d698f127b31a887 (v1.13.0)

ANSI_COLOR="1;34"
BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
BUILD_ID="24.05.20240916.086b448"
DOCUMENTATION_URL="https://nixos.org/learn.html"
HOME_URL="https://nixos.org/"
ID=nixos
IMAGE_ID=""
IMAGE_VERSION=""
LOGO="nix-snowflake"
NAME=NixOS
PRETTY_NAME="NixOS 24.05 (Uakari)"
SUPPORT_END="2024-12-31"
SUPPORT_URL="https://nixos.org/community.html"
VERSION="24.05 (Uakari)"
VERSION_CODENAME=uakari
VERSION_ID="24.05"
wagoodman commented 1 month ago

Syft is picking up that boring crypto was used, however, it's not being packed into the version string:

syft ./mybin -o json | jq
Full JSON ```json { "artifacts": [ { "id": "c9dc90c23223383a", "name": "localhost", "version": "(devel)", "type": "go-module", "foundBy": "go-module-binary-cataloger", "locations": [ { "path": "/example", "accessPath": "/example", "annotations": { "evidence": "primary" } } ], "licenses": [], "language": "go", "cpes": [], "purl": "pkg:golang/localhost@(devel)", "metadataType": "go-module-buildinfo-entry", "metadata": { "goBuildSettings": [ { "key": "-buildmode", "value": "exe" }, { "key": "-compiler", "value": "gc" }, { "key": "CGO_ENABLED", "value": "1" }, { "key": "CGO_CFLAGS", "value": "" }, { "key": "CGO_CPPFLAGS", "value": "" }, { "key": "CGO_CXXFLAGS", "value": "" }, { "key": "CGO_LDFLAGS", "value": "" }, { "key": "GOARCH", "value": "amd64" }, { "key": "GOEXPERIMENT", "value": "boringcrypto" }, { "key": "GOOS", "value": "linux" }, { "key": "GOAMD64", "value": "v1" } ], "goCompiledVersion": "go1.23.2", "architecture": "amd64", "mainModule": "localhost", "goCryptoSettings": [ "boring-crypto" ], "goExperiments": [ "boringcrypto" ] } }, { "id": "c76107e9620c2017", "name": "stdlib", "version": "go1.23.2", "type": "go-module", "foundBy": "go-module-binary-cataloger", "locations": [ { "path": "/example", "accessPath": "/example", "annotations": { "evidence": "primary" } } ], "licenses": [ { "value": "BSD-3-Clause", "spdxExpression": "BSD-3-Clause", "type": "declared", "urls": [], "locations": [] } ], "language": "go", "cpes": [ { "cpe": "cpe:2.3:a:golang:go:1.23.2:-:*:*:*:*:*:*", "source": "syft-generated" } ], "purl": "pkg:golang/stdlib@1.23.2", "metadataType": "go-module-buildinfo-entry", "metadata": { "goCompiledVersion": "go1.23.2", "architecture": "" } } ], "artifactRelationships": [ { "parent": "355f6046debb552057c7ea6381269101323635a72e82428faf4575316d99a70b", "child": "c76107e9620c2017", "type": "contains" }, { "parent": "355f6046debb552057c7ea6381269101323635a72e82428faf4575316d99a70b", "child": "c9dc90c23223383a", "type": "contains" }, { "parent": "c76107e9620c2017", "child": "42305feef04b1145", "type": "evident-by" }, { "parent": "c76107e9620c2017", "child": "c9dc90c23223383a", "type": "dependency-of" }, { "parent": "c9dc90c23223383a", "child": "42305feef04b1145", "type": "evident-by" } ], "files": [ { "id": "42305feef04b1145", "location": { "path": "/example" }, "executable": { "format": "elf", "hasExports": true, "hasEntrypoint": true, "importedLibraries": [ "libc.so.6" ], "elfSecurityFeatures": { "symbolTableStripped": false, "stackCanary": false, "nx": true, "relRO": "none", "pie": false, "dso": false, "safeStack": false, "cfi": false, "fortify": false } } } ], "source": { "id": "355f6046debb552057c7ea6381269101323635a72e82428faf4575316d99a70b", "name": "example", "version": "sha256:2b92957ed9f9cfea8d04bdef2ed2069549616ca13db98639c005972c00810f87", "type": "file", "metadata": { "path": "/tmp/example", "digests": [ { "algorithm": "sha256", "value": "2b92957ed9f9cfea8d04bdef2ed2069549616ca13db98639c005972c00810f87" } ], "mimeType": "application/x-executable" } }, "distro": {}, "descriptor": { "name": "syft", "version": "1.13.0", "configuration": { "catalogers": { "requested": { "default": [ "directory" ] }, "used": [ "alpm-db-cataloger", "apk-db-cataloger", "binary-classifier-cataloger", "cocoapods-cataloger", "conan-cataloger", "dart-pubspec-lock-cataloger", "dotnet-deps-cataloger", "dotnet-portable-executable-cataloger", "dpkg-db-cataloger", "elf-binary-package-cataloger", "elixir-mix-lock-cataloger", "erlang-otp-application-cataloger", "erlang-rebar-lock-cataloger", "github-action-workflow-usage-cataloger", "github-actions-usage-cataloger", "go-module-binary-cataloger", "go-module-file-cataloger", "graalvm-native-image-cataloger", "haskell-cataloger", "java-archive-cataloger", "java-gradle-lockfile-cataloger", "java-jvm-cataloger", "java-pom-cataloger", "javascript-lock-cataloger", "linux-kernel-cataloger", "lua-rock-cataloger", "nix-store-cataloger", "opam-cataloger", "php-composer-lock-cataloger", "php-pecl-serialized-cataloger", "portage-cataloger", "python-installed-package-cataloger", "python-package-cataloger", "rpm-archive-cataloger", "rpm-db-cataloger", "ruby-gemfile-cataloger", "ruby-gemspec-cataloger", "rust-cargo-lock-cataloger", "swift-package-manager-cataloger", "swipl-pack-cataloger", "wordpress-plugins-cataloger" ] }, "data-generation": { "generate-cpes": true }, "files": { "content": { "globs": null, "skip-files-above-size": 0 }, "hashers": [ "sha-1", "sha-256" ], "selection": "owned-by-package" }, "packages": { "binary": [ "python-binary", "python-binary-lib", "pypy-binary-lib", "go-binary", "julia-binary", "helm", "redis-binary", "java-binary-openjdk", "java-binary-ibm", "java-binary-oracle", "java-binary-graalvm", "java-binary-jdk", "nodejs-binary", "go-binary-hint", "busybox-binary", "util-linux-binary", "haproxy-binary", "perl-binary", "php-cli-binary", "php-fpm-binary", "php-apache-binary", "php-composer-binary", "httpd-binary", "memcached-binary", "traefik-binary", "arangodb-binary", "postgresql-binary", "mysql-binary", "mysql-binary", "mysql-binary", "xtrabackup-binary", "mariadb-binary", "rust-standard-library-linux", "rust-standard-library-macos", "ruby-binary", "erlang-binary", "erlang-alpine-binary", "erlang-library", "swipl-binary", "dart-binary", "haskell-ghc-binary", "haskell-cabal-binary", "haskell-stack-binary", "consul-binary", "nginx-binary", "bash-binary", "openssl-binary", "gcc-binary", "fluent-bit-binary", "wordpress-cli-binary", "curl-binary", "lighttpd-binary", "proftpd-binary", "zstd-binary", "xz-binary", "gzip-binary", "sqlcipher-binary", "jq-binary" ], "golang": { "local-mod-cache-dir": "/go/pkg/mod", "main-module-version": { "from-build-settings": true, "from-contents": true, "from-ld-flags": true }, "proxies": [ "https://proxy.golang.org", "direct" ], "search-local-mod-cache-licenses": false, "search-remote-licenses": false }, "java-archive": { "include-indexed-archives": true, "include-unindexed-archives": false, "maven-base-url": "https://repo1.maven.org/maven2", "maven-localrepository-dir": "/root/.m2/repository", "max-parent-recursive-depth": 0, "use-maven-localrepository": false, "use-network": false }, "javascript": { "npm-base-url": "https://registry.npmjs.org", "search-remote-licenses": false }, "linux-kernel": { "catalog-modules": true }, "python": { "guess-unpinned-requirements": false } }, "relationships": { "exclude-binary-packages-with-file-ownership-overlap": true, "package-file-ownership": true, "package-file-ownership-overlap": true }, "search": { "scope": "squashed" } } }, "schema": { "version": "16.0.17", "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-16.0.17.json" } } ```

Snippet of the JSON output for the package that represents the go module (localhost in this case):

  {
      "id": "c9dc90c23223383a",
      "name": "localhost",
      "version": "(devel)",
      "type": "go-module",
      "foundBy": "go-module-binary-cataloger",
      "locations": [
        {
          "path": "/example",
          "accessPath": "/example",
          "annotations": {
            "evidence": "primary"
          }
        }
      ],
      "licenses": [],
      "language": "go",
      "cpes": [],
      "purl": "pkg:golang/localhost@(devel)",
      "metadataType": "go-module-buildinfo-entry",
      "metadata": {
        "goBuildSettings": [
          {
            "key": "-buildmode",
            "value": "exe"
          },
          {
            "key": "-compiler",
            "value": "gc"
          },
          {
            "key": "CGO_ENABLED",
            "value": "1"
          },
          {
            "key": "CGO_CFLAGS",
            "value": ""
          },
          {
            "key": "CGO_CPPFLAGS",
            "value": ""
          },
          {
            "key": "CGO_CXXFLAGS",
            "value": ""
          },
          {
            "key": "CGO_LDFLAGS",
            "value": ""
          },
          {
            "key": "GOARCH",
            "value": "amd64"
          },
          {
            "key": "GOEXPERIMENT",
            "value": "boringcrypto"
          },
          {
            "key": "GOOS",
            "value": "linux"
          },
          {
            "key": "GOAMD64",
            "value": "v1"
          }
        ],
        "goCompiledVersion": "go1.23.2",
        "architecture": "amd64",
        "mainModule": "localhost",
        "goCryptoSettings": [
          "boring-crypto"
        ],
        "goExperiments": [
          "boringcrypto"
        ]
      }
    },

Take note that there are goExperiments and goCryptoSettings that indicate boring crypto is enabled here.

I realize that you're expectations are probably derived from the go tooling output:

go version -m ./mybin
/tmp/example: go1.23.2 X:boringcrypto
    path    localhost
    mod localhost   (devel)
    build   -buildmode=exe
    build   -compiler=gc
    build   CGO_ENABLED=1
    build   CGO_CFLAGS=
    build   CGO_CPPFLAGS=
    build   CGO_CXXFLAGS=
    build   CGO_LDFLAGS=
    build   GOARCH=amd64
    build   GOEXPERIMENT=boringcrypto
    build   GOOS=linux
    build   GOAMD64=v1

Where the golang version string is go1.23.2 X:boringcrypto. I don't think we should make the version string for the package literally be this value, since this would make downstream use cases more difficult. That being said, though this version string is not semver (note the leading go value) we could treat this auxiliary information as semver does: go1.23.2+X:boringcrypto (note the +). Semantically downstream use cases should strip the auxiliary data after the +.

I'm on the fence for this change, but wanted to put it out there to see what folks think. In the meantime does the knowledge that the JSON output has what you need help you out @learnitall ?

learnitall commented 1 month ago

Ok that makes sense @wagoodman, thank you for the detailed explanation. The knowledge of the JSON output having the crypto details is helpful, as I honestly thought this was a bug, but it doesn't address the use case I'm approaching this from. I apologize for not being specific enough earlier, I can open a new issue if that would be more helpful.

My main goal is to have the crypto options of a Go binary show up within the sdpxjson output. I'd like to generate an SPDX SBOM from a Docker image that can demonstrate that the contained Go binaries use boringcrypto with the FIPS-only option set. Otherwise, the SBOM can't attest to the Go binaries using FIPs compliant crypto.

Adding the GOEXPERIMENT information into the version string would be helpful, but I don't think it would be useful for usage within an SBOM because: (1) it doesn't contain information regarding setting the FIPs-only option and (2) it's possible to build a binary with GOEXPERIMENT=boringcrypto that still uses the standard crypto libraries (ie if CGO_ENABLED=0). I'm stilling learning the SPDX format, but maybe the PackageSourceInfo field would be appropriate? If we figure out a way this could work, I can submit a PR with the implementation.

Again, my apologies for not being specific enough. Thank you for your help and patience!