CVE-2022-25867: NULL Pointer Dereference (CWE-476)
value: The package io.socket:socket.io-client before 2.0.1 are vulnerable to NULL Pointer Dereference when parsing a packet with with invalid payload format.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2022-08-02T14:15Z
Repository: https://github.com/socketio/socket.io-client-java
Language: Java
Files changed: src/main/java/io/socket/client/Manager.java, src/main/java/io/socket/parser/IOParser.java, src/test/java/io/socket/parser/ByteArrayTest.java, src/test/java/io/socket/parser/ParserTest.java
Commit date: 2021-04-27 00:14:24 +0200
Commit message: fix: ensure the payload format is valid

This commit should prevent some NPE issues encountered after the
parsing of the packet.

Related:

- https://github.com/socketio/socket.io-client-java/issues/642
- https://github.com/socketio/socket.io-client-java/issues/609
- https://github.com/socketio/socket.io-client-java/issues/505

Diff: diff --git a/src/main/java/io/socket/client/Manager.java b/src/main/java/io/socket/client/Manager.java
index 74222bc..a3c5f19 100644
--- a/src/main/java/io/socket/client/Manager.java
+++ b/src/main/java/io/socket/client/Manager.java
@@ -324,14 +324,18 @@ public class Manager extends Emitter {
         final io.socket.engineio.client.Socket socket = this.engine;
         this.subs.add(On.on(socket, Engine.EVENT_DATA, new Listener() {
             @Override
             public void call(Object... objects) {
                 Object data = objects[0];
-                if (data instanceof String) {
-                    Manager.this.ondata((String)data);
-                } else if (data instanceof byte[]) {
-                    Manager.this.ondata((byte[])data);
+                try {
+                    if (data instanceof String) {
+                        Manager.this.decoder.add((String) data);
+                    } else if (data instanceof byte[]) {
+                        Manager.this.decoder.add((byte[]) data);
+                    }
+                } catch (DecodingException e) {
+                    logger.fine("error while decoding the packet: " + e.getMessage());
                 }
             }
         }));
         this.subs.add(On.on(socket, Engine.EVENT_ERROR, new Listener() {
             @Override
@@ -351,26 +355,10 @@ public class Manager extends Emitter {
                 Manager.this.ondecoded(packet);
             }
         });
     }
 
-    private void ondata(String data) {
-        try {
-            this.decoder.add(data);
-        } catch (DecodingException e) {
-            this.onerror(e);
-        }
-    }
-
-    private void ondata(byte[] data) {
-        try {
-            this.decoder.add(data);
-        } catch (DecodingException e) {
-            this.onerror(e);
-        }
-    }
-
     private void ondecoded(Packet packet) {
         this.emit(EVENT_PACKET, packet);
     }
 
     private void onerror(Exception err) {
diff --git a/src/main/java/io/socket/parser/IOParser.java b/src/main/java/io/socket/parser/IOParser.java
index b6a959c..e9c5345 100644
--- a/src/main/java/io/socket/parser/IOParser.java
+++ b/src/main/java/io/socket/parser/IOParser.java
@@ -1,9 +1,11 @@
 package io.socket.parser;
 
 import io.socket.hasbinary.HasBinary;
+import org.json.JSONArray;
 import org.json.JSONException;
+import org.json.JSONObject;
 import org.json.JSONTokener;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -181,18 +183,41 @@ final public class IOParser implements Parser {
                     p.data = new JSONTokener(str.substring(i)).nextValue();
                 } catch (JSONException e) {
                     logger.log(Level.WARNING, "An error occured while retrieving data from JSONTokener", e);
                     throw new DecodingException("invalid payload");
                 }
+                if (!isPayloadValid(p.type, p.data)) {
+                    throw new DecodingException("invalid payload");
+                }
             }
 
             if (logger.isLoggable(Level.FINE)) {
                 logger.fine(String.format("decoded %s as %s", str, p));
             }
             return p;
         }
 
+        private static boolean isPayloadValid(int type, Object payload) {
+            switch (type) {
+                case Parser.CONNECT:
+                case Parser.CONNECT_ERROR:
+                    return payload instanceof JSONObject;
+                case Parser.DISCONNECT:
+                    return payload == null;
+                case Parser.EVENT:
+                case Parser.BINARY_EVENT:
+                    return payload instanceof JSONArray
+                            && ((JSONArray) payload).length() > 0
+                            && !((JSONArray) payload).isNull(0);
+                case Parser.ACK:
+                case Parser.BINARY_ACK:
+                    return payload instanceof JSONArray;
+                default:
+                    return false;
+            }
+        }
+
         @Override
         public void destroy() {
             if (this.reconstructor != null) {
                 this.reconstructor.finishReconstruction();
             }
diff --git a/src/test/java/io/socket/parser/ByteArrayTest.java b/src/test/java/io/socket/parser/ByteArrayTest.java
index 9bbb82b..d48547c 100644
--- a/src/test/java/io/socket/parser/ByteArrayTest.java
+++ b/src/test/java/io/socket/parser/ByteArrayTest.java
@@ -1,62 +1,62 @@
 package io.socket.parser;
 
-import io.socket.emitter.Emitter;
 import org.json.JSONArray;
 import org.json.JSONException;
-import org.json.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
+import static java.util.Arrays.asList;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
 @RunWith(JUnit4.class)
 public class ByteArrayTest {
 
     private static Parser.Encoder encoder = new IOParser.Encoder();
 
     @Test
-    public void encodeByteArray() {
-        Packet<byte[]> packet = new Packet<>(Parser.BINARY_EVENT);
-        packet.data = "abc".getBytes(Charset.forName("UTF-8"));
+    public void encodeByteArray() throws JSONException {
+        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
+        packet.data = new JSONArray(asList("abc", "abc".getBytes(StandardCharsets.UTF_8)));
         packet.id = 23;
         packet.nsp = "/cool";
         Helpers.testBin(packet);
     }
 
     @Test
     public void encodeByteArray2() {
-        Packet<byte[]> packet = new Packet<>(Parser.BINARY_EVENT);
-        packet.data = new byte[2];
+        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
+        packet.data = new JSONArray(asList("2", new byte[] { 0, 1 }));
         packet.id = 0;
         packet.nsp = "/";
         Helpers.testBin(packet);
     }
 
     @Test
     public void encodeByteArrayDeepInJson() throws JSONException {
-        JSONObject data = new JSONObject("{a: \"hi\", b: {}, c: {a: \"bye\", b: {}}}");
-        data.getJSONObject("b").put("why", new byte[3]);
-        data.getJSONObject("c").getJSONObject("b").put("a", new byte[6]);
+        JSONArray data = new JSONArray("[{a: \"hi\", b: {}, c: {a: \"bye\", b: {}}}]");
+        data.getJSONObject(0).getJSONObject("b").put("why", new byte[3]);
+        data.getJSONObject(0).getJSONObject("c").getJSONObject("b").put("a", new byte[6]);
 
-        Packet<JSONObject> packet = new Packet<>(Parser.BINARY_EVENT);
+        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
         packet.data = data;
         packet.id = 999;
         packet.nsp = "/deep";
         Helpers.testBin(packet);
     }
 
     @Test
     public void encodeDeepBinaryJSONWithNullValue() throws JSONException {
-        JSONObject data = new JSONObject("{a: \"b\", c: 4, e: {g: null}, h: null}");
-        data.put("h", new byte[9]);
+        JSONArray data = new JSONArray("[{a: \"b\", c: 4, e: {g: null}, h: null}]");
+        data.getJSONObject(0).put("h", new byte[9]);
 
-        Packet<JSONObject> packet = new Packet<>(Parser.BINARY_EVENT);
+        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
         packet.data = data;
         packet.nsp = "/";
         packet.id = 600;
         Helpers.testBin(packet);
     }
diff --git a/src/test/java/io/socket/parser/ParserTest.java b/src/test/java/io/socket/parser/ParserTest.java
index 56dc3c2..17d4886 100644
--- a/src/test/java/io/socket/parser/ParserTest.java
+++ b/src/test/java/io/socket/parser/ParserTest.java
@@ -61,7 +61,10 @@ public class ParserTest {
         Helpers.testDecodeError(String.valueOf(Parser.BINARY_EVENT));
         // event non numeric id
         Helpers.testDecodeError(Parser.EVENT + "2sd");
         // event with invalid json data
         Helpers.testDecodeError(Parser.EVENT + "2[\"a\",1,{asdf}]");
+        Helpers.testDecodeError(Parser.EVENT + "2{}");
+        Helpers.testDecodeError(Parser.EVENT + "2[]");
+        Helpers.testDecodeError(Parser.EVENT + "2[null]");
     }
 }

Vulnerable code:                 if (data instanceof String) {
                    Manager.this.ondata((String)data);
                } else if (data instanceof byte[]) {
                    Manager.this.ondata((byte[])data);
    private void ondata(String data) {
        try {
            this.decoder.add(data);
        } catch (DecodingException e) {
            this.onerror(e);
        }
    }

    private void ondata(byte[] data) {
        try {
            this.decoder.add(data);
        } catch (DecodingException e) {
            this.onerror(e);
        }
    }

import io.socket.emitter.Emitter;
import org.json.JSONObject;
    public void encodeByteArray() {
        Packet<byte[]> packet = new Packet<>(Parser.BINARY_EVENT);
        packet.data = "abc".getBytes(Charset.forName("UTF-8"));
        Packet<byte[]> packet = new Packet<>(Parser.BINARY_EVENT);
        packet.data = new byte[2];
        JSONObject data = new JSONObject("{a: \"hi\", b: {}, c: {a: \"bye\", b: {}}}");
        data.getJSONObject("b").put("why", new byte[3]);
        data.getJSONObject("c").getJSONObject("b").put("a", new byte[6]);
        Packet<JSONObject> packet = new Packet<>(Parser.BINARY_EVENT);
        JSONObject data = new JSONObject("{a: \"b\", c: 4, e: {g: null}, h: null}");
        data.put("h", new byte[9]);
        Packet<JSONObject> packet = new Packet<>(Parser.BINARY_EVENT);
Fixed code:                 try {
                    if (data instanceof String) {
                        Manager.this.decoder.add((String) data);
                    } else if (data instanceof byte[]) {
                        Manager.this.decoder.add((byte[]) data);
                    }
                } catch (DecodingException e) {
                    logger.fine("error while decoding the packet: " + e.getMessage());
import org.json.JSONArray;
import org.json.JSONObject;
                if (!isPayloadValid(p.type, p.data)) {
                    throw new DecodingException("invalid payload");
                }
        private static boolean isPayloadValid(int type, Object payload) {
            switch (type) {
                case Parser.CONNECT:
                case Parser.CONNECT_ERROR:
                    return payload instanceof JSONObject;
                case Parser.DISCONNECT:
                    return payload == null;
                case Parser.EVENT:
                case Parser.BINARY_EVENT:
                    return payload instanceof JSONArray
                            && ((JSONArray) payload).length() > 0
                            && !((JSONArray) payload).isNull(0);
                case Parser.ACK:
                case Parser.BINARY_ACK:
                    return payload instanceof JSONArray;
                default:
                    return false;
            }
        }

import java.nio.charset.StandardCharsets;
import static java.util.Arrays.asList;
    public void encodeByteArray() throws JSONException {
        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
        packet.data = new JSONArray(asList("abc", "abc".getBytes(StandardCharsets.UTF_8)));
        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
        packet.data = new JSONArray(asList("2", new byte[] { 0, 1 }));
        JSONArray data = new JSONArray("[{a: \"hi\", b: {}, c: {a: \"bye\", b: {}}}]");
        data.getJSONObject(0).getJSONObject("b").put("why", new byte[3]);
        data.getJSONObject(0).getJSONObject("c").getJSONObject("b").put("a", new byte[6]);
        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
        JSONArray data = new JSONArray("[{a: \"b\", c: 4, e: {g: null}, h: null}]");
        data.getJSONObject(0).put("h", new byte[9]);
        Packet<JSONArray> packet = new Packet<>(Parser.BINARY_EVENT);
        Helpers.testDecodeError(Parser.EVENT + "2{}");
        Helpers.testDecodeError(Parser.EVENT + "2[]");
        Helpers.testDecodeError(Parser.EVENT + "2[null]");