This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch fix/nimbus-submitted-jar-location-check
in repository https://gitbox.apache.org/repos/asf/storm.git

commit 06f6d631d0c42e05dafcdc15e743feaf13aeebbf
Author: Richard Zowalla <[email protected]>
AuthorDate: Wed Aug 19 13:27:11 2026 +0200

    Reject submitted jar locations that are not inside the Nimbus inbox
---
 .../org/apache/storm/daemon/nimbus/Nimbus.java     | 24 +++++++++++++
 .../org/apache/storm/daemon/nimbus/NimbusTest.java | 39 ++++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git 
a/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java 
b/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java
index 0463c78a3..f59b27943 100644
--- a/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java
+++ b/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java
@@ -40,6 +40,7 @@ import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.nio.channels.WritableByteChannel;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1835,6 +1836,28 @@ public class Nimbus implements Iface, Shutdownable, 
DaemonCommon {
         return ServerConfigUtils.masterInbox(conf);
     }
 
+    /**
+     * Check that a client supplied jar location names a file inside the 
nimbus inbox, i.e. one that was handed out by
+     * beginFileUpload and written through uploadChunk/finishFileUpload. Both 
paths are canonicalized first so that
+     * ".." segments and symlinks cannot point outside of the inbox.
+     *
+     * @param inboxLocation the nimbus inbox directory
+     * @param uploadedJarLocation the client supplied jar location
+     * @throws AuthorizationException if uploadedJarLocation is not inside the 
inbox
+     * @throws IOException if the paths could not be resolved
+     */
+    @VisibleForTesting
+    static void validateUploadedJarLocation(String inboxLocation, String 
uploadedJarLocation)
+        throws AuthorizationException, IOException {
+        Path inboxDir = new File(inboxLocation).getCanonicalFile().toPath();
+        Path uploadedJar = new 
File(uploadedJarLocation).getCanonicalFile().toPath();
+        if (uploadedJar.equals(inboxDir) || !uploadedJar.startsWith(inboxDir)) 
{
+            throw new WrappedAuthorizationException("uploadedJarLocation " + 
uploadedJarLocation
+                + " is not inside the nimbus inbox. Topology jars must be 
uploaded through beginFileUpload/uploadChunk"
+                + "/finishFileUpload before the topology is submitted.");
+        }
+    }
+
     /**
      * Used for local cluster.
      *
@@ -1927,6 +1950,7 @@ public class Nimbus implements Iface, Shutdownable, 
DaemonCommon {
         String jarKey = ConfigUtils.masterStormJarKey(topoId);
         if (tmpJarLocation != null) {
             //in local mode there is no jar
+            validateUploadedJarLocation(getInbox(), tmpJarLocation);
             try (FileInputStream fin = new FileInputStream(tmpJarLocation)) {
                 store.createBlob(jarKey, fin, new 
SettableBlobMeta(BlobStoreAclHandler.DEFAULT), subject);
             }
diff --git 
a/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java 
b/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java
index 792ec3aa1..2380d49a8 100644
--- a/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java
+++ b/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java
@@ -18,17 +18,22 @@
 
 package org.apache.storm.daemon.nimbus;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.storm.Config;
 import org.apache.storm.DaemonConfig;
 import org.apache.storm.blobstore.BlobStore;
 import org.apache.storm.blobstore.KeySequenceNumber;
 import org.apache.storm.blobstore.LocalFsBlobStore;
 import org.apache.storm.cluster.IStormClusterState;
+import org.apache.storm.generated.AuthorizationException;
 import org.apache.storm.generated.InvalidTopologyException;
 import org.apache.storm.generated.KeyNotFoundException;
 import org.apache.storm.generated.StormTopology;
@@ -200,4 +205,38 @@ class NimbusTest {
             verify(stormClusterState, never()).setupBlob(eq(BLOB_FILE_KEY), 
eq(nimbusInfo), any());
         }
     }
+
+    @Test
+    void testValidateUploadedJarLocationRejectsLocationsOutsideTheInbox() 
throws Exception {
+        Path inbox = Files.createTempDirectory("nimbus-inbox");
+        Path sibling = Paths.get(inbox + "evil");
+        try {
+            Path jar = Files.write(inbox.resolve("stormjar-cafebabe.jar"), new 
byte[]{ 1 });
+            Path outside = 
Files.write(Files.createDirectory(sibling).resolve("stormjar-cafebabe.jar"), 
new byte[]{ 1 });
+
+            // a location handed out by beginFileUpload is accepted, and so is 
one that only walks inside the inbox
+            Nimbus.validateUploadedJarLocation(inbox.toString(), 
jar.toString());
+            Files.createDirectory(inbox.resolve("nested"));
+            Nimbus.validateUploadedJarLocation(inbox.toString(), inbox + 
"/nested/../stormjar-cafebabe.jar");
+
+            // an absolute path elsewhere, a ".." walk out of the inbox, the 
inbox itself and a sibling directory
+            // whose name merely starts with the inbox path are all rejected
+            assertThrows(AuthorizationException.class,
+                () -> Nimbus.validateUploadedJarLocation(inbox.toString(), 
"/etc/passwd"));
+            assertThrows(AuthorizationException.class,
+                () -> Nimbus.validateUploadedJarLocation(inbox.toString(), 
inbox + "/../../etc/passwd"));
+            assertThrows(AuthorizationException.class,
+                () -> Nimbus.validateUploadedJarLocation(inbox.toString(), 
inbox.toString()));
+            assertThrows(AuthorizationException.class,
+                () -> Nimbus.validateUploadedJarLocation(inbox.toString(), 
outside.toString()));
+
+            // a symlink inside the inbox pointing back out of it is rejected 
too
+            Path link = 
Files.createSymbolicLink(inbox.resolve("stormjar-link.jar"), outside);
+            assertThrows(AuthorizationException.class,
+                () -> Nimbus.validateUploadedJarLocation(inbox.toString(), 
link.toString()));
+        } finally {
+            FileUtils.deleteQuietly(inbox.toFile());
+            FileUtils.deleteQuietly(sibling.toFile());
+        }
+    }
 }

Reply via email to