mirror of
https://github.com/corda/corda.git
synced 2025-01-02 03:06:45 +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)
|
return CordaClassResolver(AllWhitelist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun makeAllButBlacklistedClassResolver(): ClassResolver {
|
||||||
|
return CordaClassResolver(AllButBlacklisted)
|
||||||
|
}
|
||||||
|
|
||||||
class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver() {
|
class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver() {
|
||||||
/** Returns the registration for the specified class, or null if the class is not registered. */
|
/** Returns the registration for the specified class, or null if the class is not registered. */
|
||||||
override fun getRegistration(type: Class<*>): Registration? {
|
override fun getRegistration(type: Class<*>): Registration? {
|
||||||
@ -55,8 +59,9 @@ class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver()
|
|||||||
return checkClass(type.superclass)
|
return checkClass(type.superclass)
|
||||||
}
|
}
|
||||||
// It's safe to have the Class already, since Kryo loads it with initialisation off.
|
// It's safe to have the Class already, since Kryo loads it with initialisation off.
|
||||||
val hasAnnotation = checkForAnnotation(type)
|
// If we use a whitelist with blacklisting capabilities, whitelist.hasListed(type) may throw a NotSerializableException if input class is blacklisted.
|
||||||
if (!hasAnnotation && !whitelist.hasListed(type)) {
|
// 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")
|
throw KryoException("Class ${Util.className(type)} is not annotated or on the whitelist, so cannot be used in serialization")
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -33,7 +33,6 @@ import java.security.PrivateKey
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.cert.CertPath
|
import java.security.cert.CertPath
|
||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.security.spec.InvalidKeySpecException
|
import java.security.spec.InvalidKeySpecException
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
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.
|
// 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()
|
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.
|
// 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.AttachmentsClassLoader
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.testing.node.MockAttachmentStorage
|
import net.corda.testing.node.MockAttachmentStorage
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.ExpectedException
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
enum class Foo {
|
enum class Foo {
|
||||||
@ -160,4 +165,76 @@ class CordaClassResolverTests {
|
|||||||
CordaClassResolver(EmptyWhitelist).getRegistration(SubSubElement::class.java)
|
CordaClassResolver(EmptyWhitelist).getRegistration(SubSubElement::class.java)
|
||||||
CordaClassResolver(EmptyWhitelist).getRegistration(SerializableViaSuperSubInterface::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