From 11e40cee6e397755dc20eca7bdd1ecd80279e637 Mon Sep 17 00:00:00 2001
From: Eduardo Speroni <edusperoni@users.noreply.github.com>
Date: Mon, 2 Sep 2024 16:44:55 -0300
Subject: [PATCH 1/2] fix: generate correct metadata when overflowing signed
 short values (#1821)

---
 .../src/com/telerik/metadata/Generator.java   | 11 +++++-
 .../src/src/com/telerik/metadata/Writer.java  | 34 ++++++++++++++++++-
 .../runtime/src/main/cpp/MetadataReader.cpp   | 17 +++++++---
 3 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java b/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java
index 5e11277a4..6a53cea27 100644
--- a/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java
+++ b/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Generator.java
@@ -27,6 +27,8 @@ public class Generator {
     private static final String MDG_BLACKLIST = "blacklist.mdg";
     private static final String METADATA_JAVA_OUT = "mdg-java-out.txt";
 
+    private static boolean verbose_mode = false;
+
     /**
      * @param args arguments
      */
@@ -69,7 +71,13 @@ public static void main(String[] args) {
             FileOutputStream oss = new FileOutputStream(new File(metadataOutputDir, "treeStringsStream.dat"));
             FileStreamWriter outStringsStream = new FileStreamWriter(oss);
 
-            new Writer(outNodeStream, outValueStream, outStringsStream).writeTree(root);
+            if (verbose_mode) {
+                FileOutputStream ods = new FileOutputStream(new File("metadata-debug.json"));
+                FileStreamWriter outDebugStream = new FileStreamWriter(ods);
+                new Writer(outNodeStream, outValueStream, outStringsStream, outDebugStream).writeTree(root);
+            } else {
+                new Writer(outNodeStream, outValueStream, outStringsStream).writeTree(root);
+            }
         } catch (Throwable ex) {
             System.err.println(String.format("Error executing Metadata Generator: %s", ex.getMessage()));
             ex.printStackTrace(System.out);
@@ -83,6 +91,7 @@ private static void enableFlaggedFeatures(String[] args) {
                 String filePath = arg.replace(ANALYTICS_ARGUMENT_BEGINNING, "");
                 AnalyticsConfiguration.enableAnalytics(filePath);
             } else if (VERBOSE_FLAG_NAME.equals(arg)) {
+                verbose_mode = true;
                 MetadataFilterConsoleLogger.INSTANCE.setEnabled(true);
             } else if (SKIP_FLAG_NAME.equals(arg)) {
                 System.out.println("Skipping metadata generation: skip flag used.");
diff --git a/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java b/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java
index 1d76f2550..36777a831 100644
--- a/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java
+++ b/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java
@@ -1,5 +1,7 @@
 package com.telerik.metadata;
 
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
 import com.telerik.metadata.TreeNode.FieldInfo;
 import com.telerik.metadata.TreeNode.MethodInfo;
 
@@ -10,6 +12,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
 
@@ -18,14 +21,20 @@ public class Writer {
     private final StreamWriter outNodeStream;
     private final StreamWriter outValueStream;
     private final StreamWriter outStringsStream;
+    private final StreamWriter outDebugStream;
 
     private int commonInterfacePrefixPosition;
 
     public Writer(StreamWriter outNodeStream, StreamWriter outValueStream,
                   StreamWriter outStringsStream) {
+        this(outNodeStream, outValueStream, outStringsStream, null);
+    }
+    public Writer(StreamWriter outNodeStream, StreamWriter outValueStream,
+                  StreamWriter outStringsStream, StreamWriter outDebugStream) {
         this.outNodeStream = outNodeStream;
         this.outValueStream = outValueStream;
         this.outStringsStream = outStringsStream;
+        this.outDebugStream = outDebugStream;
     }
 
     private final static byte[] writeUniqueName_lenBuff = new byte[2];
@@ -214,6 +223,10 @@ public void writeTree(TreeNode root) throws Exception {
         d.push(root);
         while (!d.isEmpty()) {
             TreeNode n = d.pollFirst();
+            if (Short.toUnsignedInt((short)(curId + 1)) < Short.toUnsignedInt(curId)) {
+                // we have overflowed our maximum (16 bit) metadata size
+                throw new Exception("Metadata is too big and has overflown our current limit, please report this issue");
+            }
             n.id = n.firstChildId = n.nextSiblingId = curId++;
 
             String name = n.getName();
@@ -351,7 +364,7 @@ public void writeTree(TreeNode root) throws Exception {
         while (!d.isEmpty()) {
             TreeNode n = d.pollFirst();
 
-            nodeData[0] = n.firstChildId + (n.nextSiblingId << 16);
+            nodeData[0] = (n.firstChildId & 0xFFFF) | (n.nextSiblingId << 16);
             nodeData[1] = n.offsetName;
             nodeData[2] = n.offsetValue;
 
@@ -364,5 +377,24 @@ public void writeTree(TreeNode root) throws Exception {
 
         outNodeStream.flush();
         outNodeStream.close();
+
+        if (outDebugStream != null) {
+            d.push(root);
+            JsonArray rootArray = new JsonArray();
+            while (!d.isEmpty()) {
+                TreeNode n = d.pollFirst();
+                JsonObject obj = new JsonObject();
+                obj.addProperty("id", Short.toUnsignedInt(n.id));
+                obj.addProperty("nextSiblingId", Short.toUnsignedInt(n.nextSiblingId));
+                obj.addProperty("firstChildId", Short.toUnsignedInt(n.firstChildId));
+                obj.addProperty("name", n.getName());
+                obj.addProperty("nodeType", n.nodeType);
+                rootArray.add(obj);
+                d.addAll(n.children);
+            }
+            outDebugStream.write(rootArray.toString().getBytes());
+            outDebugStream.flush();
+            outDebugStream.close();
+        }
     }
 }
diff --git a/test-app/runtime/src/main/cpp/MetadataReader.cpp b/test-app/runtime/src/main/cpp/MetadataReader.cpp
index 9fb9daa78..5c37dfdcb 100644
--- a/test-app/runtime/src/main/cpp/MetadataReader.cpp
+++ b/test-app/runtime/src/main/cpp/MetadataReader.cpp
@@ -2,6 +2,7 @@
 #include "MetadataMethodInfo.h"
 #include "Util.h"
 #include <sstream>
+#include <android/log.h>
 
 using namespace std;
 using namespace tns;
@@ -54,20 +55,28 @@ MetadataTreeNode* MetadataReader::BuildTree() {
             while (true) {
 
                 uint16_t childNodeDataId = childNodeData - rootNodeData;
+
+                MetadataTreeNode* childNode;
                 // node (and its next siblings) already visited, so we don't need to visit it again
                 if (m_v[childNodeDataId] != emptyNode) {
+                    childNode = m_v[childNodeDataId];
+                    __android_log_print(ANDROID_LOG_ERROR, "TNS.error", "Consistency error in metadata. A child should never have been visited before its parent. Parent: %s Child: %s. Child metadata id: %u", node->name.c_str(), childNode->name.c_str(), childNodeDataId);
                     break;
+                } else {
+                    childNode = new MetadataTreeNode;
+                    childNode->name = ReadName(childNodeData->offsetName);
+                    childNode->offsetValue = childNodeData->offsetValue;
                 }
-
-                MetadataTreeNode* childNode = new MetadataTreeNode;
                 childNode->parent = node;
-                childNode->name = ReadName(childNodeData->offsetName);
-                childNode->offsetValue = childNodeData->offsetValue;
 
                 node->children->push_back(childNode);
 
                 m_v[childNodeDataId] = childNode;
 
+                if (childNodeDataId == childNodeData->nextSiblingId) {
+                    break;
+                }
+
                 childNodeData = rootNodeData + childNodeData->nextSiblingId;
             }
         }

From 1a73f6ffbb4d544d4c1da58daf9068c702985d35 Mon Sep 17 00:00:00 2001
From: Eduardo Speroni <edusperoni@gmail.com>
Date: Mon, 30 Sep 2024 10:53:22 -0300
Subject: [PATCH 2/2] fix: prevent metadata offset overflow into array space
 and convert shorts to uints before addition

---
 .../src/src/com/telerik/metadata/Writer.java           | 10 ++++++++--
 test-app/runtime/src/main/cpp/MetadataReader.h         |  2 +-
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java b/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java
index 36777a831..a09472fa2 100644
--- a/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java
+++ b/test-app/build-tools/android-metadata-generator/src/src/com/telerik/metadata/Writer.java
@@ -305,7 +305,7 @@ public void writeTree(TreeNode root) throws Exception {
         outStringsStream.close();
         writeInt(0, outValueStream);
 
-        final int array_offset = 1000 * 1000 * 1000;
+        final int array_offset = Integer.MAX_VALUE; // 2147483647, which is half of uint32
 
         d.push(root);
         while (!d.isEmpty()) {
@@ -328,6 +328,10 @@ public void writeTree(TreeNode root) throws Exception {
                 throw new Exception("should not happen");
             }
 
+            if ((n.nodeType & TreeNode.Array) != TreeNode.Array && Integer.toUnsignedLong(n.offsetValue) >= Integer.toUnsignedLong(array_offset)) {
+                throw new Exception("Non-array metadata has overflown array space. Please report this issue.");
+            }
+
             d.addAll(n.children);
         }
 
@@ -339,7 +343,7 @@ public void writeTree(TreeNode root) throws Exception {
             TreeNode n = d.pollFirst();
 
             if (n.arrayElement != null) {
-                n.offsetValue = array_offset + n.arrayElement.id;
+                n.offsetValue = array_offset + Short.toUnsignedInt(n.arrayElement.id);
             }
 
             if (!n.children.isEmpty()) {
@@ -387,6 +391,8 @@ public void writeTree(TreeNode root) throws Exception {
                 obj.addProperty("id", Short.toUnsignedInt(n.id));
                 obj.addProperty("nextSiblingId", Short.toUnsignedInt(n.nextSiblingId));
                 obj.addProperty("firstChildId", Short.toUnsignedInt(n.firstChildId));
+                obj.addProperty("offsetName", Integer.toUnsignedLong(n.offsetName));
+                obj.addProperty("offsetValue", Integer.toUnsignedLong(n.offsetValue));
                 obj.addProperty("name", n.getName());
                 obj.addProperty("nodeType", n.nodeType);
                 rootArray.add(obj);
diff --git a/test-app/runtime/src/main/cpp/MetadataReader.h b/test-app/runtime/src/main/cpp/MetadataReader.h
index 1f3706994..22f7fbfdd 100644
--- a/test-app/runtime/src/main/cpp/MetadataReader.h
+++ b/test-app/runtime/src/main/cpp/MetadataReader.h
@@ -167,7 +167,7 @@ class MetadataReader {
 
     private:
 
-        static const uint32_t ARRAY_OFFSET = 1000000000;
+        static const uint32_t ARRAY_OFFSET = INT32_MAX; // 2147483647
 
         MetadataTreeNode* BuildTree();