Skip to content

Commit

Permalink
[hazelcast#15346] Fix deserialization filtering for Externalizables a…
Browse files Browse the repository at this point in the history
…nd Deadlock in Map index
  • Loading branch information
kwart committed Jul 23, 2019
1 parent 933cfa9 commit 162d1b1
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2008-2019, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.client.serialization;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.test.TestHazelcastFactory;
import com.hazelcast.config.Config;
import com.hazelcast.config.JavaSerializationFilterConfig;
import com.hazelcast.config.MapIndexConfig;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.nio.serialization.HazelcastSerializationException;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.QuickTest;

import example.serialization.TestExternalizableDeserialized;

/**
* Tests untrusted deserialization protection for Externalizables.
*
* <pre>
* Given: Hazelcast member and clients are started.
* </pre>
*/
@RunWith(HazelcastSerialClassRunner.class)
@Category(QuickTest.class)
public class ExternalizableDeserializationProtectionTest extends HazelcastTestSupport {

@Rule
public ExpectedException expected = ExpectedException.none();

protected static TestHazelcastFactory hazelcastFactory = new TestHazelcastFactory();

@AfterClass
public static final void stopHazelcastInstances() {
hazelcastFactory.terminateAll();
}

@Before
public void killAllHazelcastInstances() throws IOException {
hazelcastFactory.terminateAll();
TestExternalizableDeserialized.isDeserialized = false;
}

@Test
public void testExternalizableProtectedOnMember() {
JavaSerializationFilterConfig javaSerializationFilterConfig = new JavaSerializationFilterConfig()
.setDefaultsDisabled(true);
javaSerializationFilterConfig.getBlacklist().addClasses(TestExternalizableDeserialized.class.getName());

Config config = smallInstanceConfig();
config.getSerializationConfig().setJavaSerializationFilterConfig(javaSerializationFilterConfig);
// the index will force deserialization
config.getMapConfig("test").addMapIndexConfig(new MapIndexConfig("name", false));
hazelcastFactory.newHazelcastInstance(config);

HazelcastInstance client = hazelcastFactory.newHazelcastClient();

expected.expect(HazelcastSerializationException.class);
client.getMap("test").put("key", new TestExternalizableDeserialized());
}

@Test
public void testExternalizableProtectedOnClient() {
JavaSerializationFilterConfig javaSerializationFilterConfig = new JavaSerializationFilterConfig()
.setDefaultsDisabled(true);
javaSerializationFilterConfig.getBlacklist().addClasses(TestExternalizableDeserialized.class.getName());

Config config = smallInstanceConfig();
hazelcastFactory.newHazelcastInstance(config);

ClientConfig clientConfig1 = new ClientConfig();
HazelcastInstance client1 = hazelcastFactory.newHazelcastClient(clientConfig1);
client1.getMap("test").put("key", new TestExternalizableDeserialized());
// we don't have an index on map, so the value should not be deserialized
assertFalse(TestExternalizableDeserialized.isDeserialized);
// deserialized on client
client1.getMap("test").get("key");
assertTrue(TestExternalizableDeserialized.isDeserialized);
TestExternalizableDeserialized.isDeserialized = false;

ClientConfig clientConfig2 = new ClientConfig();
clientConfig2.getSerializationConfig().setJavaSerializationFilterConfig(javaSerializationFilterConfig);
HazelcastInstance client2 = hazelcastFactory.newHazelcastClient(clientConfig2);
expected.expect(HazelcastSerializationException.class);
client2.getMap("test").get("key");
}

@Test
public void testExternalizableUnprotected() {
Config config = smallInstanceConfig();
config.getMapConfig("test").addMapIndexConfig(new MapIndexConfig("name", false));
hazelcastFactory.newHazelcastInstance(config);

HazelcastInstance client = hazelcastFactory.newHazelcastClient();

client.getMap("test").put("key", new TestExternalizableDeserialized());
assertTrue(TestExternalizableDeserialized.isDeserialized);
TestExternalizableDeserialized.isDeserialized = false;
client.getMap("test").get("key");
assertTrue(TestExternalizableDeserialized.isDeserialized);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ private Externalizable readGzipped(InputStream in, String className, ClassLoader
}

private Externalizable read(InputStream in, String className, ClassLoader classLoader) throws Exception {
if (classFilter != null) {
classFilter.filter(className);
}
Externalizable ds = ClassLoaderUtil.newInstance(classLoader, className);
ObjectInputStream objectInputStream = newObjectInputStream(classLoader, classFilter, in);
ds.readExternal(objectInputStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,13 @@ private static Object tryStoreIntoCache(Record record, Object valueBeforeCas, Se

//we managed to lock the record for ourselves
Object valueAfterCas = record.getValue();
Object object = serializationService.toObject(valueBeforeCas);
Object object = null;
try {
object = serializationService.toObject(valueBeforeCas);
} catch (Exception e) {
record.casCachedValue(currentThread, null);
throw e;
}
if (valueAfterCas == valueBeforeCas) {
//this check is needed to make sure a partition thread had not changed the value
//right before we won the CAS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,37 @@

package com.hazelcast.cluster;

import static com.hazelcast.nio.IOUtil.closeResource;
import static com.hazelcast.test.HazelcastTestSupport.assertTrueEventually;
import static com.hazelcast.test.HazelcastTestSupport.smallInstanceConfig;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.nio.ByteBuffer;

import org.junit.After;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import com.hazelcast.config.Config;
import com.hazelcast.config.JavaSerializationFilterConfig;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.instance.HazelcastInstanceFactory;
import com.hazelcast.internal.serialization.impl.SerializationConstants;
import com.hazelcast.nio.Packet;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.OverridePropertyRule;
import com.hazelcast.test.annotation.QuickTest;
import example.serialization.TestDeserialized;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.nio.ByteBuffer;

import static com.hazelcast.nio.IOUtil.closeResource;
import static com.hazelcast.test.OverridePropertyRule.clear;
import static org.junit.Assert.assertFalse;
import example.serialization.TestDeserialized;

/**
* Tests if deserialization blacklisting works for MulticastService.
Expand All @@ -52,9 +55,6 @@
@Category(QuickTest.class)
public class MulticastDeserializationTest {

@Rule
public final OverridePropertyRule ruleSysPropHazelcastLocalAddress = clear("hazelcast.local.localAddress");

private static final int MULTICAST_PORT = 53535;
private static final String MULTICAST_GROUP = "224.0.0.219";
// TTL = 0 : restricted to the same host, won't be output by any interface
Expand All @@ -74,23 +74,40 @@ public void tearDown() {
*/
@Test
public void test() throws Exception {
Config config = getConfig();
Config config = createConfig(true);
Hazelcast.newHazelcastInstance(config);

sendJoinDatagram(new TestDeserialized());
Thread.sleep(500L);
assertFalse("Untrusted deserialization is possible", TestDeserialized.isDeserialized);
}

private Config getConfig() {
JavaSerializationFilterConfig javaSerializationFilterConfig = new JavaSerializationFilterConfig()
.setDefaultsDisabled(true);
javaSerializationFilterConfig.getBlacklist()
.addClasses(TestDeserialized.class.getName());
@Test
public void testWithoutFilter() throws Exception {
Config config = createConfig(false);
Hazelcast.newHazelcastInstance(config);

sendJoinDatagram(new TestDeserialized());
Thread.sleep(500L);
assertTrueEventually(new AssertTask() {

@Override
public void run() throws Exception {
assertTrue("Object was not deserializaed", TestDeserialized.isDeserialized);
}
});
}

Config config = new Config();
config.getSerializationConfig()
.setJavaSerializationFilterConfig(javaSerializationFilterConfig);
private Config createConfig(boolean withFilter) {
Config config = smallInstanceConfig();
if (withFilter) {
JavaSerializationFilterConfig javaSerializationFilterConfig = new JavaSerializationFilterConfig()
.setDefaultsDisabled(true);
javaSerializationFilterConfig.getBlacklist()
.addClasses(TestDeserialized.class.getName());
config.getSerializationConfig()
.setJavaSerializationFilterConfig(javaSerializationFilterConfig);
}
JoinConfig join = config.getNetworkConfig().getJoin();
join.getTcpIpConfig()
.setEnabled(false);
Expand All @@ -114,7 +131,6 @@ private void sendJoinDatagram(Object object) throws IOException {
MulticastSocket multicastSocket = null;
try {
multicastSocket = new MulticastSocket(MULTICAST_PORT);
multicastSocket.setInterface(InetAddress.getLocalHost());
multicastSocket.setTimeToLive(MULTICAST_TTL);
InetAddress group = InetAddress.getByName(MULTICAST_GROUP);
multicastSocket.joinGroup(group);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2008-2019, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 example.serialization;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class TestExternalizableDeserialized implements Externalizable {

public static volatile boolean isDeserialized = false;

// field used for indexing in IMap
private String name = "foo";

@Override
public void writeExternal(ObjectOutput out) throws IOException {
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
isDeserialized = true;
}
}

0 comments on commit 162d1b1

Please sign in to comment.