Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,26 @@ public EnumMap instantiateInstance(SerializationStreamReader streamReader)
return EnumMap_CustomFieldSerializer.instantiate(streamReader);
}

/**
* The key type of an {@link EnumMap} is always an enum. Read the exemplar
* object with a type check, as the other server custom field serializers
* do, so that a non-enum type cannot be substituted for it. Without the
* check the substituted type reaches {@code new EnumMap(nonEnumClass)},
* which throws an uncaught {@link NullPointerException} rather than a clean
* serialization error.
*/
@SuppressWarnings("unused")
public static EnumMap instantiate(ServerSerializationStreamReader streamReader,
Type[] expectedParameterTypes, DequeMap<TypeVariable< ? >, Type> resolvedTypes)
throws SerializationException {
Object exemplar = streamReader.readObject(Enum.class, resolvedTypes);
return new EnumMap(exemplar.getClass());
}
@Override
public EnumMap instantiateInstance(ServerSerializationStreamReader streamReader,
Type[] expectedParameterTypes, DequeMap<TypeVariable< ? >, Type> resolvedTypes)
throws SerializationException {
return EnumMap_CustomFieldSerializer.instantiate(streamReader);
return instantiate(streamReader, expectedParameterTypes, resolvedTypes);
}

@Override
Expand Down
11 changes: 11 additions & 0 deletions user/test/com/google/gwt/user/server/rpc/RPCTypeCheckFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,17 @@ public String toString() {
/**
* Add data for an int object.
*/
/**
* Writes an EnumMap whose exemplar object (the value from which the server
* derives the enum key type) is a plain Integer rather than an enum constant.
*/
public void writeEnumMapWithSpoofedExemplar(int spoofedExemplar)
throws SerializationException {
writeStringFromTable(generateSerializedClassString(java.util.EnumMap.class));
write(Integer.valueOf(spoofedExemplar));
bodyString += "0" + RPC_SEPARATOR_CHAR; // empty map body
}

public void write(int integer) throws SerializationException {
try {
bodyString += Integer.toString(integer) + RPC_SEPARATOR_CHAR;
Expand Down
39 changes: 39 additions & 0 deletions user/test/com/google/gwt/user/server/rpc/RPCTypeCheckTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ public static class ClassesParamTestClass<T extends List<Integer> & MInterface<S
public static void testAClass(AClass arg1) {
}

@SuppressWarnings({"unused", "rawtypes"})
public static void testEnumMap(java.util.EnumMap arg1) {
}

@SuppressWarnings("unused")
public static void testAClassArray(AClass[] arg1) {
}
Expand Down Expand Up @@ -548,6 +552,23 @@ public static void testIntString(int arg1, String arg2) {
}
}

private static String generateEnumMapSpoofingClass() {
try {
RPCTypeCheckFactory strFactory =
new RPCTypeCheckFactory(ClassesParamTestClass.class, "testEnumMap");

// The exemplar the server reads to derive the enum key type is replaced
// with a plain Integer, an allowlisted non-enum type.
strFactory.writeEnumMapWithSpoofedExemplar(12345);

return strFactory.toString();
} catch (Exception e) {
fail(e.getMessage());

return null;
}
}

private static String generateArrayListSpoofingClass() {
try {
RPCTypeCheckFactory strFactory =
Expand Down Expand Up @@ -2250,6 +2271,24 @@ public void testEmptyListSpoofingClass() {
* This checks that a Map generated by Collections.emptyMap correctly reports
* that it is an incorrect type.
*/
/**
* The EnumMap server custom field serializer instantiates its map from an
* untyped readObject(), so an attacker can substitute a non-enum type for the
* exemplar object from which the key type is derived. Every other collection
* serializer type-checks what it reads; this one does not, so the substituted
* type reaches new EnumMap(nonEnumClass) and the server throws an uncaught
* NullPointerException instead of a clean serialization error.
*/
public void testEnumMapSpoofingClass() {
try {
RPC.decodeRequest(generateEnumMapSpoofingClass());
fail("Expected IncompatibleRemoteServiceException from testEnumMapSpoofingClass");
} catch (IncompatibleRemoteServiceException e) {
// Expected: a clean type-violation, not an uncaught NullPointerException
assertEquals(SerializedTypeViolationException.class, e.getCause().getClass());
}
}

public void testEmptyMapSpoofingClass() {
try {
RPC.decodeRequest(generateEmptyMapSpoofingClass());
Expand Down