This is an automated email from the ASF dual-hosted git repository.
mimaison pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git
The following commit(s) were added to refs/heads/trunk by this push:
new d995cd14cca KAFKA-20700: Resolve symlinks in AllowedPaths before
validation (#22631)
d995cd14cca is described below
commit d995cd14ccad3b4f1d4e8dfb80634d97d594a947
Author: nileshkumar3 <[email protected]>
AuthorDate: Wed Jun 24 13:18:16 2026 -0500
KAFKA-20700: Resolve symlinks in AllowedPaths before validation (#22631)
Resolve symlinks with toRealPath() in AllowedPaths before
validating paths against allowed.paths, so symlinks inside an allowed
directory cannot point outside the boundary.
Reviewers: Mickael Maison <[email protected]>
---
.../common/config/internals/AllowedPaths.java | 19 +++++++++++-----
.../common/config/provider/AllowedPathsTest.java | 26 ++++++++++++++++++----
2 files changed, 36 insertions(+), 9 deletions(-)
diff --git
a/clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java
b/clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java
index b0f1f1a29c7..300772ee683 100644
---
a/clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java
+++
b/clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java
@@ -18,6 +18,7 @@ package org.apache.kafka.common.config.internals;
import org.apache.kafka.common.config.ConfigException;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -49,7 +50,11 @@ public class AllowedPaths {
} else if (!Files.exists(normalisedPath)) {
throw new ConfigException("Path " + normalisedPath + "
does not exist");
} else {
- allowedPaths.add(normalisedPath);
+ try {
+ allowedPaths.add(normalisedPath.toRealPath());
+ } catch (IOException e) {
+ throw new ConfigException("Path " + normalisedPath + "
could not be resolved", e);
+ }
}
});
@@ -69,12 +74,16 @@ public class AllowedPaths {
Path parsedPath = Paths.get(path);
if (allowedPaths != null) {
- Path normalisedPath = parsedPath.normalize();
- long allowed =
allowedPaths.stream().filter(normalisedPath::startsWith).count();
- if (allowed == 0) {
+ try {
+ Path realPath = parsedPath.toRealPath();
+ long allowed =
allowedPaths.stream().filter(realPath::startsWith).count();
+ if (allowed == 0) {
+ return null;
+ }
+ return realPath;
+ } catch (IOException e) {
return null;
}
- return normalisedPath;
}
return parsedPath;
diff --git
a/clients/src/test/java/org/apache/kafka/common/config/provider/AllowedPathsTest.java
b/clients/src/test/java/org/apache/kafka/common/config/provider/AllowedPathsTest.java
index ebcaf63f880..29bc425a059 100644
---
a/clients/src/test/java/org/apache/kafka/common/config/provider/AllowedPathsTest.java
+++
b/clients/src/test/java/org/apache/kafka/common/config/provider/AllowedPathsTest.java
@@ -50,11 +50,11 @@ class AllowedPathsTest {
}
@Test
- public void testAllowedPath() {
+ public void testAllowedPath() throws IOException {
allowedPaths = new AllowedPaths(String.join(",", dir, dir2));
Path actual = allowedPaths.parseUntrustedPath(myFile);
- assertEquals(myFile, actual.toString());
+ assertEquals(Paths.get(myFile).toRealPath(), actual);
}
@Test
@@ -82,12 +82,30 @@ class AllowedPathsTest {
}
@Test
- public void testAllowedTraversal() {
+ public void testAllowedTraversal() throws IOException {
allowedPaths = new AllowedPaths(String.join(",", dir, dir2));
Path traversedPath = Paths.get(dir, "..", "dir2");
Path actual =
allowedPaths.parseUntrustedPath(traversedPath.toString());
- assertEquals(traversedPath.normalize(), actual);
+ assertEquals(Paths.get(dir2).toRealPath(), actual);
+ }
+
+ @Test
+ public void testSymlinkOutsideAllowedDirRejected() throws IOException {
+ Path outsideFile = Files.createFile(Paths.get(dir2, "outsideFile"));
+ Path symlink = Files.createSymbolicLink(Paths.get(dir, "link"),
outsideFile);
+
+ allowedPaths = new AllowedPaths(dir);
+ assertNull(allowedPaths.parseUntrustedPath(symlink.toString()));
+ }
+
+ @Test
+ public void testSymlinkInsideAllowedDirAllowed() throws IOException {
+ Path symlink = Files.createSymbolicLink(Paths.get(dir, "link"),
Paths.get(myFile));
+
+ allowedPaths = new AllowedPaths(dir);
+ Path actual = allowedPaths.parseUntrustedPath(symlink.toString());
+ assertEquals(Paths.get(myFile).toRealPath(), actual);
}
@Test