From e6fae590cf2a758c47cd5a17f9bf3780ce62c986 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 13 Oct 2021 19:14:57 +0200 Subject: [PATCH] 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. --- .../gson/internal/LazilyParsedNumber.java | 8 +++++++ .../gson/internal/LinkedHashTreeMap.java | 8 +++++++ .../google/gson/internal/LinkedTreeMap.java | 8 +++++++ .../gson/internal/LazilyParsedNumberTest.java | 18 ++++++++++++++++ .../gson/internal/LinkedHashTreeMapTest.java | 21 +++++++++++++++++++ .../gson/internal/LinkedTreeMapTest.java | 20 ++++++++++++++++++ 6 files changed, 83 insertions(+) diff --git a/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java b/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java index 3669af7b58..6138dfff38 100644 --- a/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java +++ b/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java @@ -15,6 +15,9 @@ */ package com.google.gson.internal; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.math.BigDecimal; @@ -77,6 +80,11 @@ private Object writeReplace() throws ObjectStreamException { return new BigDecimal(value); } + private void readObject(ObjectInputStream in) throws IOException { + // Don't permit directly deserializing this class; writeReplace() should have written a replacement + throw new InvalidObjectException("Deserialization is unsupported"); + } + @Override public int hashCode() { return value.hashCode(); diff --git a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java index b2707c50da..0cade0d1f8 100644 --- a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java +++ b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java @@ -17,6 +17,9 @@ package com.google.gson.internal; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.AbstractMap; @@ -861,4 +864,9 @@ public K next() { private Object writeReplace() throws ObjectStreamException { return new LinkedHashMap(this); } + + private void readObject(ObjectInputStream in) throws IOException { + // Don't permit directly deserializing this class; writeReplace() should have written a replacement + throw new InvalidObjectException("Deserialization is unsupported"); + } } diff --git a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java index 80462742e3..aaa8ce0918 100644 --- a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java +++ b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java @@ -17,6 +17,9 @@ package com.google.gson.internal; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.AbstractMap; @@ -627,4 +630,9 @@ public K next() { private Object writeReplace() throws ObjectStreamException { return new LinkedHashMap(this); } + + private void readObject(ObjectInputStream in) throws IOException { + // Don't permit directly deserializing this class; writeReplace() should have written a replacement + throw new InvalidObjectException("Deserialization is unsupported"); + } } diff --git a/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java b/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java index f108fa0de8..75e77bb55f 100644 --- a/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java +++ b/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java @@ -15,6 +15,13 @@ */ package com.google.gson.internal; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; + import junit.framework.TestCase; public class LazilyParsedNumberTest extends TestCase { @@ -29,4 +36,15 @@ public void testEquals() { LazilyParsedNumber n1Another = new LazilyParsedNumber("1"); assertTrue(n1.equals(n1Another)); } + + public void testJavaSerialization() throws IOException, ClassNotFoundException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + objOut.writeObject(new LazilyParsedNumber("123")); + objOut.close(); + + ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + Number deserialized = (Number) objIn.readObject(); + assertEquals(new BigDecimal("123"), deserialized); + } } diff --git a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java index df595b796b..65864f0c97 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java @@ -20,8 +20,15 @@ import com.google.gson.internal.LinkedHashTreeMap.AvlBuilder; import com.google.gson.internal.LinkedHashTreeMap.AvlIterator; import com.google.gson.internal.LinkedHashTreeMap.Node; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Random; @@ -224,6 +231,20 @@ public void testDoubleCapacityAllNodesOnLeft() { } } + public void testJavaSerialization() throws IOException, ClassNotFoundException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + Map map = new LinkedHashTreeMap(); + map.put("a", 1); + objOut.writeObject(map); + objOut.close(); + + ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + @SuppressWarnings("unchecked") + Map deserialized = (Map) objIn.readObject(); + assertEquals(Collections.singletonMap("a", 1), deserialized); + } + private static final Node head = new Node(); private Node node(String value) { diff --git a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java index 058de3367f..68220cf631 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java @@ -16,8 +16,14 @@ package com.google.gson.internal; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Random; @@ -140,6 +146,20 @@ public void testEqualsAndHashCode() throws Exception { MoreAsserts.assertEqualsAndHashCode(map1, map2); } + public void testJavaSerialization() throws IOException, ClassNotFoundException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + Map map = new LinkedTreeMap(); + map.put("a", 1); + objOut.writeObject(map); + objOut.close(); + + ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + @SuppressWarnings("unchecked") + Map deserialized = (Map) objIn.readObject(); + assertEquals(Collections.singletonMap("a", 1), deserialized); + } + @SafeVarargs private void assertIterationOrder(Iterable actual, T... expected) { ArrayList actualList = new ArrayList();