ignite-6643 Marshalling improvements
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/b98a003c Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/b98a003c Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/b98a003c Branch: refs/heads/ignite-6644 Commit: b98a003c31fabacab0cc1adc96479b14d6f61576 Parents: fc09631 Author: Andrey Gura <[email protected]> Authored: Mon Jan 22 22:41:54 2018 +0300 Committer: Andrey Gura <[email protected]> Committed: Wed Jan 24 18:01:59 2018 +0300 ---------------------------------------------------------------------- .../apache/ignite/IgniteSystemProperties.java | 60 +---- .../org/apache/ignite/internal/ClassSet.java | 108 +++++++++ .../ignite/internal/GridKernalContext.java | 6 + .../ignite/internal/GridKernalContextImpl.java | 13 +- .../apache/ignite/internal/IgniteKernal.java | 107 ++++++++- .../ignite/internal/MarshallerContextImpl.java | 8 +- .../wal/reader/StandaloneGridKernalContext.java | 6 + .../ignite/internal/util/IgniteUtils.java | 15 ++ .../ignite/marshaller/MarshallerUtils.java | 6 + .../ignite/marshaller/jdk/JdkMarshaller.java | 20 +- .../jdk/JdkMarshallerObjectInputStream.java | 9 +- .../spi/discovery/tcp/TcpDiscoverySpi.java | 8 +- .../org/apache/ignite/stream/StreamAdapter.java | 2 +- .../ignite/stream/socket/SocketStreamer.java | 8 +- .../test/config/class_list_exploit_excluded.txt | 18 ++ .../test/config/class_list_exploit_included.txt | 19 ++ .../apache/ignite/internal/ClassSetTest.java | 71 ++++++ .../DiscoveryUnmarshalVulnerabilityTest.java | 180 +++++++++++++++ ...ocketStreamerUnmarshalVulnerabilityTest.java | 222 +++++++++++++++++++ .../junits/GridTestKernalContext.java | 9 +- .../ignite/testsuites/IgniteBasicTestSuite.java | 3 + .../IgniteSpiDiscoverySelfTestSuite.java | 3 + .../testsuites/IgniteStreamSelfTestSuite.java | 2 + 23 files changed, 834 insertions(+), 69 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index 9cd031d..7761292 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -28,7 +28,6 @@ import org.apache.ignite.cluster.ClusterGroup; import org.apache.ignite.configuration.DataStorageConfiguration; import org.apache.ignite.internal.client.GridClient; import org.apache.ignite.internal.marshaller.optimized.OptimizedMarshaller; -import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.stream.StreamTransformer; import org.jetbrains.annotations.Nullable; @@ -222,11 +221,6 @@ public final class IgniteSystemProperties { public static final String IGNITE_MAX_COMPLETED_TX_COUNT = "IGNITE_MAX_COMPLETED_TX_COUNT"; /** - * Concurrency level for all concurrent hash maps created by Ignite. - */ - public static final String IGNITE_MAP_CONCURRENCY_LEVEL = "IGNITE_MAP_CONCURRENCY_LEVEL"; - - /** * Transactions that take more time, than value of this property, will be output to log * with warning level. {@code 0} (default value) disables warning on slow transactions. */ @@ -320,13 +314,6 @@ public final class IgniteSystemProperties { /** Ttl of removed cache entries (ms). */ public static final String IGNITE_CACHE_REMOVED_ENTRIES_TTL = "IGNITE_CACHE_REMOVED_ENTRIES_TTL"; - /** Maximum amount of concurrent updates per system thread in atomic caches in case of PRIMARY_SYNC or FULL_ASYNC - * write synchronization mode. If this limit is exceeded then update will be performed with FULL_SYNC - * synchronization mode. If value is {@code 0} then limit is unbounded. - */ - public static final String IGNITE_ATOMIC_CACHE_MAX_CONCURRENT_DHT_UPDATES = - "IGNITE_ATOMIC_CACHE_MAX_CONCURRENT_DHT_UPDATES"; - /** * Comma separated list of addresses in format "10.100.22.100:45000,10.100.22.101:45000". * Makes sense only for {@link org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder}. @@ -479,9 +466,6 @@ public final class IgniteSystemProperties { /** Number of cache operation retries in case of topology exceptions. */ public static final String IGNITE_CACHE_RETRIES_COUNT = "IGNITE_CACHE_RETRIES_COUNT"; - /** Number of times pending cache objects will be dumped to the log in case of partition exchange timeout. */ - public static final String IGNITE_DUMP_PENDING_OBJECTS_THRESHOLD = "IGNITE_DUMP_PENDING_OBJECTS_THRESHOLD"; - /** If this property is set to {@code true} then Ignite will log thread dump in case of partition exchange timeout. */ public static final String IGNITE_THREAD_DUMP_ON_EXCHANGE_TIMEOUT = "IGNITE_THREAD_DUMP_ON_EXCHANGE_TIMEOUT"; @@ -524,6 +508,12 @@ public final class IgniteSystemProperties { public static final String IGNITE_BINARY_MARSHALLER_USE_STRING_SERIALIZATION_VER_2 = "IGNITE_BINARY_MARSHALLER_USE_STRING_SERIALIZATION_VER_2"; + /** Defines path to the file that contains list of classes allowed to safe deserialization.*/ + public static final String IGNITE_MARSHALLER_WHITELIST = "IGNITE_MARSHALLER_WHITELIST"; + + /** Defines path to the file that contains list of classes disallowed to safe deserialization.*/ + public static final String IGNITE_MARSHALLER_BLACKLIST = "IGNITE_MARSHALLER_BLACKLIST"; + /** * If set to {@code true}, then default selected keys set is used inside * {@code GridNioServer} which lead to some extra garbage generation when @@ -689,16 +679,7 @@ public final class IgniteSystemProperties { */ public static final String IGNITE_FORCE_START_JAVA7 = "IGNITE_FORCE_START_JAVA7"; - /** Returns true for system properties only avoiding sending sensitive information. */ - private static final IgnitePredicate<Map.Entry<String, String>> PROPS_FILTER = new IgnitePredicate<Map.Entry<String, String>>() { - @Override public boolean apply(final Map.Entry<String, String> entry) { - final String key = entry.getKey(); - - return key.startsWith("java.") || key.startsWith("os.") || key.startsWith("user."); - } - }; - - /** + /** * When set to {@code true}, Ignite switches to compatibility mode with versions that don't * support service security permissions. In this case security permissions will be ignored * (if they set). @@ -740,11 +721,6 @@ public final class IgniteSystemProperties { public static final String IGNITE_ENABLE_FORCIBLE_NODE_KILL = "IGNITE_ENABLE_FORCIBLE_NODE_KILL"; /** - * - */ - public static final String IGNITE_WAL_ARCHIVE_COMPACT_SKIP_DELTA_RECORD = "IGNITE_WAL_ARCHIVE_COMPACT_SKIP_DELTA_RECORD"; - - /** * Tasks stealing will be started if tasks queue size per data-streamer thread exceeds this threshold. * <p> * Default value is {@code 4}. @@ -1023,26 +999,4 @@ public final class IgniteSystemProperties { return sysProps; } - - /** - * Does the same as {@link #snapshot()} but filters out - * possible sensitive user data. - * - * @return Snapshot of system properties. - */ - @SuppressWarnings("unchecked") - public static Properties safeSnapshot() { - final Properties props = snapshot(); - - final Iterator<Map.Entry<Object, Object>> iter = props.entrySet().iterator(); - - while (iter.hasNext()) { - final Map.Entry entry = iter.next(); - - if (!PROPS_FILTER.apply(entry)) - iter.remove(); - } - - return props; - } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/ClassSet.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/ClassSet.java b/modules/core/src/main/java/org/apache/ignite/internal/ClassSet.java new file mode 100644 index 0000000..400984d --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/ClassSet.java @@ -0,0 +1,108 @@ +/* + * 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.ignite.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.IllegalFormatException; +import java.util.Map; + +/** + * Set of classes represented as prefix tree. + * {@code *} symbol is allowed and indicates that all packages and classes are included. + */ +public class ClassSet { + /** Corresponds to {@code *} symbol. */ + private static final Map<String, Node> ALL = Collections.emptyMap(); + + /** Root. */ + private Node root = new Node(); + + /** + * Adds class name to the set. + * + * @param clsName Class name. + */ + public void add(String clsName) { + String[] tokens = clsName.split("\\."); + + Node cur = root; + + for (int i = 0; i < tokens.length; i++) { + if (cur.children == ALL) + return; + + if (tokens[i].equals("*")) { + if (i != tokens.length - 1) + throw new IllegalArgumentException("Incorrect class name format."); + + cur.children = ALL; + + return; + } + + if (cur.children == null) + cur.children = new HashMap<>(); + + Node n = cur.children.get(tokens[i]); + + if (n == null) { + n = new Node(); + + cur.children.put(tokens[i], n); + } + + cur = n; + } + } + + /** + * @param clsName Class name. + */ + public boolean contains(String clsName) { + String[] tokens = clsName.split("\\."); + + Node cur = root; + + for (int i = 0; i < tokens.length; i++) { + if (cur.children == ALL) + return true; + + if (cur.children == null) + return false; + + Node n = cur.children.get(tokens[i]); + + if (n == null) + return false; + + if (i == tokens.length - 1) + return true; + + cur = n; + } + + return false; + } + + /** */ + private static class Node { + /** Children. */ + private Map<String, Node> children; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java index ce12b61..2877dad 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java @@ -69,6 +69,7 @@ import org.apache.ignite.internal.suggestions.GridPerformanceSuggestions; import org.apache.ignite.internal.util.IgniteExceptionRegistry; import org.apache.ignite.internal.util.StripedExecutor; import org.apache.ignite.internal.util.tostring.GridToStringExclude; +import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.plugin.PluginNotFoundException; import org.apache.ignite.plugin.PluginProvider; import org.apache.ignite.thread.IgniteStripedThreadPoolExecutor; @@ -655,4 +656,9 @@ public interface GridKernalContext extends Iterable<GridComponent> { * @return subscription processor to manage internal-only (strict node-local) subscriptions between components. */ public GridInternalSubscriptionProcessor internalSubscriptionProcessor(); + + /** + * @return class name filter for marshaller. + */ + public IgnitePredicate<String> classNameFilter(); } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java index 36c6231..61d0dd1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java @@ -91,6 +91,7 @@ import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.plugin.PluginNotFoundException; import org.apache.ignite.plugin.PluginProvider; import org.apache.ignite.thread.IgniteStripedThreadPoolExecutor; @@ -385,6 +386,9 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable /** */ private GridInternalSubscriptionProcessor internalSubscriptionProc; + /** Class name filter. */ + private IgnitePredicate<String> clsFilter; + /** * No-arg constructor is required by externalization. */ @@ -438,7 +442,8 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable ExecutorService qryExecSvc, ExecutorService schemaExecSvc, @Nullable Map<String, ? extends ExecutorService> customExecSvcs, - List<PluginProvider> plugins + List<PluginProvider> plugins, + IgnitePredicate<String> clsFilter ) { assert grid != null; assert cfg != null; @@ -463,6 +468,7 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable this.qryExecSvc = qryExecSvc; this.schemaExecSvc = schemaExecSvc; this.customExecSvcs = customExecSvcs; + this.clsFilter = clsFilter; marshCtx = new MarshallerContextImpl(plugins); @@ -1092,6 +1098,11 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable } /** {@inheritDoc} */ + @Override public IgnitePredicate<String> classNameFilter() { + return clsFilter; + } + + /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridKernalContextImpl.class, this); } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java index 16698b5..3094963 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java @@ -17,10 +17,14 @@ package org.apache.ignite.internal; +import java.io.BufferedReader; import java.io.Externalizable; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.InvalidObjectException; import java.io.ObjectInput; import java.io.ObjectOutput; @@ -259,6 +263,8 @@ import static org.apache.ignite.internal.IgniteVersionUtils.VER; import static org.apache.ignite.internal.IgniteVersionUtils.VER_STR; import static org.apache.ignite.lifecycle.LifecycleEventType.AFTER_NODE_START; import static org.apache.ignite.lifecycle.LifecycleEventType.BEFORE_NODE_START; +import static org.apache.ignite.marshaller.MarshallerUtils.CLS_NAMES_FILE; +import static org.apache.ignite.marshaller.MarshallerUtils.JDK_CLS_NAMES_FILE; /** * Ignite kernal. @@ -834,7 +840,8 @@ public class IgniteKernal implements IgniteEx, IgniteMXBean, Externalizable { qryExecSvc, schemaExecSvc, customExecSvcs, - plugins + plugins, + classNameFilter() ); cfg.getMarshaller().setContext(ctx.marshallerContext()); @@ -1685,6 +1692,104 @@ public class IgniteKernal implements IgniteEx, IgniteMXBean, Externalizable { } /** + * Returns class name filter for marshaller. + * @return Class name filter for marshaller. + */ + private IgnitePredicate<String> classNameFilter() throws IgniteCheckedException { + ClassSet whiteList = classWhiteList(); + ClassSet blackList = classBlackList(); + + return new IgnitePredicate<String>() { + @Override public boolean apply(String s) { + // Allows all primitive arrays and checks arrays' type. + if ((blackList != null || whiteList != null) && s.charAt(0) == '[') { + if (s.charAt(1) == 'L' && s.length() > 2) + s = s.substring(2, s.length() - 1); + else + return true; + } + + return (blackList == null || !blackList.contains(s)) && (whiteList == null || whiteList.contains(s)); + } + }; + } + + /** + * @return White list of classes. + */ + private ClassSet classWhiteList() throws IgniteCheckedException { + ClassSet clsSet = null; + + String fileName = IgniteSystemProperties.getString(IgniteSystemProperties.IGNITE_MARSHALLER_WHITELIST); + + if (fileName != null) { + clsSet = new ClassSet(); + + addClassNames(JDK_CLS_NAMES_FILE, clsSet); + addClassNames(CLS_NAMES_FILE, clsSet); + addClassNames(fileName, clsSet); + } + + return clsSet; + } + + /** + * @return Black list of classes. + */ + private ClassSet classBlackList() throws IgniteCheckedException { + ClassSet clsSet = null; + + String blackListFileName = IgniteSystemProperties.getString(IgniteSystemProperties.IGNITE_MARSHALLER_BLACKLIST); + + if (blackListFileName != null) + addClassNames(blackListFileName, clsSet = new ClassSet()); + + return clsSet; + } + + + /** + * Reads class names from resource referred by given system property name and returns set of classes. + * @param fileName File name containing list of classes. + * @param clsSet Class set for update. + * @return Set of classes. + */ + private void addClassNames(String fileName, ClassSet clsSet) throws IgniteCheckedException { + InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName); + + if (is == null) { + try { + is = new FileInputStream(new File(fileName)); + } + catch (FileNotFoundException e) { + throw new IgniteCheckedException("File " + fileName + " not found."); + } + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line; + + for (int i = 1; (line = reader.readLine()) != null; i++) { + String s = line.trim(); + + if (!s.isEmpty() && s.charAt(0) != '#' && s.charAt(0) != '[') { + try { + clsSet.add(s); + } + catch (IllegalArgumentException e) { + throw new IgniteCheckedException("Exception occurred while reading list of classes" + + "[path=" + fileName + ", row=" + i + ", line=" + s + ']', e); + } + } + } + } + catch (IOException e) { + throw new IgniteCheckedException("Exception occurred while reading and creating list of classes " + + "[path=" + fileName + ']', e); + } + } + + /** * Add helper. * * @param helper Helper. http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java index 08661a3..8b0dc4f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java @@ -57,18 +57,14 @@ import org.jetbrains.annotations.Nullable; import org.jsr166.ConcurrentHashMap8; import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID; +import static org.apache.ignite.marshaller.MarshallerUtils.CLS_NAMES_FILE; +import static org.apache.ignite.marshaller.MarshallerUtils.JDK_CLS_NAMES_FILE; /** * Marshaller context implementation. */ public class MarshallerContextImpl implements MarshallerContext { /** */ - private static final String CLS_NAMES_FILE = "META-INF/classnames.properties"; - - /** */ - private static final String JDK_CLS_NAMES_FILE = "META-INF/classnames-jdk.properties"; - - /** */ private final Map<Integer, MappedName> sysTypesMap = new HashMap<>(); /** */ http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java index fa3f7f3..92a6e6d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java @@ -84,6 +84,7 @@ import org.apache.ignite.internal.suggestions.GridPerformanceSuggestions; import org.apache.ignite.internal.util.IgniteExceptionRegistry; import org.apache.ignite.internal.util.StripedExecutor; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.marshaller.Marshaller; import org.apache.ignite.plugin.PluginNotFoundException; import org.apache.ignite.plugin.PluginProvider; @@ -628,4 +629,9 @@ public class StandaloneGridKernalContext implements GridKernalContext { @NotNull @Override public Iterator<GridComponent> iterator() { return null; } + + /** {@inheritDoc} */ + @Override public IgnitePredicate<String> classNameFilter() { + return null; + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 0f6a41a..238030a 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -8532,6 +8532,18 @@ public abstract class IgniteUtils { * @throws ClassNotFoundException If class not found. */ public static Class<?> forName(String clsName, @Nullable ClassLoader ldr) throws ClassNotFoundException { + return U.forName(clsName, ldr, null); + } + + /** + * Gets class for provided name. Accepts primitive types names. + * + * @param clsName Class name. + * @param ldr Class loader. + * @return Class. + * @throws ClassNotFoundException If class not found. + */ + public static Class<?> forName(String clsName, @Nullable ClassLoader ldr, IgnitePredicate<String> clsFilter) throws ClassNotFoundException { assert clsName != null; Class<?> cls = primitiveMap.get(clsName); @@ -8558,6 +8570,9 @@ public abstract class IgniteUtils { cls = ldrMap.get(clsName); if (cls == null) { + if (clsFilter != null && !clsFilter.apply(clsName)) + throw new RuntimeException("Deserialization of class " + clsName + " is disallowed."); + Class old = ldrMap.putIfAbsent(clsName, cls = Class.forName(clsName, true, ldr)); if (old != null) http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/marshaller/MarshallerUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/MarshallerUtils.java b/modules/core/src/main/java/org/apache/ignite/marshaller/MarshallerUtils.java index ad63702..bec1f57 100644 --- a/modules/core/src/main/java/org/apache/ignite/marshaller/MarshallerUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/MarshallerUtils.java @@ -25,6 +25,12 @@ import org.jetbrains.annotations.Nullable; * Utility marshaller methods. */ public class MarshallerUtils { + /** Jdk class names file. */ + public static final String JDK_CLS_NAMES_FILE = "META-INF/classnames-jdk.properties"; + + /** Class names file. */ + public static final String CLS_NAMES_FILE = "META-INF/classnames.properties"; + /** Job sender node version. */ private static final ThreadLocal<IgniteProductVersion> JOB_SND_NODE_VER = new ThreadLocal<>(); http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshaller.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshaller.java b/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshaller.java index 6759c40..ae4033b 100644 --- a/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshaller.java +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshaller.java @@ -27,6 +27,7 @@ import org.apache.ignite.internal.util.io.GridByteArrayInputStream; import org.apache.ignite.internal.util.io.GridByteArrayOutputStream; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.marshaller.AbstractNodeNameAwareMarshaller; import org.jetbrains.annotations.Nullable; @@ -67,6 +68,23 @@ import org.jetbrains.annotations.Nullable; * For information about Spring framework visit <a href="http://www.springframework.org/">www.springframework.org</a> */ public class JdkMarshaller extends AbstractNodeNameAwareMarshaller { + /** Class name filter. */ + private final IgnitePredicate<String> clsFilter; + + /** + * Default constructor. + */ + public JdkMarshaller() { + this(null); + } + + /** + * @param clsFilter Class name filter. + */ + public JdkMarshaller(IgnitePredicate<String> clsFilter) { + this.clsFilter = clsFilter; + } + /** {@inheritDoc} */ @Override protected void marshal0(@Nullable Object obj, OutputStream out) throws IgniteCheckedException { assert out != null; @@ -116,7 +134,7 @@ public class JdkMarshaller extends AbstractNodeNameAwareMarshaller { ObjectInputStream objIn = null; try { - objIn = new JdkMarshallerObjectInputStream(new JdkMarshallerInputStreamWrapper(in), clsLdr); + objIn = new JdkMarshallerObjectInputStream(new JdkMarshallerInputStreamWrapper(in), clsLdr, clsFilter); return (T)objIn.readObject(); } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshallerObjectInputStream.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshallerObjectInputStream.java b/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshallerObjectInputStream.java index 03256e0..d9fdd3d 100644 --- a/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshallerObjectInputStream.java +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/jdk/JdkMarshallerObjectInputStream.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; /** * This class defines custom JDK object input stream. @@ -30,17 +31,21 @@ class JdkMarshallerObjectInputStream extends ObjectInputStream { /** */ private final ClassLoader clsLdr; + /** Class name filter. */ + private final IgnitePredicate<String> clsFilter; + /** * @param in Parent input stream. * @param clsLdr Custom class loader. * @throws IOException If initialization failed. */ - JdkMarshallerObjectInputStream(InputStream in, ClassLoader clsLdr) throws IOException { + JdkMarshallerObjectInputStream(InputStream in, ClassLoader clsLdr, IgnitePredicate<String> clsFilter) throws IOException { super(in); assert clsLdr != null; this.clsLdr = clsLdr; + this.clsFilter = clsFilter; enableResolveObject(true); } @@ -51,7 +56,7 @@ class JdkMarshallerObjectInputStream extends ObjectInputStream { // Must have 'Class.forName()' instead of clsLoader.loadClass() // due to weird ClassNotFoundExceptions for arrays of classes // in certain cases. - return U.forName(desc.getName(), clsLdr); + return U.forName(desc.getName(), clsLdr, clsFilter); } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java index 713bbab..34b8fdf 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java @@ -55,6 +55,7 @@ import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.AddressResolver; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteInterruptedCheckedException; +import org.apache.ignite.internal.IgniteKernal; import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.X; @@ -333,7 +334,7 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter implements DiscoverySpi { protected volatile long gridStartTime; /** Marshaller. */ - private final Marshaller marsh = new JdkMarshaller(); + private Marshaller marsh; /** Statistics. */ protected final TcpDiscoveryStatistics stats = new TcpDiscoveryStatistics(); @@ -551,6 +552,11 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter implements DiscoverySpi { if (ignite != null) { setLocalAddress(ignite.configuration().getLocalHost()); setAddressResolver(ignite.configuration().getAddressResolver()); + + if (ignite instanceof IgniteKernal) // IgniteMock instance can be injected from tests. + marsh = new JdkMarshaller(((IgniteKernal)ignite).context().classNameFilter()); + else + marsh = new JdkMarshaller(); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/stream/StreamAdapter.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/stream/StreamAdapter.java b/modules/core/src/main/java/org/apache/ignite/stream/StreamAdapter.java index 3f1dfad..49f0c15 100644 --- a/modules/core/src/main/java/org/apache/ignite/stream/StreamAdapter.java +++ b/modules/core/src/main/java/org/apache/ignite/stream/StreamAdapter.java @@ -46,7 +46,7 @@ public abstract class StreamAdapter<T, K, V> { private IgniteDataStreamer<K, V> stmr; /** Ignite. */ - private Ignite ignite; + protected Ignite ignite; /** * Empty constructor. http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/main/java/org/apache/ignite/stream/socket/SocketStreamer.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/stream/socket/SocketStreamer.java b/modules/core/src/main/java/org/apache/ignite/stream/socket/SocketStreamer.java index f45423b..827c8d0 100644 --- a/modules/core/src/main/java/org/apache/ignite/stream/socket/SocketStreamer.java +++ b/modules/core/src/main/java/org/apache/ignite/stream/socket/SocketStreamer.java @@ -24,6 +24,7 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; +import org.apache.ignite.internal.IgniteKernal; import org.apache.ignite.internal.util.nio.GridBufferedParser; import org.apache.ignite.internal.util.nio.GridDelimitedParser; import org.apache.ignite.internal.util.nio.GridNioCodecFilter; @@ -38,6 +39,7 @@ import org.apache.ignite.internal.util.typedef.internal.A; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.marshaller.Marshaller; import org.apache.ignite.marshaller.MarshallerUtils; +import org.apache.ignite.marshaller.jdk.JdkMarshaller; import org.apache.ignite.stream.StreamAdapter; import org.apache.ignite.stream.StreamTupleExtractor; import org.jetbrains.annotations.Nullable; @@ -217,7 +219,7 @@ public class SocketStreamer<T, K, V> extends StreamAdapter<T, K, V> { /** * Converts message to Java object using Jdk marshaller. */ - private static class DefaultConverter<T> implements SocketMessageConverter<T> { + private class DefaultConverter<T> implements SocketMessageConverter<T> { /** Marshaller. */ private final Marshaller marsh; @@ -227,7 +229,9 @@ public class SocketStreamer<T, K, V> extends StreamAdapter<T, K, V> { * @param igniteInstanceName Ignite instance name. */ private DefaultConverter(@Nullable String igniteInstanceName) { - marsh = MarshallerUtils.jdkMarshaller(igniteInstanceName); + marsh = new JdkMarshaller(((IgniteKernal)ignite).context().classNameFilter()); + + MarshallerUtils.setNodeName(marsh, igniteInstanceName); } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/config/class_list_exploit_excluded.txt ---------------------------------------------------------------------- diff --git a/modules/core/src/test/config/class_list_exploit_excluded.txt b/modules/core/src/test/config/class_list_exploit_excluded.txt new file mode 100644 index 0000000..d2657e8 --- /dev/null +++ b/modules/core/src/test/config/class_list_exploit_excluded.txt @@ -0,0 +1,18 @@ +# +# 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. +# + +# Intentionally empty. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/config/class_list_exploit_included.txt ---------------------------------------------------------------------- diff --git a/modules/core/src/test/config/class_list_exploit_included.txt b/modules/core/src/test/config/class_list_exploit_included.txt new file mode 100644 index 0000000..9a07d54 --- /dev/null +++ b/modules/core/src/test/config/class_list_exploit_included.txt @@ -0,0 +1,19 @@ +# +# 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. +# + +org.apache.ignite.spi.discovery.tcp.DiscoveryUnmarshalVulnerabilityTest$Exploit +org.apache.ignite.stream.socket.SocketStreamerUnmarshalVulnerabilityTest$Exploit \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/internal/ClassSetTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/ClassSetTest.java b/modules/core/src/test/java/org/apache/ignite/internal/ClassSetTest.java new file mode 100644 index 0000000..c51957a --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/ClassSetTest.java @@ -0,0 +1,71 @@ +/* + * 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.ignite.internal; + +import junit.framework.TestCase; + +/** + * Tests for {@link ClassSet} class. + */ +public class ClassSetTest extends TestCase { + /** + * @throws Exception If failed. + */ + public void testAddAndContains() throws Exception { + ClassSet clsSet = new ClassSet(); + + clsSet.add("org.apache.ignite.Ignite"); + + assertTrue(clsSet.contains("org.apache.ignite.Ignite")); + assertFalse(clsSet.contains("org.apache.ignite.NotIgnite")); + assertFalse(clsSet.contains("org.apache.Ignite")); + } + + /** + * @throws Exception If failed. + */ + public void testAddWithMaskAndContains() throws Exception { + ClassSet clsSet = new ClassSet(); + + clsSet.add("org.apache.ignite.*"); + + assertTrue(clsSet.contains("org.apache.ignite.Ignite")); + assertTrue(clsSet.contains("org.apache.ignite.NotIgnite")); + assertFalse(clsSet.contains("org.apache.Ignite")); + } + + /** + * @throws Exception If failed. + */ + public void testReduceOnAddWithMask() throws Exception { + ClassSet clsSet = new ClassSet(); + + clsSet.add("org.apache.ignite.Ignite"); + clsSet.add("org.apache.ignite.Ignition"); + + assertTrue(clsSet.contains("org.apache.ignite.Ignite")); + assertTrue(clsSet.contains("org.apache.ignite.Ignition")); + assertFalse(clsSet.contains("org.apache.ignite.NotIgnite")); + + clsSet.add("org.apache.ignite.*"); + + assertTrue(clsSet.contains("org.apache.ignite.Ignite")); + assertTrue(clsSet.contains("org.apache.ignite.Ignition")); + assertTrue(clsSet.contains("org.apache.ignite.NotIgnite")); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java new file mode 100644 index 0000000..448c9af --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java @@ -0,0 +1,180 @@ +/* + * 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.ignite.spi.discovery.tcp; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.util.IgniteUtils; +import org.apache.ignite.internal.util.lang.GridAbsPredicate; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.marshaller.jdk.JdkMarshaller; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.IgniteSystemProperties.IGNITE_MARSHALLER_BLACKLIST; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_MARSHALLER_WHITELIST; + +/** + * Tests for whitelist and blacklist ot avoiding deserialization vulnerability. + */ +public class DiscoveryUnmarshalVulnerabilityTest extends GridCommonAbstractTest { + /** Marshaller. */ + private static final JdkMarshaller MARSH = new JdkMarshaller(); + + /** Shared value. */ + private static final AtomicBoolean SHARED = new AtomicBoolean(); + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + SHARED.set(false); + + System.clearProperty(IGNITE_MARSHALLER_WHITELIST); + System.clearProperty(IGNITE_MARSHALLER_BLACKLIST); + + IgniteUtils.clearClassCache(); + } + + /** + * @throws Exception If failed. + */ + public void testNoLists() throws Exception { + testExploit(true); + } + + /** + * @throws Exception If failed. + */ + public void testWhiteListIncluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_included.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_WHITELIST, path); + + testExploit(true); + } + + /** + * @throws Exception If failed. + */ + public void testWhiteListExcluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_excluded.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_WHITELIST, path); + + testExploit(false); + } + + /** + * @throws Exception If failed. + */ + public void testBlackListIncluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_included.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_BLACKLIST, path); + + testExploit(false); + } + + /** + * @throws Exception If failed. + */ + public void testBlackListExcluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_excluded.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_BLACKLIST, path); + + testExploit(true); + } + + /** + * @throws Exception If failed. + */ + public void testBothListIncluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_included.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_WHITELIST, path); + System.setProperty(IGNITE_MARSHALLER_BLACKLIST, path); + + testExploit(false); + } + + /** + * @param positive Positive. + */ + private void testExploit(boolean positive) throws Exception { + try { + startGrid(); + + attack(marshal(new Exploit())); + + boolean res = GridTestUtils.waitForCondition(new GridAbsPredicate() { + @Override public boolean apply() { + return SHARED.get(); + } + }, 3000L); + + if (positive) + assertTrue(res); + else + assertFalse(res); + } + finally { + stopAllGrids(); + } + } + + /** + * @param obj Object. + */ + private static byte[] marshal(Object obj) throws IgniteCheckedException { + return MARSH.marshal(obj); + } + + /** + * @param data Data. + */ + private void attack(byte[] data) throws IOException { + InetAddress addr = InetAddress.getLoopbackAddress(); + + try ( + Socket sock = new Socket(addr, 47500); + OutputStream oos = new BufferedOutputStream(sock.getOutputStream()) + ) { + oos.write(U.IGNITE_HEADER); + oos.write(data); + } + } + + /** */ + private static class Exploit implements Serializable { + /** + * @param is Input stream. + */ + private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException { + SHARED.set(true); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/stream/socket/SocketStreamerUnmarshalVulnerabilityTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/stream/socket/SocketStreamerUnmarshalVulnerabilityTest.java b/modules/core/src/test/java/org/apache/ignite/stream/socket/SocketStreamerUnmarshalVulnerabilityTest.java new file mode 100644 index 0000000..dadc5b6 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/stream/socket/SocketStreamerUnmarshalVulnerabilityTest.java @@ -0,0 +1,222 @@ +/* + * 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.ignite.stream.socket; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.util.IgniteUtils; +import org.apache.ignite.internal.util.lang.GridAbsPredicate; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteBiTuple; +import org.apache.ignite.marshaller.Marshaller; +import org.apache.ignite.marshaller.jdk.JdkMarshaller; +import org.apache.ignite.stream.StreamSingleTupleExtractor; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.IgniteSystemProperties.IGNITE_MARSHALLER_BLACKLIST; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_MARSHALLER_WHITELIST; + +/** + * Tests for whitelist and blacklist ot avoiding deserialization vulnerability. + */ +public class SocketStreamerUnmarshalVulnerabilityTest extends GridCommonAbstractTest { + /** Shared value. */ + private static final AtomicBoolean SHARED = new AtomicBoolean(); + + /** Port. */ + private static int port; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + try (ServerSocket sock = new ServerSocket(0)) { + port = sock.getLocalPort(); + } + + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setCacheConfiguration(defaultCacheConfiguration()); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + SHARED.set(false); + + System.clearProperty(IGNITE_MARSHALLER_WHITELIST); + System.clearProperty(IGNITE_MARSHALLER_BLACKLIST); + + IgniteUtils.clearClassCache(); + } + + /** + * @throws Exception If failed. + */ + public void testNoLists() throws Exception { + testExploit(true); + } + + /** + * @throws Exception If failed. + */ + public void testWhiteListIncluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_included.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_WHITELIST, path); + + testExploit(true); + } + + /** + * @throws Exception If failed. + */ + public void testWhiteListExcluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_excluded.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_WHITELIST, path); + + testExploit(false); + } + + /** + * @throws Exception If failed. + */ + public void testBlackListIncluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_included.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_BLACKLIST, path); + + testExploit(false); + } + + /** + * @throws Exception If failed. + */ + public void testBlackListExcluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_excluded.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_BLACKLIST, path); + + testExploit(true); + } + + /** + * @throws Exception If failed. + */ + public void testBothListIncluded() throws Exception { + String path = U.resolveIgnitePath("modules/core/src/test/config/class_list_exploit_included.txt").getPath(); + + System.setProperty(IGNITE_MARSHALLER_WHITELIST, path); + System.setProperty(IGNITE_MARSHALLER_BLACKLIST, path); + + testExploit(false); + } + + /** + * @param positive Positive. + */ + private void testExploit(boolean positive) throws Exception { + try { + Ignite ignite = startGrid(); + + SocketStreamer<Exploit, Integer, String> sockStmr = null; + + try (IgniteDataStreamer<Integer, String> stmr = ignite.dataStreamer(DEFAULT_CACHE_NAME)) { + stmr.allowOverwrite(true); + stmr.autoFlushFrequency(10); + + sockStmr = new SocketStreamer<>(); + + sockStmr.setIgnite(ignite); + + sockStmr.setStreamer(stmr); + + sockStmr.setPort(port); + + sockStmr.setSingleTupleExtractor(new StreamSingleTupleExtractor<Exploit, Integer, String>() { + @Override public Map.Entry<Integer, String> extract(Exploit msg) { + return new IgniteBiTuple<>(1, "val"); + } + }); + + sockStmr.start(); + + try (Socket sock = new Socket(InetAddress.getLocalHost(), port); + OutputStream os = new BufferedOutputStream(sock.getOutputStream())) { + Marshaller marsh = new JdkMarshaller(); + + byte[] msg = marsh.marshal(new Exploit()); + + os.write(msg.length >>> 24); + os.write(msg.length >>> 16); + os.write(msg.length >>> 8); + os.write(msg.length); + + os.write(msg); + } + catch (IOException | IgniteCheckedException e) { + throw new IgniteException(e); + } + + boolean res = GridTestUtils.waitForCondition(new GridAbsPredicate() { + @Override public boolean apply() { + return SHARED.get(); + } + }, 3000L); + + if (positive) + assertTrue(res); + else + assertFalse(res); + } + finally { + if (sockStmr != null) + sockStmr.stop(); + } + } + finally { + stopAllGrids(); + } + } + + /** */ + private static class Exploit implements Serializable { + /** + * @param is Input stream. + */ + private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException { + SHARED.set(true); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridTestKernalContext.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridTestKernalContext.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridTestKernalContext.java index 6b39faa..cfc0abe 100644 --- a/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridTestKernalContext.java +++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridTestKernalContext.java @@ -32,6 +32,7 @@ import org.apache.ignite.internal.IgniteKernal; import org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.plugin.PluginProvider; import org.apache.ignite.testframework.GridTestUtils; @@ -78,7 +79,8 @@ public class GridTestKernalContext extends GridKernalContextImpl { null, null, null, - U.allPluginProviders() + U.allPluginProviders(), + null ); GridTestUtils.setFieldValue(grid(), "cfg", config()); @@ -132,6 +134,11 @@ public class GridTestKernalContext extends GridKernalContextImpl { } /** {@inheritDoc} */ + @Override public IgnitePredicate<String> classNameFilter() { + return super.classNameFilter(); + } + + /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridTestKernalContext.class, this, super.toString()); } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java index 1b4e2da..9fee224 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java @@ -20,6 +20,7 @@ package org.apache.ignite.testsuites; import java.util.Set; import junit.framework.TestSuite; import org.apache.ignite.GridSuppressedExceptionSelfTest; +import org.apache.ignite.internal.ClassSetTest; import org.apache.ignite.internal.ClusterGroupHostsSelfTest; import org.apache.ignite.internal.ClusterGroupSelfTest; import org.apache.ignite.internal.GridFailFastNodeFailureDetectionSelfTest; @@ -191,6 +192,8 @@ public class IgniteBasicTestSuite extends TestSuite { suite.addTestSuite(GridCleanerTest.class); + suite.addTestSuite(ClassSetTest.class); + return suite; } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java index 626875c..6e51c36 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java @@ -21,6 +21,7 @@ import junit.framework.TestSuite; import org.apache.ignite.spi.GridTcpSpiForwardingSelfTest; import org.apache.ignite.spi.discovery.AuthenticationRestartTest; import org.apache.ignite.spi.discovery.IgniteDiscoveryCacheReuseSelfTest; +import org.apache.ignite.spi.discovery.tcp.DiscoveryUnmarshalVulnerabilityTest; import org.apache.ignite.spi.discovery.tcp.IgniteClientConnectTest; import org.apache.ignite.spi.discovery.tcp.IgniteClientReconnectMassiveShutdownTest; import org.apache.ignite.spi.discovery.tcp.TcpClientDiscoveryMarshallerCheckSelfTest; @@ -111,6 +112,8 @@ public class IgniteSpiDiscoverySelfTestSuite extends TestSuite { // Disco cache reuse. suite.addTest(new TestSuite(IgniteDiscoveryCacheReuseSelfTest.class)); + suite.addTest(new TestSuite(DiscoveryUnmarshalVulnerabilityTest.class)); + return suite; } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b98a003c/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteStreamSelfTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteStreamSelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteStreamSelfTestSuite.java index d7c677c..9eac277 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteStreamSelfTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteStreamSelfTestSuite.java @@ -19,6 +19,7 @@ package org.apache.ignite.testsuites; import junit.framework.TestSuite; import org.apache.ignite.stream.socket.SocketStreamerSelfTest; +import org.apache.ignite.stream.socket.SocketStreamerUnmarshalVulnerabilityTest; /** * Stream test suite. @@ -32,6 +33,7 @@ public class IgniteStreamSelfTestSuite extends TestSuite { TestSuite suite = new TestSuite("Ignite Stream Test Suite"); suite.addTest(new TestSuite(SocketStreamerSelfTest.class)); + suite.addTest(new TestSuite(SocketStreamerUnmarshalVulnerabilityTest.class)); return suite; }
