The server custom field serializer for EnumMap instantiates the map from an
untyped readObject(), unlike every other collection serializer in the RPC
core, which read what they instantiate from with a type check.
user/src/com/google/gwt/user/server/rpc/core/java/util/EnumMap_ServerCustomFieldSerializer.java
overrides the type-checking server instantiate but discards the type
information it is given and calls the client serializer:
public EnumMap instantiateInstance(ServerSerializationStreamReader streamReader,
Type[] expectedParameterTypes, DequeMap<TypeVariable<?>, Type> resolvedTypes)
throws SerializationException {
return EnumMap_CustomFieldSerializer.instantiate(streamReader); // untyped
}
EnumMap_CustomFieldSerializer.instantiate then does:
Object exemplar = streamReader.readObject(); // no expected type
Class clazz = exemplar.getClass();
return new EnumMap(clazz); // expects an enum class
Compare TreeMap_ServerCustomFieldSerializer / TreeSet_ServerCustomFieldSerializer,
which read their comparator through the typed path
streamReader.readObject(Comparator.class, resolvedTypes).
Because the exemplar is read untyped, the server takes the
deserialize(signature, null, null) path, where the
SerializedTypeViolationException check (guarded by expectedType != null) does
not run. An RPC message can therefore substitute an allow-listed non-enum
type for the exemplar, and new EnumMap(nonEnumClass) throws
NullPointerException: Cannot read the array length because "this.keyUniverse" is null, which is not a SerializationException and is not caught, so the
call fails with an unexpected server error (HTTP 500) instead of a clean
deserialization failure.
The ServerCustomFieldSerializer javadoc states this is the serializer's own
responsibility:
It is this method's responsibility to verify the types of objects that it
reads. ... In practice, any call to ServerSerializationStreamReader.readObject()
should use the type checking version, passing in the expected type of the
object to be read. See the built-in GWT server custom field serializers for
examples.
Reproduction
Any RPC service with an EnumMap parameter is affected. With a service method
void store(EnumMap settings) and a serialization policy that allow-lists the
service's ordinary types (EnumMap, its enum key type, Integer, String), an
unauthenticated POST whose EnumMap exemplar is an allow-listed Integer instead
of an enum constant returns HTTP 500, and the server log shows the uncaught
NullPointerException. With the exemplar read through the type-checking path the
same request is rejected cleanly.
The existing type-check test family
(RPCTypeCheckTest / RPCTypeCheckCollectionsTest / RPCTypeCheckArraysTest)
covers every other collection serializer for exactly this substitution
(testMapHashSetSpoofingMap, testVectorHashSetSpoofingVector, and so on) but
has no case for EnumMap.
I have a fix that mirrors TreeMap/TreeSet plus a test in that family; I will
open a PR referencing this issue.
The server custom field serializer for
EnumMapinstantiates the map from anuntyped
readObject(), unlike every other collection serializer in the RPCcore, which read what they instantiate from with a type check.
user/src/com/google/gwt/user/server/rpc/core/java/util/EnumMap_ServerCustomFieldSerializer.javaoverrides the type-checking server instantiate but discards the type
information it is given and calls the client serializer:
EnumMap_CustomFieldSerializer.instantiatethen does:Compare
TreeMap_ServerCustomFieldSerializer/TreeSet_ServerCustomFieldSerializer,which read their comparator through the typed path
streamReader.readObject(Comparator.class, resolvedTypes).Because the exemplar is read untyped, the server takes the
deserialize(signature, null, null)path, where theSerializedTypeViolationExceptioncheck (guarded byexpectedType != null) doesnot run. An RPC message can therefore substitute an allow-listed non-enum
type for the exemplar, and
new EnumMap(nonEnumClass)throwsNullPointerException: Cannot read the array length because "this.keyUniverse" is null, which is not aSerializationExceptionand is not caught, so thecall fails with an unexpected server error (HTTP 500) instead of a clean
deserialization failure.
The
ServerCustomFieldSerializerjavadoc states this is the serializer's ownresponsibility:
Reproduction
Any RPC service with an
EnumMapparameter is affected. With a service methodvoid store(EnumMap settings)and a serialization policy that allow-lists theservice's ordinary types (
EnumMap, its enum key type,Integer,String), anunauthenticated POST whose EnumMap exemplar is an allow-listed
Integerinsteadof an enum constant returns HTTP 500, and the server log shows the uncaught
NullPointerException. With the exemplar read through the type-checking path thesame request is rejected cleanly.
The existing type-check test family
(
RPCTypeCheckTest/RPCTypeCheckCollectionsTest/RPCTypeCheckArraysTest)covers every other collection serializer for exactly this substitution
(
testMapHashSetSpoofingMap,testVectorHashSetSpoofingVector, and so on) buthas no case for
EnumMap.I have a fix that mirrors
TreeMap/TreeSetplus a test in that family; I willopen a PR referencing this issue.