From 002fef75ff61a44aa3f663a79d67727faacce16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenzo=20Dematt=C3=A9?= Date: Wed, 23 Apr 2025 20:23:45 +0200 Subject: [PATCH] [Entitlements] Fix: consider case sensitiveness differences (#126990) Our path comparison for file access is string based, due to the fact that we need to support Paths created for different file systems/platforms. However, Windows files and paths are (sort of) case insensitive. This PR fixes the problem by abstracting String comparison operations and making them case sensitive or not based on the host OS. --- docs/changelog/126990.yaml | 7 ++ .../policy/CaseInsensitiveComparison.java | 31 +++++++ .../policy/CaseSensitiveComparison.java | 27 +++++++ .../runtime/policy/FileAccessTree.java | 76 ++++++++++------- .../policy/FileAccessTreeComparison.java | 81 +++++++++++++++++++ .../entitlement/runtime/policy/FileUtils.java | 40 --------- .../runtime/policy/PolicyManager.java | 8 +- .../policy/FileAccessTreeComparisonTests.java | 68 ++++++++++++++++ .../runtime/policy/FileAccessTreeTests.java | 60 +++++++++++--- .../runtime/policy/FileUtilsTests.java | 51 ------------ 10 files changed, 317 insertions(+), 132 deletions(-) create mode 100644 docs/changelog/126990.yaml create mode 100644 libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseInsensitiveComparison.java create mode 100644 libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseSensitiveComparison.java create mode 100644 libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeComparison.java create mode 100644 libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeComparisonTests.java diff --git a/docs/changelog/126990.yaml b/docs/changelog/126990.yaml new file mode 100644 index 000000000000..a8b875cc6a22 --- /dev/null +++ b/docs/changelog/126990.yaml @@ -0,0 +1,7 @@ +pr: 126990 +summary: "Fix: consider case sensitiveness differences in Windows/Unix-like filesystems\ + \ for files entitlements" +area: Infra/Core +type: bug +issues: + - 127047 diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseInsensitiveComparison.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseInsensitiveComparison.java new file mode 100644 index 000000000000..4f122b9c36ed --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseInsensitiveComparison.java @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +class CaseInsensitiveComparison extends FileAccessTreeComparison { + + CaseInsensitiveComparison(char separatorChar) { + super(CaseInsensitiveComparison::caseInsensitiveCharacterComparator, separatorChar); + } + + private static int caseInsensitiveCharacterComparator(char c1, char c2) { + return Character.compare(Character.toLowerCase(c1), Character.toLowerCase(c2)); + } + + @Override + protected boolean pathStartsWith(String pathString, String pathPrefix) { + return pathString.regionMatches(true, 0, pathPrefix, 0, pathPrefix.length()); + } + + @Override + protected boolean pathsAreEqual(String path1, String path2) { + return path1.equalsIgnoreCase(path2); + } +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseSensitiveComparison.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseSensitiveComparison.java new file mode 100644 index 000000000000..700dc4b7cc79 --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/CaseSensitiveComparison.java @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +class CaseSensitiveComparison extends FileAccessTreeComparison { + + CaseSensitiveComparison(char separatorChar) { + super(Character::compare, separatorChar); + } + + @Override + protected boolean pathStartsWith(String pathString, String pathPrefix) { + return pathString.startsWith(pathPrefix); + } + + @Override + protected boolean pathsAreEqual(String path1, String path2) { + return path1.equals(path2); + } +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java index 4b3b7cca75db..5f9af60c802b 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java @@ -11,11 +11,13 @@ package org.elasticsearch.entitlement.runtime.policy; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Strings; +import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -33,7 +35,6 @@ import java.util.function.BiConsumer; import static java.util.Comparator.comparing; import static org.elasticsearch.core.PathUtils.getDefaultFileSystem; -import static org.elasticsearch.entitlement.runtime.policy.FileUtils.PATH_ORDER; import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; @@ -70,9 +71,9 @@ import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEnt * Secondly, the path separator (whether slash or backslash) sorts after the dot character. This means, for example, if the array contains * {@code ["/a", "/a.xml"]} and we look up {@code "/a/b"} using normal string comparison, the binary search would land on {@code "/a.xml"}, * which is neither an exact match nor a containing directory, and so the lookup would incorrectly report no match, even though - * {@code "/a"} is in the array. To fix this, we define {@link FileUtils#PATH_ORDER} which sorts path separators before any other - * character. In the example, this would cause {@code "/a/b"} to sort between {@code "/a"} and {@code "/a.xml"} so that it correctly - * finds {@code "/a"}. + * {@code "/a"} is in the array. To fix this, we define {@link FileAccessTreeComparison#pathComparator()} which sorts path separators + * before any other character. In the example, this would cause {@code "/a/b"} to sort between {@code "/a"} and {@code "/a.xml"} so that + * it correctly finds {@code "/a"}. *

* With the paths pruned, sorted, and segregated by permission, each binary search has the following properties: *