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

rmaucher pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit a9aa2f7871d0026bb16c133097dc59b6f5226f0d
Author: opencode <[email protected]>
AuthorDate: Sun Oct 11 14:53:49 2026 +0200

    Deserialize the key of a backup request with the loaders of the map
    
    Answering a backup request read the key of the received message without
    specifying class loaders, unlike every other receive path which
    deserializes with the loaders of the map. A key whose class is not
    visible to the default loaders then failed the reply with an unchecked
    exception on the responder, which left the requester without an answer
    until its rpc timeout.
    
    The request is now deserialized with the loaders of the map before the
    lookup, and a key that cannot be deserialized fails the request with a
    plain error log and no reply, the same outcome as for a missing entry.
---
 .../tribes/tipis/AbstractReplicatedMap.java        | 11 +++-
 .../tribes/tipis/TestAbstractReplicatedMap.java    | 70 ++++++++++++++++++++++
 2 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java 
b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
index 3ef84d7777..8ecf02efd3 100644
--- a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
@@ -700,7 +700,16 @@ public abstract class AbstractReplicatedMap<K, V>
 
         // backup request
         if (mapmsg.getMsgType() == MapMessage.MSG_RETRIEVE_BACKUP) {
-            MapEntry<K,V> entry = innerMap.get(mapmsg.getKey());
+            MapEntry<K,V> entry;
+            try {
+                // Deserialize the key with the external loaders, like the 
other receive paths do,
+                // since the default loaders cannot see the class of a key 
from an application
+                mapmsg.deserialize(getExternalLoaders());
+                entry = innerMap.get(mapmsg.getKey());
+            } catch (ClassNotFoundException | IOException x) {
+                
log.error(sm.getString("abstractReplicatedMap.unable.deserialize.MapMessage"), 
x);
+                return null;
+            }
             if (entry == null || (!entry.isSerializable())) {
                 return null;
             }
diff --git 
a/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java 
b/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
index ae82476a55..0f3673def6 100644
--- a/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
+++ b/test/org/apache/catalina/tribes/tipis/TestAbstractReplicatedMap.java
@@ -35,6 +35,7 @@ import org.apache.catalina.tribes.ErrorHandler;
 import org.apache.catalina.tribes.Member;
 import org.apache.catalina.tribes.UniqueId;
 import org.apache.catalina.tribes.group.GroupChannel;
+import org.apache.catalina.tribes.io.XByteBuffer;
 import org.apache.catalina.tribes.membership.MemberImpl;
 import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapMessage;
 
@@ -266,4 +267,73 @@ public class TestAbstractReplicatedMap {
         Assert.assertFalse(map.isEmpty());
         Assert.assertTrue(map.values().contains(null));
     }
+
+    private static final class SerialKey implements Serializable {
+
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        private final String id;
+
+        SerialKey(String id) {
+            this.id = id;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o instanceof SerialKey && id.equals(((SerialKey) o).id);
+        }
+
+        @Override
+        public int hashCode() {
+            return id.hashCode();
+        }
+    }
+
+    /*
+     * A received backup request carries its key only in serialized form. The 
reply must deserialize
+     * that key with the loaders of the map, like the other receive paths do, 
and must fail the
+     * request without throwing when the key cannot be deserialized, so that 
the requester gets a
+     * normal timeout instead of being answered by a failure of the responder.
+     */
+    @Test
+    public void testBackupRequestDeserializesKeyOfReceivedMessage() throws 
Exception {
+        LazyReplicatedMap<Object,String> map = new 
LazyReplicatedMap<Object,String>(
+                (key, value) -> {
+                    // Not made primary here
+                }, new TestChannel(), 1000, "TestAbstractReplicatedMap", null);
+        map.put(new SerialKey("key"), "value");
+        Member sender = new MemberImpl("localhost", 4017, -1);
+
+        // Round trip over the serialization to get a message with the key 
data but no live key
+        byte[] wire = XByteBuffer.serialize(new MapMessage(
+                "TestAbstractReplicatedMap".getBytes(StandardCharsets.UTF_8), 
MapMessage.MSG_RETRIEVE_BACKUP, false,
+                new SerialKey("key"), null, null, null, null));
+
+        MapMessage received = (MapMessage) XByteBuffer.deserialize(wire);
+        Serializable reply = map.replyRequest(received, sender);
+        Assert.assertTrue(reply instanceof MapMessage);
+        Assert.assertEquals("value", ((MapMessage) reply).getValue());
+
+        // Corrupt the class name inside the serialized key, so it cannot be 
deserialized
+        byte[] corrupted = wire.clone();
+        int at = indexOf(corrupted, 
"SerialKey".getBytes(StandardCharsets.US_ASCII));
+        Assert.assertTrue(at > 0);
+        corrupted[at] = 'X';
+        MapMessage broken = (MapMessage) XByteBuffer.deserialize(corrupted);
+        Assert.assertNull(map.replyRequest(broken, sender));
+    }
+
+    private static int indexOf(byte[] data, byte[] pattern) {
+        outer:
+        for (int i = 0; i <= data.length - pattern.length; i++) {
+            for (int j = 0; j < pattern.length; j++) {
+                if (data[i + j] != pattern[j]) {
+                    continue outer;
+                }
+            }
+            return i;
+        }
+        return -1;
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to