Gradle dependency constrains are not applied

Viewed 1993

I have following in my Gradle config:

dependencies {    
    implementation "org.slf4j:slf4j-api:1.7.32"
    implementation "org.apache.logging.log4j:log4j-slf4j-impl:2.15.0"
    implementation "org.slf4j:jul-to-slf4j:1.7.32"
    implementation "org.slf4j:jcl-over-slf4j:1.7.32"

    constraints {
        add("implementation", "org.apache.logging.log4j:log4j-core") {
            version {
                strictly("[2.15")
                prefer("2.15.0")
            }
            because("CVE-2021-44228 Log4j 2 Vulnerability")
        }
        add("implementation", "org.apache.logging.log4j:log4j-api") {
            version {
                strictly("[2.15")
                prefer("2.15.0")
            }
            because("CVE-2021-44228 Log4j 2 Vulnerability")
        }
    }
}

Though this config doesn't depend on log4j directly, it has some transient dependencies on log4j. And I expect that it would enforce use of version 2.15.0 or later.

But unfortunately it doesn't change anything:

$ gradle dependencies | grep log4j
+--- org.apache.logging.log4j:log4j-slf4j-impl:2.15.0
|    \--- org.apache.logging.log4j:log4j-api:2.15.0 -> 2.13.3
.....
+--- org.apache.logging.log4j:log4j-api:{strictly [2.15; prefer 2.15.0} -> 2.13.3 (c)
\--- org.apache.logging.log4j:log4j-core:{strictly [2.15; prefer 2.15.0} -> 2.13.3 (c)

And

$ gradle dependencyInsight --dependency org.apache.logging.log4j

> Task :dependencyInsight 
org.apache.logging.log4j:log4j-api:2.13.3
variant "compile" [
  org.gradle.status                  = release (not requested)
  org.gradle.usage                   = java-api
  org.gradle.libraryelements         = jar (compatible with: classes+resources)
  org.gradle.category                = library

  Requested attributes not found in the selected variant:
     org.gradle.dependency.bundling     = external
     org.gradle.jvm.environment         = standard-jvm
     org.jetbrains.kotlin.platform.type = jvm
     org.gradle.jvm.version             = 13
]
Selection reasons:
  - Selected by rule
  - By constraint : CVE-2021-44228 Log4j 2 Vulnerability

org.apache.logging.log4j:log4j-api:{strictly [2.15; prefer 2.15.0} -> 2.13.3
\--- compileClasspath

org.apache.logging.log4j:log4j-api:2.15.0 -> 2.13.3
\--- org.apache.logging.log4j:log4j-slf4j-impl:2.15.0
     \--- compileClasspath 

Why it downgrades to version 2.13.3? Even though it was set as 2.15 for log4j-slf4j-impl and also required by constraints.

Same result with Gradle 6.9 and 7.2

--

Upd:

For simplicity I changed the constraints to:

    add("implementation", "org.apache.logging.log4j:log4j-core:2.15.0") {
        because("CVE-2021-44228 Log4j 2 Vulnerability")
    }
    add("implementation", "org.apache.logging.log4j:log4j-api:2.15.0") {
        because("CVE-2021-44228 Log4j 2 Vulnerability")
    }

Still no effect

3 Answers

The problem was caused by io.spring.dependency-management Gradle plugin, which was also used in that project. Removing that plugin fixed the issue.

So the fix is to remove io.spring.dependency-management plugin.

Also, the correct constraint must be following:

constraints {
    add("implementation", "org.apache.logging.log4j:log4j-core") {
        version {
            strictly("[2.15,3[")
            prefer("2.15.0")
        }
        because("CVE-2021-44228 Log4j 2 Vulnerability")
    }
}

I.e., just log4j-core is enough, and version range must be exactly [2.15,3[

This library depends on org.slf4j:slf4j-api:1.7.25.

dependencies {    
    testIplementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.15.0'
}

There probably is no need for any constraints; try mavenCentral()? And as one can see (link above), it comes with compile, runtime and test dependencies. And when Maven Central suggests this should be testImplementation, this may be the correct configuration to use.

The Gradle Team published a workaround specifically for the critical Log4J bug in versions prior to 2.15.0. However, a new vulnerability has been reported that prompted the release of 2.16.0 so you should update your rules accordingly.

However, Gradle's recommended fix does not work if you're using Spring's dependency management plugin id("io.spring.dependency-management") version "x.y.z".

To deal with Gradle dependencies sans Spring dependency management, simply create a plugin that sets version requirements for Log4J:

class Log4jConventionPlugin : Plugin<Project> {
    private val log = KotlinLogging.logger { }
    
    private val dependencies = listOf(
        "org.apache.logging.log4j:log4j-core",
        "org.apache.logging.log4j:log4j-slf4j-impl",
        "org.apache.logging.log4j:log4j-jul"
    )

    override fun apply(project: Project) {
        applyConstraints(project)
        applySpringDependencyManagementFix(project)
    }
    
    // apply standard Gradle constraints
    private fun applyConstraints(project: Project) {
        
        project.dependencies.constraints { constraints ->
            listOf("compileClasspath", "implementation", "runtimeOnly", "runtimeClasspath").forEach { configuration ->
                dependencies.forEach { dependency ->
                    constraints.add(configuration, dependency) { c ->
                        c.version { v ->
                            v.strictly("[2.16, 3[")
                            v.require("2.16.0")
                            v.reject("[2.0, 2.16[")
                        }
                        c.because("CVE-2021-44228: Log4j vulnerable to remote code execution")
                        log.info { "Applied log4j version constraint to $dependency for configuration $configuration" }
                    }
                }
            }
        }
        log.info { "Applied log4j version constraints" }
    }

    // constraints Spring Boot dependency management constraints
    private fun applySpringDependencyManagementFix(project: Project) {
        project.plugins.findPlugin("io.spring.dependency-management") ?: return
        project.configurations.all {
            it.resolutionStrategy.eachDependency { details ->
                if (details.requested.group == "org.apache.logging.log4j") {
                    details.useVersion("2.16.+")
                }
            }
        }
        log.info { "Applied fixed Log4J resolution to Spring dependency management" }
    }

}
Related