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

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a863e8  Robustify FromJSONGraph (#619)
9a863e8 is described below

commit 9a863e8a9349cb8be47da31a6c9ae05c2fb92653
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Jun 10 13:59:09 2026 -0400

    Robustify FromJSONGraph (#619)
    
    Bounds-check the node index and add small arity checks so malformed JSON
    raises ValueError instead of reading out of bounds. Covered by a C++
    malformed-input test.
---
 src/ffi/extra/serialization.cc        | 18 +++++++++++++
 tests/cpp/extra/test_serialization.cc | 50 +++++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/src/ffi/extra/serialization.cc b/src/ffi/extra/serialization.cc
index e32d647..a4b820d 100644
--- a/src/ffi/extra/serialization.cc
+++ b/src/ffi/extra/serialization.cc
@@ -270,6 +270,12 @@ class ObjectGraphDeserializer {
   }
 
   Any GetOrDecodeNode(int64_t node_index) {
+    // node_index comes from the input (root_index and child references), so
+    // validate it before indexing into decoded_nodes_ / nodes_, which would
+    // otherwise read out of bounds.
+    if (node_index < 0 || node_index >= static_cast<int64_t>(nodes_.size())) {
+      TVM_FFI_THROW(ValueError) << "Invalid JSON Object Graph: invalid node 
index " << node_index;
+    }
     // already decoded null index
     if (node_index == decoded_null_index_) {
       return Any(nullptr);
@@ -312,6 +318,11 @@ class ObjectGraphDeserializer {
       }
       case TypeIndex::kTVMFFIDevice: {
         Array<int32_t> data = node["data"].cast<Array<int32_t>>();
+        if (data.size() != 2) {
+          TVM_FFI_THROW(ValueError)
+              << "Invalid JSON Object Graph: Device data must be an array of "
+              << "[device_type, device_id], got " << data.size() << " 
element(s)";
+        }
         return DLDevice{static_cast<DLDeviceType>(data[0]), data[1]};
       }
       case TypeIndex::kTVMFFIStr: {
@@ -356,6 +367,13 @@ class ObjectGraphDeserializer {
   MapType DecodeMapLikeData(const json::Array& data) {
     MapType result;
     const int64_t n = static_cast<int64_t>(data.size());
+    // Map/Dict data is a flat array of alternating [key, value] indices, so 
the
+    // length must be even; an odd length means the input is malformed and 
would
+    // otherwise read data[i + 1] past the end on the final iteration.
+    if (n % 2 != 0) {
+      TVM_FFI_THROW(ValueError) << "Invalid JSON Object Graph: Map/Dict data 
must contain an even "
+                                << "number of [key, value] entries, got " << n;
+    }
     for (int64_t i = 0; i < n; i += 2) {
       int64_t key_index = data[i].cast<int64_t>();
       int64_t value_index = data[i + 1].cast<int64_t>();
diff --git a/tests/cpp/extra/test_serialization.cc 
b/tests/cpp/extra/test_serialization.cc
index 664c055..8d29f5b 100644
--- a/tests/cpp/extra/test_serialization.cc
+++ b/tests/cpp/extra/test_serialization.cc
@@ -819,6 +819,56 @@ TEST(Serialization, ErrorMissingNodes) {
   EXPECT_ANY_THROW(FromJSONGraph(graph));
 }
 
+// ---------------------------------------------------------------------------
+// Malformed-input validation: every case below must THROW an ffi::Error rather
+// than read out of bounds when deserializing an object graph.
+// ---------------------------------------------------------------------------
+TEST(Serialization, MalformedInput) {
+  // NOTE: use EXPECT_ANY_THROW rather than EXPECT_THROW(..., tvm::ffi::Error).
+  // FromJSONGraph is compiled into the shared library, so the tvm::ffi::Error 
it
+  // throws carries the library's typeinfo. On macOS (hidden-visibility 
typeinfo)
+  // that does not match the test executable's typeinfo, so an exact-type match
+  // spuriously fails even though the error is thrown correctly. This matches 
the
+  // other Serialization.Error* tests in this file, which also use 
EXPECT_ANY_THROW.
+  auto expect_throws = [](const json::Object& graph) { 
EXPECT_ANY_THROW(FromJSONGraph(graph)); };
+
+  // root_index points past the end of the nodes array.
+  expect_throws({{"root_index", 99}, {"nodes", 
json::Array{json::Object{{"type", "None"}}}}});
+
+  // root_index is negative.
+  expect_throws({{"root_index", -5}, {"nodes", 
json::Array{json::Object{{"type", "None"}}}}});
+
+  // A child reference inside an array node is out of range.
+  expect_throws(
+      {{"root_index", 0},
+       {"nodes", json::Array{json::Object{{"type", "ffi.Array"}, {"data", 
json::Array{42}}}}}});
+
+  // A key/value reference inside a map node is out of range.
+  expect_throws(
+      {{"root_index", 0},
+       {"nodes", json::Array{json::Object{{"type", "ffi.Map"}, {"data", 
json::Array{5, 6}}}}}});
+
+  // Map data has an odd number of entries (would read one past the end).
+  expect_throws({{"root_index", 0},
+                 {"nodes", json::Array{json::Object{{"type", "ffi.Map"}, 
{"data", json::Array{0}}},
+                                       json::Object{{"type", "int"}, {"data", 
1}}}}});
+
+  // Device data has the wrong number of elements.
+  expect_throws(
+      {{"root_index", 0},
+       {"nodes", json::Array{json::Object{{"type", "Device"}, {"data", 
json::Array{1}}}}}});
+
+  // A node is missing the required "type" key.
+  expect_throws({{"root_index", 0}, {"nodes", 
json::Array{json::Object{{"data", 1}}}}});
+
+  // A node has the wrong value type for a child reference (string where an int
+  // index is expected).
+  expect_throws(
+      {{"root_index", 0},
+       {"nodes", json::Array{json::Object{{"type", "ffi.Array"},
+                                          {"data", 
json::Array{String("not-an-index")}}}}}});
+}
+
 // ---------------------------------------------------------------------------
 // String serialization roundtrip (json::Stringify / json::Parse)
 // ---------------------------------------------------------------------------

Reply via email to