mirror of
https://github.com/corda/corda.git
synced 2024-12-20 21:43:14 +00:00
Blacklist implementation for internal Kryo
Blacklist support for internal Kryo, supporting inheritance and forciblyAllowed classes.
This commit is contained in:
parent
a9b794ace5
commit
56bad3a9b4
@ -0,0 +1,125 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
import sun.misc.Unsafe
|
||||
import sun.security.util.Password
|
||||
import java.io.*
|
||||
import java.lang.invoke.*
|
||||
import java.lang.reflect.*
|
||||
import java.net.*
|
||||
import java.security.*
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
import java.util.logging.Handler
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
/**
|
||||
* This is a [ClassWhitelist] implementation where everything is whitelisted except for blacklisted classes and interfaces.
|
||||
* In practice, as flows are arbitrary code in which it is convenient to do many things,
|
||||
* we can often end up pulling in a lot of objects that do not make sense to put in a checkpoint.
|
||||
* Thus, by blacklisting classes/interfaces we don't expect to be serialised, we can better handle/monitor the aforementioned behaviour.
|
||||
* Inheritance works for blacklisted items, but one can specifically exclude classes from blacklisting as well.
|
||||
*/
|
||||
object AllButBlacklisted : ClassWhitelist {
|
||||
|
||||
private val blacklistedClasses = hashSetOf<String>(
|
||||
|
||||
// Known blacklisted classes.
|
||||
Thread::class.java.name,
|
||||
HashSet::class.java.name,
|
||||
HashMap::class.java.name,
|
||||
ClassLoader::class.java.name,
|
||||
Handler::class.java.name, // MemoryHandler, StreamHandler
|
||||
Runtime::class.java.name,
|
||||
Unsafe::class.java.name,
|
||||
ZipFile::class.java.name,
|
||||
Provider::class.java.name,
|
||||
SecurityManager::class.java.name,
|
||||
Random::class.java.name,
|
||||
|
||||
// Known blacklisted interfaces.
|
||||
Connection::class.java.name,
|
||||
// TODO: AutoCloseable::class.java.name,
|
||||
|
||||
// java.security.
|
||||
KeyStore::class.java.name,
|
||||
Password::class.java.name,
|
||||
AccessController::class.java.name,
|
||||
Permission::class.java.name,
|
||||
|
||||
// java.net.
|
||||
DatagramSocket::class.java.name,
|
||||
ServerSocket::class.java.name,
|
||||
Socket::class.java.name,
|
||||
URLConnection::class.java.name,
|
||||
// TODO: add more from java.net.
|
||||
|
||||
// java.io.
|
||||
Console::class.java.name,
|
||||
File::class.java.name,
|
||||
FileDescriptor::class.java.name,
|
||||
FilePermission::class.java.name,
|
||||
RandomAccessFile::class.java.name,
|
||||
Reader::class.java.name,
|
||||
Writer::class.java.name,
|
||||
// TODO: add more from java.io.
|
||||
|
||||
// java.lang.invoke classes.
|
||||
CallSite::class.java.name, // for all CallSites eg MutableCallSite, VolatileCallSite etc.
|
||||
LambdaMetafactory::class.java.name,
|
||||
MethodHandle::class.java.name,
|
||||
MethodHandleProxies::class.java.name,
|
||||
MethodHandles::class.java.name,
|
||||
MethodHandles.Lookup::class.java.name,
|
||||
MethodType::class.java.name,
|
||||
SerializedLambda::class.java.name,
|
||||
SwitchPoint::class.java.name,
|
||||
|
||||
// java.lang.invoke interfaces.
|
||||
MethodHandleInfo::class.java.name,
|
||||
|
||||
// java.lang.invoke exceptions.
|
||||
LambdaConversionException::class.java.name,
|
||||
WrongMethodTypeException::class.java.name,
|
||||
|
||||
// java.lang.reflect.
|
||||
AccessibleObject::class.java.name, // For Executable, Field, Method, Constructor.
|
||||
Modifier::class.java.name,
|
||||
Parameter::class.java.name,
|
||||
ReflectPermission::class.java.name
|
||||
// TODO: add more from java.lang.reflect.
|
||||
)
|
||||
|
||||
// Specifically exclude classes from the blacklist,
|
||||
// even if any of their superclasses and/or implemented interfaces are blacklisted.
|
||||
private val forciblyAllowedClasses = hashSetOf<String>(
|
||||
LinkedHashSet::class.java.name,
|
||||
LinkedHashMap::class.java.name,
|
||||
InputStream::class.java.name,
|
||||
BufferedInputStream::class.java.name,
|
||||
Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream").name
|
||||
)
|
||||
|
||||
/**
|
||||
* This implementation supports inheritance; thus, if a superclass or superinterface is blacklisted, so is the input class.
|
||||
*/
|
||||
override fun hasListed(type: Class<*>): Boolean {
|
||||
// Check if excluded.
|
||||
if (type.name !in forciblyAllowedClasses) {
|
||||
// Check if listed.
|
||||
if (type.name in blacklistedClasses)
|
||||
throw IllegalStateException("Class ${type.name} is blacklisted, so it cannot be used in serialization.")
|
||||
// Inheritance check.
|
||||
else {
|
||||
val aMatch = blacklistedClasses.firstOrNull { Class.forName(it).isAssignableFrom(type) }
|
||||
if (aMatch != null) {
|
||||
// TODO: blacklistedClasses += type.name // add it, so checking is faster next time we encounter this class.
|
||||
val matchType = if (Class.forName(aMatch).isInterface) "superinterface" else "superclass"
|
||||
throw IllegalStateException("The $matchType $aMatch of ${type.name} is blacklisted, so it cannot be used in serialization.")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -25,6 +25,10 @@ fun makeNoWhitelistClassResolver(): ClassResolver {
|
||||
return CordaClassResolver(AllWhitelist)
|
||||
}
|
||||
|
||||
fun makeAllButBlacklistedClassResolver(): ClassResolver {
|
||||
return CordaClassResolver(AllButBlacklisted)
|
||||
}
|
||||
|
||||
class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver() {
|
||||
/** Returns the registration for the specified class, or null if the class is not registered. */
|
||||
override fun getRegistration(type: Class<*>): Registration? {
|
||||
@ -55,8 +59,9 @@ class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver()
|
||||
return checkClass(type.superclass)
|
||||
}
|
||||
// It's safe to have the Class already, since Kryo loads it with initialisation off.
|
||||
val hasAnnotation = checkForAnnotation(type)
|
||||
if (!hasAnnotation && !whitelist.hasListed(type)) {
|
||||
// If we use a whitelist with blacklisting capabilities, whitelist.hasListed(type) may throw a NotSerializableException if input class is blacklisted.
|
||||
// Thus, blacklisting precedes annotation checking.
|
||||
if (!whitelist.hasListed(type) && !checkForAnnotation(type)) {
|
||||
throw KryoException("Class ${Util.className(type)} is not annotated or on the whitelist, so cannot be used in serialization")
|
||||
}
|
||||
return null
|
||||
|
@ -33,7 +33,6 @@ import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -460,7 +459,7 @@ object KotlinObjectSerializer : Serializer<DeserializeAsKotlinObjectDef>() {
|
||||
}
|
||||
|
||||
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
|
||||
private val internalKryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeNoWhitelistClassResolver())) }.build()
|
||||
private val internalKryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeAllButBlacklistedClassResolver())) }.build()
|
||||
private val kryoPool = KryoPool.Builder { DefaultKryoCustomizer.customize(CordaKryo(makeStandardClassResolver())) }.build()
|
||||
|
||||
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
|
||||
|
@ -8,7 +8,12 @@ import net.corda.core.node.AttachmentClassLoaderTests
|
||||
import net.corda.core.node.AttachmentsClassLoader
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.testing.node.MockAttachmentStorage
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import java.lang.IllegalStateException
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
|
||||
@CordaSerializable
|
||||
enum class Foo {
|
||||
@ -160,4 +165,76 @@ class CordaClassResolverTests {
|
||||
CordaClassResolver(EmptyWhitelist).getRegistration(SubSubElement::class.java)
|
||||
CordaClassResolver(EmptyWhitelist).getRegistration(SerializableViaSuperSubInterface::class.java)
|
||||
}
|
||||
|
||||
// Blacklist tests.
|
||||
@get:Rule
|
||||
val expectedEx = ExpectedException.none()!!
|
||||
|
||||
@Test
|
||||
fun `Check blacklisted class`() {
|
||||
expectedEx.expect(IllegalStateException::class.java)
|
||||
expectedEx.expectMessage("Class java.util.HashSet is blacklisted, so it cannot be used in serialization.")
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// HashSet is blacklisted.
|
||||
resolver.getRegistration(HashSet::class.java)
|
||||
}
|
||||
|
||||
open class SubHashSet<E> : HashSet<E>()
|
||||
@Test
|
||||
fun `Check blacklisted subclass`() {
|
||||
expectedEx.expect(IllegalStateException::class.java)
|
||||
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.core.serialization.CordaClassResolverTests\$SubHashSet is blacklisted, so it cannot be used in serialization.")
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// SubHashSet extends the blacklisted HashSet.
|
||||
resolver.getRegistration(SubHashSet::class.java)
|
||||
}
|
||||
|
||||
class SubSubHashSet<E> : SubHashSet<E>()
|
||||
@Test
|
||||
fun `Check blacklisted subsubclass`() {
|
||||
expectedEx.expect(IllegalStateException::class.java)
|
||||
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.core.serialization.CordaClassResolverTests\$SubSubHashSet is blacklisted, so it cannot be used in serialization.")
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// SubSubHashSet extends SubHashSet, which extends the blacklisted HashSet.
|
||||
resolver.getRegistration(SubSubHashSet::class.java)
|
||||
}
|
||||
|
||||
class ConnectionImpl(val connection: Connection) : Connection by connection
|
||||
@Test
|
||||
fun `Check blacklisted interface impl`() {
|
||||
expectedEx.expect(IllegalStateException::class.java)
|
||||
expectedEx.expectMessage("The superinterface java.sql.Connection of net.corda.core.serialization.CordaClassResolverTests\$ConnectionImpl is blacklisted, so it cannot be used in serialization.")
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// ConnectionImpl implements blacklisted Connection.
|
||||
resolver.getRegistration(ConnectionImpl::class.java)
|
||||
}
|
||||
|
||||
interface SubConnection : Connection
|
||||
class SubConnectionImpl(val subConnection: SubConnection) : SubConnection by subConnection
|
||||
@Test
|
||||
fun `Check blacklisted super-interface impl`() {
|
||||
expectedEx.expect(IllegalStateException::class.java)
|
||||
expectedEx.expectMessage("The superinterface java.sql.Connection of net.corda.core.serialization.CordaClassResolverTests\$SubConnectionImpl is blacklisted, so it cannot be used in serialization.")
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// SubConnectionImpl implements SubConnection, which extends the blacklisted Connection.
|
||||
resolver.getRegistration(SubConnectionImpl::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Check forcibly allowed`() {
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// LinkedHashSet is allowed for serialization.
|
||||
resolver.getRegistration(LinkedHashSet::class.java)
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
class CordaSerializableHashSet<E> : HashSet<E>()
|
||||
@Test
|
||||
fun `Check blacklist precedes CordaSerializable`() {
|
||||
expectedEx.expect(IllegalStateException::class.java)
|
||||
expectedEx.expectMessage("The superclass java.util.HashSet of net.corda.core.serialization.CordaClassResolverTests\$CordaSerializableHashSet is blacklisted, so it cannot be used in serialization.")
|
||||
val resolver = CordaClassResolver(AllButBlacklisted)
|
||||
// CordaSerializableHashSet is @CordaSerializable, but extends the blacklisted HashSet.
|
||||
resolver.getRegistration(CordaSerializableHashSet::class.java)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user