Using GraalVM Native Image SBOM Support for Vulnerability Scanning

William Blair
graalvm
Published in
7 min readFeb 16, 2023

Software supply chains remain vulnerable to exploitation as researchers and adversaries frequently discover security vulnerabilities in widely used software libraries. When designing hardware, engineers often assemble a bill of materials (BOM) that itemizes all the individual components in a design. Each entry may contain information relevant for auditing and assembling a design, such as the manufacturer or layout constraints. In contrast, it is often difficult to ascertain the origin of a piece of software, which may be represented as an executable file, compressed archive, or a layered filesystem run by a container orchestration engine. This makes it difficult for developers and operators to periodically review the integrity of their software supply chain and limit the attack surface available to adversaries.

In this blog post, we describe how GraalVM Native Image generates native executables with an embedded software bill of materials (SBOM). Developers and operators can use this SBOM to identify vulnerable dependencies in native executables. We detect vulnerable dependencies by extracting and scanning this SBOM with syft and grype, respectively. These are publicly available software supply chain tools maintained by Anchore. In addition, we show how to obtain SBOMs using the GraalVM Native Image Inspection Tool.

Software Bill of Materials (SBOM)

A software bill of materials (SBOM) summarizes all the library dependencies used by a software artifact. An SBOM describes all the Java libraries used by a native executable produced by GraalVM Native Image. Without an SBOM, it can be difficult to determine whether any of the dependencies found in a native executable contain security vulnerabilities since the metadata that documents library versions is discarded when library classes are taken from JAR files and compiled into the host's instruction set architecture.

Recent versions of GraalVM enable developers to embed an SBOM into their native executables. The SBOM documents all the Java libraries associated with classes that are reachable during the analysis stage of the GraalVM Native Image build process. This includes classes that are compiled directly into the executable, in addition to others that are used during static initialization or are inlined directly by the compiler. The SBOM may be embedded into the native executable when building both standalone executables and shared libraries. Currently, the SBOM is represented in the CycloneDX format, however users can use the popular Anchore syft CLI tool to obtain the SBOM in any format supported by syft. You can embed an SBOM into native executables built for either Linux, macOS, or Windows. Furthermore, the syft tool can retrieve SBOMs from native executables built for any of these platforms.

Each entry within the SBOM documents a Java library, the version contained within the native executable, and a set of common platform enumerations (CPEs). CPEs allow vulnerability scanners to match the Java library to Common Vulnerabilities and Exposures (CVEs) recorded in external databases. In the following example, we will describe how we can use this new feature to check for vulnerable libraries within a small Micronaut Server similar to the Micronaut demo server.

Embedding an SBOM into a Micronaut Server

We start by adding an SBOM to our Micronaut server application by passing parameters to the GraalVM Native Build Tools with Maven. If you are building your application with Gradle, you can use the same command line parameters with the relevant Gradle plugin. To generate a native Micronaut server with an embedded SBOM, we add the following snippet into the build phase of our pom.xml file.

<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<environment>
<USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM>false</USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM>
</environment>
<buildArgs combine.children="append">
<buildArg>--enable-sbom=cyclonedx,strict</buildArg>
</buildArgs>
</configuration>
</plugin>

Note that the --enable-sbom API option can take multiple parameters. In this case, we have specified that we want the embedded SBOM to be in the CycloneDX format (cyclonedx) and that we want the build to terminate if any class that is reachable after the analysis phase completes cannot be associated with a Java library (strict). The latter option is helpful for ensuring that every class that may be included in the native executable, either via a static initializer, an inlined method, or a class that gets compiled into the final executable, is associated with a specific version of a Java library. This optional "strict" mode can ensure that all the library dependencies in a native executable are accounted for in the SBOM. In some cases, obtaining a complete record of all Java libraries is infeasible, such as when compiling obfuscated or closed-source ‘fat’ JAR files into a native executable. In these settings, omitting the strict parameter still allows the compiler to include a possibly partial SBOM into the executable. The format of the SBOM is always required and currently cyclonedx is the only supported format.

The following command will build the native executable using Maven:

./mvnw package -Dpackaging=native-image

This will produce a native executable containing an SBOM that describes all of the Java libraries included within the native executable. If this native executable is deployed into a container image, then a developer or operator can scan for potential vulnerabilities by using Anchore's vulnerability scanning tools.

Scanning a Native Executable for Vulnerable Java Libraries

Anchore provides a suite of tools that enable developers to secure their software supply chains. The syft command line interface (CLI) tool allows developers to generate SBOMs that document the contents of individual filesystems which may be represented as individual directories or as the layered filesystems that make up container images. In addition to the Java dependencies given within native executables, syft documents the version of the Linux distribution used in a container image along with versions of whatever system libraries and utilities are given within the container. The grype scanner cross-references the CPEs contained in these SBOMs with publicly available vulnerability databases in order to identify vulnerable SBOM components. These Anchore tools allow us to quickly check whether a native executable contains vulnerable software libraries.

In this example, we assume that a developer wants to check their native executable for vulnerabilities using the grype scanner. The syft CLI tool allows the developer to export the SBOM in multiple formats. This can be helpful to support additional manual analysis or for vulnerability scanning against different databases. To extract the SBOM, a developer can do the following once they have installed the syft tool. In this example, we use syft version 0.68.1.

$ syft packages /path/to/native-image/

This will list the packages given in the native executable for the developer to inspect. Currently, syft can extract the SBOM from native executables built for Linux, macOS, and Windows. Syft also supports scanning an entire container image with the following command:

$ syft packages micronautguide:latest

All native executables utilize the GraalVM SDK, Substrate VM (SVM), and Java Runtime Environment (JRE) to some extent. Therefore, every GraalVM Native Image SBOM will document both the GraalVM version and JRE version contained within the native executable. This can be helpful for identifying vulnerable runtime components within a native executable. In addition, the SBOM documents each JRE package used by the native executable. Currently, most vulnerability databases mark a specific version of the JRE or GraalVM as vulnerable if a security vulnerability impacts one or more packages within the runtime. However, we also include CPEs for individual JRE components to enable detecting vulnerable runtime packages.

Whether an SBOM is obtained from a container image or from a filesystem directory, the developer will want to check whether any of the libraries contained within the SBOM have known vulnerabilities. Fortunately, the grype tool can scan SBOMs with the following command:

$ syft packages -o cyclonedx-json /path/to-native-image/ | grype

Note that we have to explicitly export the SBOM with the -o flag in order to scan for vulnerabilities.

GraalVM also includes a Native Image Inspection Tool for obtaining an SBOM embedded within a native executable. This can be helpful if users want to examine or scan the SBOM in isolation, as opposed to the SBOM of a directory or container image that contains a native executable. The Native Image Inspection Tool is currently supported on Linux and macOS, and can obtain SBOMs by using the following:

$ $JAVA_HOME/bin/native-image-inspect --sbom /path/to-native-image

As with syft, the SBOM obtained by the Native Image Inspection Tool can be submitted directly to a vulnerability scanner like grype.

$ $JAVA_HOME/bin/native-image-inspect --sbom /path/to-native-image | grype

Once we have completed the vulnerability scan, we may receive a notification like the following, if, for example, the Micronaut server used an old version of the popular Log4j library that is vulnerable to the Log4Shell attack.

NAME       INSTALLED FIXED-IN TYPE                  VULNERABILITY   SEVERITY
log4j-core 2.17.0 graalvm-native-image CVE-2021-44832 Medium

This provides valuable context for potential vulnerabilities hiding in a developer's application and highlights which components need updating. In this case, grype has matched the CPE attached to the old log4j version given in our native executable to the CVE associated with the Log4Shell attack. By updating this vulnerable component, the developer prevents adversaries from crafting malicious log inputs that exploit features in the logging infrastructure. It is worth noting that native executables were not vulnerable to the original Log4Shell attack, but the discovery of additional CVEs following the original disclosure motivates the need to update the log4j dependency. This example highlights the importance of periodically running vulnerability scans in order to keep up to date with the constantly shifting attack surface present in software supply chains.

Generating CPEs to match all of a native executable’s libraries may cause some overlap between libraries. In some cases, a vulnerability may be a false positive if the CPE for one library matches a vulnerability contained in a related, but unused library. For example, the Netty framework consists of many individual libraries. An app may not use Netty's HTTP codec library but relies on an older version of Netty's HTTP server library. A CPE for the server library may match a vulnerability in the unused codec library and cause a false positive. In these cases, a developer can check that the framework libraries used by their native executable are kept up to date with the framework to minimize these overlapping vulnerabilities.

Summary

In this blog post, we demonstrated how to embed an SBOM into a native executable built by GraalVM Native Image. This SBOM allows organizations to periodically scan for vulnerabilities in any library embedded within their deployed native executables using publicly available tools and vulnerability databases. We showed how organizations can perform these vulnerability scans using the syft and grype tools provided by Anchore, in addition to the GraalVM Native Image Inspection Tool. This empowers organizations to maintain the integrity of their software supply chain in the face of constantly evolving attack vectors.

--

--