This is an automated email from the ASF dual-hosted git repository.

jonmeredith pushed a commit to branch cassandra-4.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-4.0 by this push:
     new ae995eb3d3 NPE when deserializing malformed collections from client
ae995eb3d3 is described below

commit ae995eb3d3cc1c98f61db0d071522b6f09443927
Author: Jon Meredith <[email protected]>
AuthorDate: Tue May 9 08:54:40 2023 -0600

    NPE when deserializing malformed collections from client
    
    patch by Jon Meredith; reviewed by Caleb Rackliffe for CASSANDRA-18505
---
 CHANGES.txt                                        |  1 +
 .../serializers/CollectionSerializer.java          |  8 +++++
 .../cassandra/serializers/ListSerializer.java      |  9 ++++--
 .../cassandra/serializers/MapSerializer.java       |  8 ++---
 .../cassandra/serializers/SetSerializer.java       |  4 +--
 .../cql3/validation/entities/CollectionsTest.java  |  4 +--
 .../apache/cassandra/transport/SerDeserTest.java   | 36 ++++++++++++++++++++++
 7 files changed, 60 insertions(+), 10 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index a10b0f67df..ac053c3f5b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0.10
+ * NPE when deserializing malformed collections from client (CASSANDRA-18505)
  * Improve 'Not enough space for compaction' logging messages (CASSANDRA-18260)
  * Incremental repairs fail on mixed IPv4/v6 addresses serializing SyncRequest 
(CASSANDRA-18474)
  * Deadlock updating sstable metadata if disk boundaries need reloading 
(CASSANDRA-18443)
diff --git 
a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java 
b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
index eb2991b8d7..1722c3d109 100644
--- a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
@@ -127,6 +127,14 @@ public abstract class CollectionSerializer<T> extends 
TypeSerializer<T>
         return accessor.slice(input, offset + TypeSizes.INT_SIZE, size);
     }
 
+    public static <V> V readNonNullValue(V input, ValueAccessor<V> accessor, 
int offset, ProtocolVersion version)
+    {
+        V value = readValue(input, accessor, offset, version);
+        if (value == null)
+            throw new MarshalException("Null value read when not allowed");
+        return value;
+    }
+
     protected static void skipValue(ByteBuffer input, ProtocolVersion version)
     {
         int size = input.getInt();
diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java 
b/src/java/org/apache/cassandra/serializers/ListSerializer.java
index 240e0a62dc..35c31414e2 100644
--- a/src/java/org/apache/cassandra/serializers/ListSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java
@@ -71,7 +71,7 @@ public class ListSerializer<T> extends 
CollectionSerializer<List<T>>
             int offset = sizeOfCollectionSize(n, version);
             for (int i = 0; i < n; i++)
             {
-                V value = readValue(input, accessor, offset, version);
+                V value = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(value, accessor, version);
                 elements.validate(value, accessor);
             }
@@ -102,7 +102,12 @@ public class ListSerializer<T> extends 
CollectionSerializer<List<T>>
             List<T> l = new ArrayList<T>(Math.min(n, 256));
             for (int i = 0; i < n; i++)
             {
-                // We can have nulls in lists that are used for IN values
+                // CASSANDRA-6839: "We can have nulls in lists that are used 
for IN values"
+                // CASSANDRA-8613 checks IN clauses and throws an exception if 
null is in the list.
+                // Leaving for this as-is for now in case there is some 
unknown use
+                // for it, but should likely be changed to readNonNull. 
Validate has been
+                // changed to throw on null elements as otherwise it would 
NPE, and it's unclear
+                // if callers could handle null elements.
                 V databb = readValue(input, accessor, offset, version);
                 offset += sizeOfValue(databb, accessor, version);
                 if (databb != null)
diff --git a/src/java/org/apache/cassandra/serializers/MapSerializer.java 
b/src/java/org/apache/cassandra/serializers/MapSerializer.java
index 9eae598003..9c0a001534 100644
--- a/src/java/org/apache/cassandra/serializers/MapSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/MapSerializer.java
@@ -89,11 +89,11 @@ public class MapSerializer<K, V> extends 
CollectionSerializer<Map<K, V>>
             int offset = sizeOfCollectionSize(n, version);
             for (int i = 0; i < n; i++)
             {
-                T key = readValue(input, accessor, offset, version);
+                T key = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(key, accessor, version);
                 keys.validate(key, accessor);
 
-                T value = readValue(input, accessor, offset, version);
+                T value = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(value, accessor, version);
                 values.validate(value, accessor);
             }
@@ -123,11 +123,11 @@ public class MapSerializer<K, V> extends 
CollectionSerializer<Map<K, V>>
             Map<K, V> m = new LinkedHashMap<K, V>(Math.min(n, 256));
             for (int i = 0; i < n; i++)
             {
-                I key = readValue(input, accessor, offset, version);
+                I key = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(key, accessor, version);
                 keys.validate(key, accessor);
 
-                I value = readValue(input, accessor, offset, version);
+                I value = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(value, accessor, version);
                 values.validate(value, accessor);
 
diff --git a/src/java/org/apache/cassandra/serializers/SetSerializer.java 
b/src/java/org/apache/cassandra/serializers/SetSerializer.java
index 0b7a2a5fa2..bd5e0188be 100644
--- a/src/java/org/apache/cassandra/serializers/SetSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/SetSerializer.java
@@ -80,7 +80,7 @@ public class SetSerializer<T> extends 
CollectionSerializer<Set<T>>
             int offset = sizeOfCollectionSize(n, version);
             for (int i = 0; i < n; i++)
             {
-                V value = readValue(input, accessor, offset, version);
+                V value = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(value, accessor, version);
                 elements.validate(value, accessor);
             }
@@ -111,7 +111,7 @@ public class SetSerializer<T> extends 
CollectionSerializer<Set<T>>
 
             for (int i = 0; i < n; i++)
             {
-                V value = readValue(input, accessor, offset, version);
+                V value = readNonNullValue(input, accessor, offset, version);
                 offset += sizeOfValue(value, accessor, version);
                 elements.validate(value, accessor);
                 l.add(elements.deserialize(value, accessor));
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java 
b/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java
index b1596e346b..bec2ccdd98 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java
@@ -975,7 +975,7 @@ public class CollectionsTest extends CQLTester
         createTable("CREATE TABLE %s(pk int PRIMARY KEY, s set<text>)");
         assertInvalidMessage("Not enough bytes to read a set",
                              "INSERT INTO %s (pk, s) VALUES (?, ?)", 1, 
"test");
-        assertInvalidMessage("String didn't validate.",
+        assertInvalidMessage("Null value read when not allowed",
                              "INSERT INTO %s (pk, s) VALUES (?, ?)", 1, 
Long.MAX_VALUE);
         assertInvalidMessage("Not enough bytes to read a set",
                              "INSERT INTO %s (pk, s) VALUES (?, ?)", 1, "");
@@ -989,7 +989,7 @@ public class CollectionsTest extends CQLTester
         createTable("CREATE TABLE %s(pk int PRIMARY KEY, m map<text, text>)");
         assertInvalidMessage("Not enough bytes to read a map",
                              "INSERT INTO %s (pk, m) VALUES (?, ?)", 1, 
"test");
-        assertInvalidMessage("String didn't validate.",
+        assertInvalidMessage("Null value read when not allowed",
                              "INSERT INTO %s (pk, m) VALUES (?, ?)", 1, 
Long.MAX_VALUE);
         assertInvalidMessage("Not enough bytes to read a map",
                              "INSERT INTO %s (pk, m) VALUES (?, ?)", 1, "");
diff --git a/test/unit/org/apache/cassandra/transport/SerDeserTest.java 
b/test/unit/org/apache/cassandra/transport/SerDeserTest.java
index da76070b6f..603320311d 100644
--- a/test/unit/org/apache/cassandra/transport/SerDeserTest.java
+++ b/test/unit/org/apache/cassandra/transport/SerDeserTest.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.cql3.*;
 import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.serializers.CollectionSerializer;
+import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.transport.Event.TopologyChange;
@@ -107,6 +108,41 @@ public class SerDeserTest
         assertEquals(m, 
mt.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(mb, 
m.size(), version), version));
     }
 
+    @Test(expected = MarshalException.class)
+    public void setsMayNotContainNullsTest()
+    {
+        ProtocolVersion version = ProtocolVersion.MIN_SUPPORTED_VERSION;
+        SetType<?> st = SetType.getInstance(UTF8Type.instance, true);
+        List<ByteBuffer> sb = new ArrayList<>(1);
+        sb.add(null);
+
+        
st.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(sb, 
sb.size(), version), version);
+    }
+
+    @Test(expected = MarshalException.class)
+    public void mapKeysMayNotContainNullsTest()
+    {
+        ProtocolVersion version = ProtocolVersion.MIN_SUPPORTED_VERSION;
+        MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, 
LongType.instance, true);
+        List<ByteBuffer> mb = new ArrayList<>(2);
+        mb.add(null);
+        mb.add(LongType.instance.decompose(999L));
+
+        
mt.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(mb, 
mb.size(), version), version);
+    }
+
+    @Test(expected = MarshalException.class)
+    public void mapValueMayNotContainNullsTest()
+    {
+        ProtocolVersion version = ProtocolVersion.MIN_SUPPORTED_VERSION;
+        MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, 
LongType.instance, true);
+        List<ByteBuffer> mb = new ArrayList<>(2);
+        mb.add(UTF8Type.instance.decompose("danger"));
+        mb.add(null);
+
+        
mt.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(mb, 
mb.size(), version), version);
+    }
+
     @Test
     public void eventSerDeserTest() throws Exception
     {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to