anchore / syft

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

OpenJDK CPEs #2422

Open joshbressers opened 6 months ago

joshbressers commented 6 months ago

The CPEs that Syft emits for the binary version of OpenJDK versions appear to be incorrect.

For our example of JDK 8 we will use the eclipse-temurin:8u392-b08-jdk image (the openjdk:8 image isn't detected by Syft, which is probably a different bug that needs investigating)

➜  /tmp syft eclipse-temurin:8u392-b08-jdk | grep "^java"
 ✔ Loaded image                                                                                                                                       eclipse-temurin:8u392-b08-jdk
 ✔ Parsed image                                                                                             sha256:29907afc7d5fa0cdc60fef0cea01bf7abb5cef4944107c6d46896f3242b1cc42
 ✔ Cataloged packages              [152 packages]
java                 1.8.0_392-b08                            binary

We can see the Java version detected as 1.8.0_392-b08. If we look in the container this checks out

➜  /tmp docker run --rm -it eclipse-temurin:8u392-b08-jdk java -version
openjdk version "1.8.0_392"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_392-b08)
OpenJDK 64-Bit Server VM (Temurin)(build 25.392-b08, mixed mode)

The CPE generated by Syft is then

   "cpes": [
    "cpe:2.3:a:oracle:openjdk:1.8.0_392-b08:*:*:*:*:*:*:*",
    "cpe:2.3:a:java:java:1.8.0_392-b08:*:*:*:*:*:*:*"
   ],

Which is probably wrong.

If we look at the official CPE dictionary for OpenJDK CPEs we see this (I have trimmed this list for brevity)

➜  /tmp grep "cpe:2.3:a:oracle:openjdk" official-cpe-dictionary_v2.3.xml
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:-:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.6.0:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.6.0:-:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.6.0:update1:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.6.0:update7:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.7.0:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.7.0:-:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.7.0:update1:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.7.0:update9:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:1.8.0:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:7:-:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:7:update1:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:7:update99:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:8:-:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:8:milestone1:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:8:milestone9:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:8:update101:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:8:update92:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:9:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:9.0.1:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:9.0.4:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:10:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:10.0.1:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:10.0.2:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:11:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:11.0.1:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:11.0.14:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:12:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:13:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:14:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:15:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:16:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:17:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:17.0.1:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:18:*:*:*:*:*:*:*"/>
    <cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:20:*:*:*:*:*:*:*"/>

The OpenJDK CPE names versus the versions we return in Syft do not match. This is true of OpenJDK8 as well as newer versions. For example if we look at OpenJDK 21

➜  /tmp syft openjdk:21 | grep "^java"
 ✔ Loaded image                                                                                                                                                          openjdk:21
 ✔ Parsed image                                                                                             sha256:079114de2be199f2ae0f7766ac0187d24a0c3a2d658fc51bffc6af5b8bd85469
 ✔ Cataloged packages              [115 packages]
java                     21+35-2513                     binary

We get CPEs

      "cpes": [
        "cpe:2.3:a:oracle:openjdk:21\\+35-2513:*:*:*:*:*:*:*",
        "cpe:2.3:a:java:java:21\\+35-2513:*:*:*:*:*:*:*"
      ],

Based on the above, I would expect the CPE for OpenJDK 21 to look like

<cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:21:*:*:*:*:*:*:*"/>

The CPE for OpenJDK 8 above I would expect to look like

<cpe-23:cpe23-item name="cpe:2.3:a:oracle:openjdk:8:update392:*:*:*:*:*:*"/>

It looks like versions 8 and before use a different format than newer versions.

willmurphyscode commented 6 months ago

Met with @joshbressers about this issue this morning, and we think that, basically, we need to special-case OpenJDK 8 versions in Syft's CPE generation:

❯ syft -q -o json eclipse-temurin:8u392-b08-jdk | jq '.artifacts[] | select(.name == "java") | .cpes'
[
  "cpe:2.3:a:oracle:openjdk:1.8.0_392-b08:*:*:*:*:*:*:*",
  "cpe:2.3:a:java:java:1.8.0_392-b08:*:*:*:*:*:*:*"
]

This should instead print out something like:

[
  " cpe:2.3:a:oracle:openjdk:8:update392:*:*:*:*:*:*"
... (not sure if there should be others)
]
westonsteimel commented 5 months ago

I think it's all java versions prior to 9 that would be prefixed with a 1 in the jdk

westonsteimel commented 5 months ago

The _ suffix seems to correspond to update, so if we enhanced the syft logic to interpret the _{XXX} as updateX in the cpe component then that would help some here. I'm not sure on the + suffix part yet. I'm not sure there is any good place to represent that in the cpe form, so it may be that we should be stripping that part out when constructing a CPE

westonsteimel commented 5 months ago

I wonder if we should have a more specific cataloger for detecting java jdk and jre rather than a generic binary cataloger. For instance, the image eclipse-temurin:11.0.21_9-jre-alpine has the following with alot of useful infomration that could be surfaced. I haven't checked all of the variants yet but wonder if this file is somewhat standard

cat /opt/java/openjdk/release
IMPLEMENTOR="Eclipse Adoptium"
IMPLEMENTOR_VERSION="Temurin-11.0.21+9"
JAVA_RUNTIME_VERSION="11.0.21+9"
JAVA_VERSION="11.0.21"
JAVA_VERSION_DATE="2023-10-17"
LIBC="musl"
MODULES="java.base java.compiler java.datatransfer java.xml java.prefs java.desktop java.instrument java.logging java.management java.security.sasl java.naming java.rmi java.management.rmi java.net.http java.scripting java.security.jgss java.transaction.xa java.sql java.sql.rowset java.xml.crypto java.se java.smartcardio jdk.accessibility jdk.internal.vm.ci jdk.management jdk.unsupported jdk.internal.vm.compiler jdk.aot jdk.internal.jvmstat jdk.attach jdk.charsets jdk.compiler jdk.crypto.ec jdk.crypto.cryptoki jdk.dynalink jdk.internal.ed jdk.editpad jdk.hotspot.agent jdk.httpserver jdk.internal.le jdk.internal.opt jdk.internal.vm.compiler.management jdk.jartool jdk.javadoc jdk.jcmd jdk.management.agent jdk.jconsole jdk.jdeps jdk.jdwp.agent jdk.jdi jdk.jfr jdk.jlink jdk.jshell jdk.jsobject jdk.jstatd jdk.localedata jdk.management.jfr jdk.naming.dns jdk.naming.ldap jdk.naming.rmi jdk.net jdk.pack jdk.rmic jdk.scripting.nashorn jdk.scripting.nashorn.shell jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported.desktop jdk.xml.dom jdk.zipfs"
OS_ARCH="x86_64"
OS_NAME="Linux"
SOURCE=".:git:ef680be160c5"
BUILD_SOURCE="git:0a454394ec842383e3d7c03aae5972ab24e10d85"
BUILD_SOURCE_REPO="https://github.com/adoptium/temurin-build.git"
SOURCE_REPO="https://github.com/adoptium/jdk11u.git"
FULL_VERSION="11.0.21+9"
SEMANTIC_VERSION="11.0.21+9"
BUILD_INFO="OS: Linux Version: 5.15.0-48-generic"
JVM_VARIANT="Hotspot"
JVM_VERSION="11.0.21+9"
IMAGE_TYPE="JRE"
westonsteimel commented 5 months ago

Seems to be present for versions 11 and above at least for the eclipse-temurin and ibmjava images

westonsteimel commented 5 months ago

Sorry, I went off on a bit of a tangent, we'd still need the same sort of adjustments to the CPE generation, I was just looking at getting additional metadata to allow differentiating between various jdk/jre distributions in future

westonsteimel commented 5 months ago

I think we should just special case the version comparison logic in grype and forget about trying to make the CPEs perfect in syft since we know that will never be possible. Instead just have special logic in grype for when we're doing CPE version comparison for these specific packages

luhring commented 4 months ago

Ignore if this isn't helpful, and I'm not a Java expert, but this resonates a lot based on what I'm seeing (from @westonsteimel):

I wonder if we should have a more specific cataloger for detecting java jdk and jre rather than a generic binary cataloger

We use Syft/Grype in wolfictl, and noticed it wasn't finding OpenJDK or OpenJDK's published advisories here: https://openjdk.org/groups/vulnerability/advisories/

I saw at least two things contributing to this:

  1. There's not one "OpenJDK binary". It's a development kit that has several components, and in some distros, java isn't even among those (whereas javac, javadoc, etc. are).
  2. From my spot checking, the prevailing CPE for OpenJDK in the project's own advisories is: cpe:2.3:a:oracle:jdk:..., not cpe:2.3:a:oracle:openjdk:... (although I did see that one from time to time).

Since this was causing a fair amount of false negatives, I added a last-mile CPE addition in wolfictl based on the distro package name, but I'm not sure if that approach would make sense in Syft's context. Just mentioning this in case it helps us find a good solution upstream here that we can use in wolfictl. 😃

westonsteimel commented 2 months ago

Some very rough initial notes which may be useful for a future java installation cataloger:

Mayber parse all of the .properties files found under $JAVA_HOME?

Also extract the main system properties from the system class (they usually agree with what is in the release file, but that isn't always available, and sometimes the properties map has a bit more info) They seem to be embedded as a map within a java class file. The file I found the map embedded in was classes/java/lang/VersionProps.class from jmods/java.base.jmod - unsure if this will be consitent for prior versions so worth invesitgating further

grep -nr '+37' . Binary file ./bin/jwebserver matches Binary file ./bin/jarsigner matches Binary file ./bin/jfr matches Binary file ./bin/jdb matches Binary file ./bin/jstack matches Binary file ./bin/rmiregistry matches Binary file ./bin/jar matches Binary file ./bin/jcmd matches Binary file ./bin/jrunscript matches Binary file ./bin/jps matches Binary file ./bin/java matches Binary file ./bin/jhsdb matches Binary file ./bin/javap matches Binary file ./bin/jdeprscan matches Binary file ./bin/javac matches Binary file ./bin/keytool matches Binary file ./bin/jmod matches Binary file ./bin/jmap matches Binary file ./bin/jshell matches Binary file ./bin/jstat matches Binary file ./bin/jlink matches Binary file ./bin/serialver matches Binary file ./bin/javadoc matches Binary file ./bin/jinfo matches Binary file ./bin/jstatd matches Binary file ./bin/jdeps matches Binary file ./bin/jconsole matches Binary file ./bin/jpackage matches Binary file ./bin/jimage matches ./release:2:JAVA_RUNTIME_VERSION="22+37" Binary file ./lib/server/classes_nocoops.jsa matches Binary file ./lib/server/libjvm.dylib matches Binary file ./lib/server/classes.jsa matches Binary file ./lib/ct.sym matches Binary file ./lib/src.zip matches Binary file ./lib/modules matches Binary file ./lib/client/classes_nocoops.jsa matches Binary file ./lib/client/libjvm.dylib matches Binary file ./lib/client/classes.jsa matches Binary file ./jmods/jdk.jfr.jmod matches Binary file ./jmods/bin/java matches Binary file ./jmods/bin/keytool matches Binary file ./jmods/classes/java/lang/VersionProps.class matches Binary file ./jmods/java.base.jmod matches Binary file ./jmods/jdk.charsets.jmod matches Binary file ./jmods/jdk.localedata.jmod matches Binary file ./jmods/java.desktop.jmod matches Binary file ./jmods/lib/server/libjvm.dylib matches Binary file ./jmods/lib/client/libjvm.dylib matches Binary file ./jmods/javafx.web.jmod matches Binary file ./jmods/base/bin/java matches Binary file ./jmods/base/bin/keytool matches Binary file ./jmods/base/classes/java/lang/VersionProps.class matches Binary file ./jmods/base/java.base.jmod matches Binary file ./jmods/base/lib/server/libjvm.dylib matches Binary file ./jmods/base/lib/client/libjvm.dylib matches

I also found several properties files which may be worth considering find . -type f -name *.properties ./lib/javafx.properties ./lib/psfontj2d.properties ./lib/javafx.properties ./conf/logging.properties ./conf/sound.properties ./conf/net.properties ./conf/management/management.properties ./conf/jaxp.properties ./jmods/classes/sun/net/www/content-types.properties ./jmods/classes/java/time/chrono/hijrah-config-Hijrah-umalqura_islamic-umalqura.properties ./jmods/conf/net.properties ./jmods/base/classes/sun/net/www/content-types.properties ./jmods/base/classes/java/time/chrono/hijrah-config-Hijrah-umalqura_islamic-umalqura.properties

Specifically the llib/javafx.properties file could be useful for understanding which version of javafx is installed cat lib/javafx.properties ───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── │ File: lib/javafx.properties ───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 1 │ javafx.version=22.0.0+1 2 │ javafx.runtime.version=22.0.0+1 3 │ javafx.runtime.build=b01

javafx in particular is now distributed as a separate project (OpenJFX) and has separate versioning so it would definitely be ideal to capture this as an owned separate dependency.

There is more at https://github.com/anchore/vulnerability-data-tools/blob/main/annotation_format_examples/CVE-2024-20925.toml