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

rzo1 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/storm.git


The following commit(s) were added to refs/heads/master by this push:
     new 364dd7913 Reject image tags that are neither mapped nor valid image 
hashes before building the manifest path (#8983)
364dd7913 is described below

commit 364dd79134b1cfc093d6c8ee8913181cdeb0f736
Author: Richard Zowalla <[email protected]>
AuthorDate: Sat Aug 22 18:15:45 2026 +0200

    Reject image tags that are neither mapped nor valid image hashes before 
building the manifest path (#8983)
---
 .../oci/LocalOrHdfsImageTagToManifestPlugin.java   | 23 +++++-
 .../LocalOrHdfsImageTagToManifestPluginTest.java   | 85 ++++++++++++++++++++++
 2 files changed, 106 insertions(+), 2 deletions(-)

diff --git 
a/external/storm-hdfs-oci/src/main/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPlugin.java
 
b/external/storm-hdfs-oci/src/main/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPlugin.java
index 949f0e1a7..c32444365 100644
--- 
a/external/storm-hdfs-oci/src/main/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPlugin.java
+++ 
b/external/storm-hdfs-oci/src/main/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPlugin.java
@@ -23,6 +23,7 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.io.InputStreamReader;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -84,6 +85,16 @@ public class LocalOrHdfsImageTagToManifestPlugin implements 
OciImageTagToManifes
 
     private static final String ALPHA_NUMERIC = "[a-zA-Z0-9]+";
 
+    /**
+     * Check that a string can be used as an image hash, i.e. it consists of 
exactly
+     * {@link #SHA256_HASH_LENGTH} alphanumeric characters.
+     * @param hash the string to check
+     * @return true if the string has the shape of an image hash
+     */
+    private static boolean isValidHash(String hash) {
+        return hash != null && hash.length() == SHA256_HASH_LENGTH && 
hash.matches(ALPHA_NUMERIC);
+    }
+
     @Override
     public void init(Map<String, Object> conf) throws IOException {
         this.conf = conf;
@@ -213,7 +224,7 @@ public class LocalOrHdfsImageTagToManifestPlugin implements 
OciImageTagToManifes
             String[] imageTagArray = imageTags.split(",");
             String hash = line.substring(index + 1);
 
-            if (!hash.matches(ALPHA_NUMERIC) || hash.length() != 
SHA256_HASH_LENGTH) {
+            if (!isValidHash(hash)) {
                 LOG.warn("Malformed image hash: " + hash);
                 continue;
             }
@@ -229,6 +240,10 @@ public class LocalOrHdfsImageTagToManifestPlugin 
implements OciImageTagToManifes
     @Override
     public synchronized ImageManifest getManifestFromImageTag(String imageTag) 
throws IOException {
         String hash = getHashFromImageTag(imageTag);
+        if (!isValidHash(hash)) {
+            throw new IOException("Cannot get manifest for image tag " + 
imageTag
+                + ": " + hash + " is not a valid image hash");
+        }
         ImageManifest manifest = manifestCache.get(hash);
         if (manifest != null) {
             return manifest;
@@ -271,12 +286,16 @@ public class LocalOrHdfsImageTagToManifestPlugin 
implements OciImageTagToManifes
 
         // 1) Go to local file
         // 2) Go to HDFS
-        // 3) Use tag as is/Assume tag is the hash
+        // 3) Use tag as is/Assume tag is the hash; only acceptable if the tag 
looks like a hash
         if ((hash = localImageToHashCache.get(imageTag)) != null) {
             return hash;
         } else if ((hash = hdfsImageToHashCache.get(imageTag)) != null) {
             return hash;
         } else {
+            if (!isValidHash(imageTag)) {
+                throw new UncheckedIOException(new IOException("Image tag " + 
imageTag
+                    + " is not in the image-tag-to-hash files and is not a 
valid image hash itself"));
+            }
             return imageTag;
         }
     }
diff --git 
a/external/storm-hdfs-oci/src/test/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPluginTest.java
 
b/external/storm-hdfs-oci/src/test/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPluginTest.java
new file mode 100644
index 000000000..2cda2b063
--- /dev/null
+++ 
b/external/storm-hdfs-oci/src/test/java/org/apache/storm/container/oci/LocalOrHdfsImageTagToManifestPluginTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.storm.container.oci;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.storm.DaemonConfig;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class LocalOrHdfsImageTagToManifestPluginTest {
+
+    private static final String KNOWN_HASH = 
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+    private static final String UNKNOWN_HASH = 
"fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
+    //same length as a hash, but made of characters that would still escape 
the manifest directory
+    private static final String HASH_LENGTH_PATH = "../../../user/foo/bar/" + 
"a".repeat(42);
+
+    @TempDir
+    Path tempDir;
+
+    private LocalOrHdfsImageTagToManifestPlugin createPlugin() throws 
IOException {
+        Path hashFile = tempDir.resolve("image-tag-to-hash");
+        Files.write(hashFile, ("busybox:latest:" + KNOWN_HASH + 
"\n").getBytes(StandardCharsets.UTF_8));
+
+        Map<String, Object> conf = new HashMap<>();
+        
conf.put("storm.oci.local.or.hdfs.image.tag.to.manifest.plugin.local.hash.file",
 hashFile.toString());
+        conf.put(DaemonConfig.STORM_OCI_IMAGE_HDFS_TOPLEVEL_DIR, "/storm/oci");
+
+        LocalOrHdfsImageTagToManifestPlugin plugin = new 
LocalOrHdfsImageTagToManifestPlugin();
+        plugin.init(conf);
+        return plugin;
+    }
+
+    @Test
+    public void testKnownImageTagIsMappedToItsHash() throws Exception {
+        assertEquals(KNOWN_HASH, 
createPlugin().getHashFromImageTag("busybox:latest"));
+    }
+
+    @Test
+    public void testUnmappedImageTagIsUsedAsHashWhenItLooksLikeOne() throws 
Exception {
+        assertEquals(UNKNOWN_HASH, 
createPlugin().getHashFromImageTag(UNKNOWN_HASH));
+    }
+
+    @Test
+    public void testUnmappedImageTagThatIsNotAHashIsRejected() throws 
Exception {
+        LocalOrHdfsImageTagToManifestPlugin plugin = createPlugin();
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getHashFromImageTag("../../../user/foo/bar"));
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getHashFromImageTag("busybox:unknown"));
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getHashFromImageTag(".."));
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getHashFromImageTag("/etc/passwd"));
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getHashFromImageTag(HASH_LENGTH_PATH));
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getHashFromImageTag(UNKNOWN_HASH + "a"));
+    }
+
+    @Test
+    public void testGetManifestFromImageTagRejectsUnmappedNonHashTag() throws 
Exception {
+        LocalOrHdfsImageTagToManifestPlugin plugin = createPlugin();
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getManifestFromImageTag("../../../user/foo/bar"));
+        assertThrows(UncheckedIOException.class, () -> 
plugin.getManifestFromImageTag(HASH_LENGTH_PATH));
+    }
+}

Reply via email to