From 8f1f8a20376a5f30d187db78c218e1cd97202b38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Galder=20Zamarren=CC=83o?= <galder@zamarreno.com>
Date: Wed, 13 Dec 2017 11:34:14 +0100
Subject: [PATCH] ISPN-8624 White list unmarshalling for GenericJBossMarshaller

---
 .../client/hotrod/RemoteCacheManager.java     |  7 +++-
 .../hotrod/marshall/MarshallerUtil.java       | 16 +-------
 .../marshall/WhiteListMarshallingTest.java    | 39 +++++++++++++++++++
 .../org/infinispan/commons/logging/Log.java   |  5 +++
 .../commons/marshall/MarshallUtil.java        | 30 ++++++++++++++
 .../jboss/GenericJBossMarshaller.java         | 32 +++++++++++++++
 6 files changed, 114 insertions(+), 15 deletions(-)
 create mode 100644 client/hotrod-client/src/test/java/org/infinispan/client/hotrod/marshall/WhiteListMarshallingTest.java

diff --git a/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/RemoteCacheManager.java b/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/RemoteCacheManager.java
index f266c7e4447..235a62fc284 100644
--- a/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/RemoteCacheManager.java
+++ b/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/RemoteCacheManager.java
@@ -33,6 +33,7 @@
 import org.infinispan.commons.api.BasicCacheContainer;
 import org.infinispan.commons.executors.ExecutorFactory;
 import org.infinispan.commons.marshall.Marshaller;
+import org.infinispan.commons.marshall.jboss.GenericJBossMarshaller;
 import org.infinispan.commons.util.FileLookupFactory;
 import org.infinispan.commons.util.TypedProperties;
 import org.infinispan.commons.util.Util;
@@ -562,7 +563,11 @@ public void start() {
       if (marshaller == null) {
          marshaller = configuration.marshaller();
          if (marshaller == null) {
-            marshaller = Util.getInstance(configuration.marshallerClass());
+            Class<? extends Marshaller> clazz = configuration.marshallerClass();
+            if (clazz == GenericJBossMarshaller.class && !configuration.serialWhitelist().isEmpty())
+               marshaller = new GenericJBossMarshaller(configuration.serialWhitelist());
+            else
+               marshaller = Util.getInstance(clazz);
          }
       }
 
diff --git a/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/marshall/MarshallerUtil.java b/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/marshall/MarshallerUtil.java
index 876fa6de338..990a1082132 100644
--- a/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/marshall/MarshallerUtil.java
+++ b/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/marshall/MarshallerUtil.java
@@ -5,6 +5,7 @@
 import org.infinispan.client.hotrod.logging.Log;
 import org.infinispan.client.hotrod.logging.LogFactory;
 import org.infinispan.commons.CacheException;
+import org.infinispan.commons.marshall.MarshallUtil;
 import org.infinispan.commons.marshall.Marshaller;
 import org.infinispan.commons.util.Util;
 
@@ -15,8 +16,6 @@
 import java.io.ObjectStreamClass;
 import java.io.ObjectStreamConstants;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * @author Galder Zamarreño
@@ -100,18 +99,7 @@ public CheckedInputStream(InputStream in, List<String> whitelist) throws IOExcep
       @Override
       protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
          //Enforce SerialKiller's whitelist
-         boolean safeClass = false;
-         for (String whiteRegExp : whitelist) {
-            Pattern whitePattern = Pattern.compile(whiteRegExp);
-            Matcher whiteMatcher = whitePattern.matcher(desc.getName());
-            if (whiteMatcher.find()) {
-               safeClass = true;
-
-               if (log.isTraceEnabled())
-                  log.tracef("Whitelist match: '%s'", desc.getName());
-            }
-         }
-
+         boolean safeClass = MarshallUtil.isSafeClass(desc.getName(), whitelist);
          if (!safeClass)
             throw log.classNotInWhitelist(desc.getName());
 
diff --git a/client/hotrod-client/src/test/java/org/infinispan/client/hotrod/marshall/WhiteListMarshallingTest.java b/client/hotrod-client/src/test/java/org/infinispan/client/hotrod/marshall/WhiteListMarshallingTest.java
new file mode 100644
index 00000000000..2a83b083645
--- /dev/null
+++ b/client/hotrod-client/src/test/java/org/infinispan/client/hotrod/marshall/WhiteListMarshallingTest.java
@@ -0,0 +1,39 @@
+package org.infinispan.client.hotrod.marshall;
+
+import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
+import org.infinispan.client.hotrod.exceptions.HotRodClientException;
+import org.infinispan.client.hotrod.test.InternalRemoteCacheManager;
+import org.infinispan.client.hotrod.test.SingleHotRodServerTest;
+import org.infinispan.test.data.Person;
+import org.testng.annotations.Test;
+
+import java.io.Serializable;
+
+@Test(testName = "client.hotrod.marshall.WhiteListMarshallingTest", groups = {"functional", "smoke"} )
+public class WhiteListMarshallingTest extends SingleHotRodServerTest {
+
+   @Override
+   protected RemoteCacheManager getRemoteCacheManager() {
+      ConfigurationBuilder builder = new ConfigurationBuilder();
+      builder.addJavaSerialWhiteList(".*Person.*");
+      builder.addServer().host("127.0.0.1").port(hotrodServer.getPort());
+      return new InternalRemoteCacheManager(builder.build());
+   }
+
+   @Test(expectedExceptions = HotRodClientException.class,
+      expectedExceptionsMessageRegExp = ".*ISPN004034:.*")
+   public void testUnsafeClassNotAllowed() {
+      remoteCacheManager.getCache().put("unsafe", new UnsafeClass());
+      remoteCacheManager.getCache().get("unsafe");
+   }
+
+   public void testSafeClassAllowed() {
+      remoteCacheManager.getCache().put("safe", new Person());
+      remoteCacheManager.getCache().get("safe");
+   }
+
+   private static final class UnsafeClass implements Serializable {
+   }
+
+}
diff --git a/commons/src/main/java/org/infinispan/commons/logging/Log.java b/commons/src/main/java/org/infinispan/commons/logging/Log.java
index 1054f40cba6..34d7345777d 100644
--- a/commons/src/main/java/org/infinispan/commons/logging/Log.java
+++ b/commons/src/main/java/org/infinispan/commons/logging/Log.java
@@ -4,6 +4,7 @@
 import static org.jboss.logging.Logger.Level.WARN;
 
 import org.infinispan.commons.CacheConfigurationException;
+import org.infinispan.commons.CacheException;
 import org.jboss.logging.BasicLogger;
 import org.jboss.logging.annotations.Cause;
 import org.jboss.logging.annotations.LogMessage;
@@ -107,5 +108,9 @@
 
    @Message(value = "Cannot find resource '%s'", id = 918)
    IOException cannotFindResource(String fileName);
+
+   @Message(value = "Class '%s' blocked by deserialization white list. Adjust the client configuration serialization white list regular expression to include this class.", id = 28023)
+   CacheException classNotInWhitelist(String className);
+
 }
 
diff --git a/commons/src/main/java/org/infinispan/commons/marshall/MarshallUtil.java b/commons/src/main/java/org/infinispan/commons/marshall/MarshallUtil.java
index f8b39af35aa..fa2f2ed3804 100644
--- a/commons/src/main/java/org/infinispan/commons/marshall/MarshallUtil.java
+++ b/commons/src/main/java/org/infinispan/commons/marshall/MarshallUtil.java
@@ -1,15 +1,20 @@
 package org.infinispan.commons.marshall;
 
 import net.jcip.annotations.Immutable;
+import org.infinispan.commons.logging.Log;
+import org.infinispan.commons.logging.LogFactory;
 import org.infinispan.commons.util.Util;
 
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * MarshallUtil.
@@ -22,6 +27,8 @@
 
    private static final byte NULL_VALUE = -1;
 
+   private static final Log log = LogFactory.getLog(MarshallUtil.class);
+
    /**
     * Marshall the {@code map} to the {@code ObjectOutput}.
     * <p>
@@ -365,6 +372,29 @@ public static int unmarshallInt(ObjectInput in) throws IOException {
       }
    }
 
+   /**
+    * Checks whether class name is matched by the class name white list regular expressions provided.
+    *
+    * @param className class to verify
+    * @param whitelist list of regular expressions to match class name against
+    * @return true if the class matched at least one of the regular expressions,
+    *         false otherwise
+    */
+   public static boolean isSafeClass(String className, List<String> whitelist) {
+      for (String whiteRegExp : whitelist) {
+         Pattern whitePattern = Pattern.compile(whiteRegExp);
+         Matcher whiteMatcher = whitePattern.matcher(className);
+         if (whiteMatcher.find()) {
+            if (log.isTraceEnabled())
+               log.tracef("Whitelist match: '%s'", className);
+
+            return true;
+         }
+      }
+
+      return false;
+   }
+
    public interface ArrayBuilder<E> {
       E[] build(int size);
    }
diff --git a/commons/src/main/java/org/infinispan/commons/marshall/jboss/GenericJBossMarshaller.java b/commons/src/main/java/org/infinispan/commons/marshall/jboss/GenericJBossMarshaller.java
index d623a408cdf..eada3cda0a6 100644
--- a/commons/src/main/java/org/infinispan/commons/marshall/jboss/GenericJBossMarshaller.java
+++ b/commons/src/main/java/org/infinispan/commons/marshall/jboss/GenericJBossMarshaller.java
@@ -1,5 +1,11 @@
 package org.infinispan.commons.marshall.jboss;
 
+import org.infinispan.commons.marshall.MarshallUtil;
+import org.jboss.marshalling.Unmarshaller;
+
+import java.io.IOException;
+import java.util.List;
+
 /**
  * A marshaller that makes use of <a href="http://www.jboss.org/jbossmarshalling">JBoss Marshalling</a>
  * to serialize and deserialize objects. This marshaller is oriented at external,
@@ -23,4 +29,30 @@ public GenericJBossMarshaller(ClassLoader classLoader) {
             new DefaultContextClassResolver(classLoader != null ? classLoader : this.getClass().getClassLoader()));
    }
 
+   public GenericJBossMarshaller(List<String> whitelist) {
+      super();
+      baseCfg.setClassResolver(
+         new CheckedClassResolver(whitelist, this.getClass().getClassLoader()));
+   }
+
+   private static final class CheckedClassResolver extends DefaultContextClassResolver {
+
+      private final List<String> whitelist;
+
+      CheckedClassResolver(List<String> whitelist, ClassLoader defaultClassLoader) {
+         super(defaultClassLoader);
+         this.whitelist = whitelist;
+      }
+
+      @Override
+      public Class<?> resolveClass(Unmarshaller unmarshaller, String name, long serialVersionUID) throws IOException, ClassNotFoundException {
+         boolean safeClass = MarshallUtil.isSafeClass(name, whitelist);
+         if (!safeClass)
+            throw log.classNotInWhitelist(name);
+
+         return super.resolveClass(unmarshaller, name, serialVersionUID);
+      }
+
+   }
+
 }
