File gradle-CVE-2023-35946.patch of Package gradle

From 859eae2b2acf751ae7db3c9ffefe275aa5da0d5d Mon Sep 17 00:00:00 2001
From: Louis Jacomet <louis@gradle.com>
Date: Thu, 15 Jun 2023 17:10:18 +0200
Subject: [PATCH] Fix dependency cache path traversal vulnerability

Gradle leverages the protection added to prevent ZipSlip for any path
computation inside a Gradle cache.

See https://github.com/gradle/gradle/security/advisories/GHSA-2h6c-rv6q-494v
---
 .../local/DefaultPathKeyFileStore.java        |  26 ++-
 .../local/DefaultPathKeyFileStoreTest.groovy  |   3 +-
 .../CacheResolveIntegrationTest.groovy        | 151 ++++++++++++++++++
 3 files changed, 174 insertions(+), 6 deletions(-)

--- a/subprojects/core/src/main/java/org/gradle/internal/resource/local/DefaultPathKeyFileStore.java
+++ b/subprojects/core/src/main/java/org/gradle/internal/resource/local/DefaultPathKeyFileStore.java
@@ -26,11 +26,15 @@ import org.gradle.api.internal.file.dele
 import org.gradle.internal.UncheckedException;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.RelativePathUtil;
+import org.gradle.wrapper.PathTraversalChecker;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import static org.gradle.internal.FileUtils.hasExtension;
 
@@ -79,8 +83,17 @@ public class DefaultPathKeyFileStore imp
         return saveIntoFileStore(source, getFile(path), false);
     }
 
-    private File getFile(String path) {
-        return new File(baseDir, path);
+    private File getFile(String... path) {
+        String composedPath;
+        if (path.length == 1) {
+            composedPath = path[0];
+        } else {
+            // We need to ignore empty Strings as this is what "new File(parent, path)" was doing for "path" empty.
+            composedPath = Arrays.stream(path)
+                .filter(((Predicate<String>) String::isEmpty).negate())
+                .collect(Collectors.joining(File.separator));
+	}
+        return new File(baseDir, PathTraversalChecker.safePathName(trimLeadingSlash(composedPath)));
     }
 
     private File getFileWhileCleaningInProgress(String path) {
@@ -234,4 +247,10 @@ public class DefaultPathKeyFileStore imp
             return new File(baseDir, path);
         }
     }
-}
+
+    private static String trimLeadingSlash(String composedPath) {
+        if (!composedPath.isEmpty() && composedPath.charAt(0) == '/') {
+            return composedPath.substring(1);
+        }
+        return composedPath;
+    }}
--- a/subprojects/core/src/test/groovy/org/gradle/internal/resource/local/DefaultPathKeyFileStoreTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/resource/local/DefaultPathKeyFileStoreTest.groovy
@@ -78,7 +78,8 @@ class DefaultPathKeyFileStoreTest extend
         def b = createFile("def")
 
         when:
-        store.move("a", a)
+        // leading slash does not mean absolute path
+        store.move("/a", a)
         store.move("b", b)
 
         then:
--- /dev/null
+++ b/subprojects/core/src/main/java/org/gradle/internal/resource/local/PathTraversalChecker.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.wrapper;
+
+import org.gradle.api.NonNullApi;
+
+import java.io.File;
+import java.util.Locale;
+
+import static java.lang.String.format;
+
+@NonNullApi
+public class PathTraversalChecker {
+
+    /**
+     * Checks the entry name for path traversal vulnerable sequences.
+     *
+     * This code is used for path traversal, ZipSlip and TarSlip detection.
+     *
+     * <b>IMPLEMENTATION NOTE</b>
+     * We do it this way instead of the way recommended in <a href="https://snyk.io/research/zip-slip-vulnerability"></a>
+     * for performance reasons, calling {@link File#getCanonicalPath()} is too expensive.
+     *
+     * @throws IllegalArgumentException if the entry contains vulnerable sequences
+     */
+    public static String safePathName(String name) {
+        if (isUnsafePathName(name)) {
+            throw new IllegalArgumentException(format("'%s' is not a safe archive entry or path name.", name));
+        }
+        return name;
+    }
+
+    public static boolean isUnsafePathName(String name) {
+        return name.isEmpty()
+            || name.startsWith("/")
+            || name.startsWith("\\")
+            || containsDirectoryNavigation(name)
+            || (name.contains(":") && isWindows());
+    }
+
+    private static boolean containsDirectoryNavigation(String name) {
+        if (!name.contains("..")) {
+            return false;
+        }
+        // We have a .. but if not before a file separator or at the end, it is OK
+        return name.endsWith("\\..")
+            || name.contains("..\\")
+            || name.endsWith("/..")
+            || name.contains("../");
+    }
+
+    private static boolean isWindows() {
+        return System.getProperty("os.name").toLowerCase(Locale.US).contains("windows");
+    }
+}
openSUSE Build Service is sponsored by