Skip to content

Commit e6fae59

Browse files
authored
Prevent Java deserialization of internal classes (#1991)
Adversaries might be able to forge data which can be abused for DoS attacks. These classes are already writing a replacement JDK object during serialization for a long time, so this change should not cause any issues.
1 parent bda2e3d commit e6fae59

File tree

6 files changed

+83
-0
lines changed

6 files changed

+83
-0
lines changed

Diff for: gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package com.google.gson.internal;
1717

18+
import java.io.IOException;
19+
import java.io.InvalidObjectException;
20+
import java.io.ObjectInputStream;
1821
import java.io.ObjectStreamException;
1922
import java.math.BigDecimal;
2023

@@ -77,6 +80,11 @@ private Object writeReplace() throws ObjectStreamException {
7780
return new BigDecimal(value);
7881
}
7982

83+
private void readObject(ObjectInputStream in) throws IOException {
84+
// Don't permit directly deserializing this class; writeReplace() should have written a replacement
85+
throw new InvalidObjectException("Deserialization is unsupported");
86+
}
87+
8088
@Override
8189
public int hashCode() {
8290
return value.hashCode();

Diff for: gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
package com.google.gson.internal;
1919

20+
import java.io.IOException;
21+
import java.io.InvalidObjectException;
22+
import java.io.ObjectInputStream;
2023
import java.io.ObjectStreamException;
2124
import java.io.Serializable;
2225
import java.util.AbstractMap;
@@ -861,4 +864,9 @@ public K next() {
861864
private Object writeReplace() throws ObjectStreamException {
862865
return new LinkedHashMap<K, V>(this);
863866
}
867+
868+
private void readObject(ObjectInputStream in) throws IOException {
869+
// Don't permit directly deserializing this class; writeReplace() should have written a replacement
870+
throw new InvalidObjectException("Deserialization is unsupported");
871+
}
864872
}

Diff for: gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
package com.google.gson.internal;
1919

20+
import java.io.IOException;
21+
import java.io.InvalidObjectException;
22+
import java.io.ObjectInputStream;
2023
import java.io.ObjectStreamException;
2124
import java.io.Serializable;
2225
import java.util.AbstractMap;
@@ -627,4 +630,9 @@ public K next() {
627630
private Object writeReplace() throws ObjectStreamException {
628631
return new LinkedHashMap<K, V>(this);
629632
}
633+
634+
private void readObject(ObjectInputStream in) throws IOException {
635+
// Don't permit directly deserializing this class; writeReplace() should have written a replacement
636+
throw new InvalidObjectException("Deserialization is unsupported");
637+
}
630638
}

Diff for: gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
*/
1616
package com.google.gson.internal;
1717

18+
import java.io.ByteArrayInputStream;
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.io.ObjectInputStream;
22+
import java.io.ObjectOutputStream;
23+
import java.math.BigDecimal;
24+
1825
import junit.framework.TestCase;
1926

2027
public class LazilyParsedNumberTest extends TestCase {
@@ -29,4 +36,15 @@ public void testEquals() {
2936
LazilyParsedNumber n1Another = new LazilyParsedNumber("1");
3037
assertTrue(n1.equals(n1Another));
3138
}
39+
40+
public void testJavaSerialization() throws IOException, ClassNotFoundException {
41+
ByteArrayOutputStream out = new ByteArrayOutputStream();
42+
ObjectOutputStream objOut = new ObjectOutputStream(out);
43+
objOut.writeObject(new LazilyParsedNumber("123"));
44+
objOut.close();
45+
46+
ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
47+
Number deserialized = (Number) objIn.readObject();
48+
assertEquals(new BigDecimal("123"), deserialized);
49+
}
3250
}

Diff for: gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java

+21
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,15 @@
2020
import com.google.gson.internal.LinkedHashTreeMap.AvlBuilder;
2121
import com.google.gson.internal.LinkedHashTreeMap.AvlIterator;
2222
import com.google.gson.internal.LinkedHashTreeMap.Node;
23+
24+
import java.io.ByteArrayInputStream;
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.IOException;
27+
import java.io.ObjectInputStream;
28+
import java.io.ObjectOutputStream;
2329
import java.util.ArrayList;
2430
import java.util.Arrays;
31+
import java.util.Collections;
2532
import java.util.Iterator;
2633
import java.util.Map;
2734
import java.util.Random;
@@ -224,6 +231,20 @@ public void testDoubleCapacityAllNodesOnLeft() {
224231
}
225232
}
226233

234+
public void testJavaSerialization() throws IOException, ClassNotFoundException {
235+
ByteArrayOutputStream out = new ByteArrayOutputStream();
236+
ObjectOutputStream objOut = new ObjectOutputStream(out);
237+
Map<String, Integer> map = new LinkedHashTreeMap<String, Integer>();
238+
map.put("a", 1);
239+
objOut.writeObject(map);
240+
objOut.close();
241+
242+
ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
243+
@SuppressWarnings("unchecked")
244+
Map<String, Integer> deserialized = (Map<String, Integer>) objIn.readObject();
245+
assertEquals(Collections.singletonMap("a", 1), deserialized);
246+
}
247+
227248
private static final Node<String, String> head = new Node<String, String>();
228249

229250
private Node<String, String> node(String value) {

Diff for: gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java

+20
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616

1717
package com.google.gson.internal;
1818

19+
import java.io.ByteArrayInputStream;
20+
import java.io.ByteArrayOutputStream;
21+
import java.io.IOException;
22+
import java.io.ObjectInputStream;
23+
import java.io.ObjectOutputStream;
1924
import java.util.ArrayList;
2025
import java.util.Arrays;
26+
import java.util.Collections;
2127
import java.util.Iterator;
2228
import java.util.Map;
2329
import java.util.Random;
@@ -140,6 +146,20 @@ public void testEqualsAndHashCode() throws Exception {
140146
MoreAsserts.assertEqualsAndHashCode(map1, map2);
141147
}
142148

149+
public void testJavaSerialization() throws IOException, ClassNotFoundException {
150+
ByteArrayOutputStream out = new ByteArrayOutputStream();
151+
ObjectOutputStream objOut = new ObjectOutputStream(out);
152+
Map<String, Integer> map = new LinkedTreeMap<String, Integer>();
153+
map.put("a", 1);
154+
objOut.writeObject(map);
155+
objOut.close();
156+
157+
ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
158+
@SuppressWarnings("unchecked")
159+
Map<String, Integer> deserialized = (Map<String, Integer>) objIn.readObject();
160+
assertEquals(Collections.singletonMap("a", 1), deserialized);
161+
}
162+
143163
@SafeVarargs
144164
private <T> void assertIterationOrder(Iterable<T> actual, T... expected) {
145165
ArrayList<T> actualList = new ArrayList<T>();

0 commit comments

Comments
 (0)