Repository: Innoptech/OpenSTL (language: python)

Issue:
Read large STL file with over 1000000 triangle
I am trying to read STL file which contains over 1000000 triangles, and the run-time error "Triangle count exceeds the maximum allowable value." occurred. I found this is thrown through the line 218
 https://github.com/Innoptech/OpenSTL/blob/fb0cfce0db8d7b3a29eb366a63620989849cbae2/modules/core/include/openstl/core/stl.h#L218

Why the limitation is set to 1000000? Moreover, how can I deal with the large STL files which contain over 1000000 triangles?


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index f9e605a..0449bf0 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,11 @@ faces = [
 triangles = openstl.convert.triangles(vertices, faces)
 ``` 
 
+### Read large STL file 
+To read large STL file with a trianlge count > **1 000 000**, the openstl buffer overflow safety must be unactivated with
+`openstl.set_activate_overflow_safety(False)` after import. Deactivating overflow safety may expose the application 
+to potential buffer overflow risks (if openstl is used in a backend server with sensible data for example).
+
 # C++ Usage
 ### Read STL from file
 ```c++
diff --git a/modules/core/include/openstl/core/stl.h b/modules/core/include/openstl/core/stl.h
index 8af14fc..6eec88e 100644
--- a/modules/core/include/openstl/core/stl.h
+++ b/modules/core/include/openstl/core/stl.h
@@ -34,6 +34,8 @@ SOFTWARE.
 #include <cmath>
 #include <algorithm>
 
+#define MAX_TRIANGLES 1000000
+
 namespace openstl
 {
 // Disable padding for the structure
@@ -126,6 +128,15 @@ namespace openstl
     // Deserialize
     //---------------------------------------------------------------------------------------------------------
 
+    /**
+     * A library-level configuration to activate/deactivate the buffer overflow safety
+     * @return
+     */
+    bool& activateOverflowSafety() {
+        static bool safety_enabled = true;
+        return safety_enabled;
+    }
+
     /**
      * @brief Read a vertex from a stream.
      *
@@ -172,6 +183,9 @@ namespace openstl
                 readVertex(stream, tri.v2);
                 triangles.push_back(tri);
             }
+            if (activateOverflowSafety() && triangles.size() > MAX_TRIANGLES) {
+                throw std::runtime_error("Triangle count exceeds the maximum allowable value.");
+            }
         }
         return triangles;
     }
@@ -185,18 +199,15 @@ namespace openstl
      */
     template <typename Stream>
     std::vector<Triangle> deserializeBinaryStl(Stream& stream) {
-        // Get the current position and determine the file size
         auto start_pos = stream.tellg();
         stream.seekg(0, std::ios::end);
         auto end_pos = stream.tellg();
         stream.seekg(start_pos);
 
-        // Ensure the file is large enough for the header and triangle count
         if (end_pos - start_pos < 84) {
             throw std::runtime_error("File is too small to be a valid STL file.");
         }
 
-        // Explicitly read the header (80 bytes)
         char header[80];
         stream.read(header, sizeof(header));
 
@@ -204,7 +215,6 @@ namespace openstl
             throw std::runtime_error("Failed to read the full header. Possible corruption or incomplete file.");
         }
 
-        // Read and validate triangle count (4 bytes)
         uint32_t triangle_qty;
         stream.read(reinterpret_cast<char*>(&triangle_qty), sizeof(triangle_qty));
 
@@ -212,21 +222,17 @@ namespace openstl
             throw std::runtime_error("Failed to read the triangle count. Possible corruption or incomplete file.");
         }
 
-        // Validate triangle count
-        const uint32_t MAX_TRIANGLES = 1000000;
-        if (triangle_qty > MAX_TRIANGLES) {
+        // Apply the triangle count limit only if activateOverflowSafety is true
+        if (activateOverflowSafety() && triangle_qty > MAX_TRIANGLES) {
             throw std::runtime_error("Triangle count exceeds the maximum allowable value.");
         }
 
-        // Calculate the expected size of the triangle data
         std::size_t expected_data_size = sizeof(Triangle) * triangle_qty;
 
-        // Ensure the stream has enough data left
         if (end_pos - stream.tellg() < static_cast<std::streamoff>(expected_data_size)) {
             throw std::runtime_error("Not enough data in stream for the expected triangle count.");
         }
 
-        // Read triangles
         std::vector<Triangle> triangles(triangle_qty);
         stream.read(reinterpret_cast<char*>(triangles.data()), expected_data_size);
 
diff --git a/python/core/src/stl.cpp b/python/core/src/stl.cpp
index 507643f..38aed25 100644
--- a/python/core/src/stl.cpp
+++ b/python/core/src/stl.cpp
@@ -104,8 +104,19 @@ namespace pybind11 { namespace detail {
 }} // namespace pybind11::detail
 
 
-
 void serialize(py::module_ &m) {
+    // Define getter and setter for the activateOverflowSafety option
+    m.def("get_activate_overflow_safety", []() {
+        return activateOverflowSafety();
+    });
+
+    m.def("set_activate_overflow_safety", [](bool value) {
+        if (!value) {
+            py::print("Warning: Deactivating overflow safety may expose the application to potential buffer overflow risks.",
+                      py::module_::import("sys").attr("stderr"));
+        }
+        activateOverflowSafety() = value;
+    });
 
     py::enum_<StlFormat>(m, "format")
             .value("ascii", StlFormat::ASCII)


Test patch:
diff --git a/tests/core/src/deserialize.test.cpp b/tests/core/src/deserialize.test.cpp
index 06d42c5..5171ef2 100644
--- a/tests/core/src/deserialize.test.cpp
+++ b/tests/core/src/deserialize.test.cpp
@@ -161,7 +161,6 @@ TEST_CASE("Binary STL Serialization/Deserialization Security and Integrity Tests
         CHECK_THROWS_AS(deserializeBinaryStl(file), std::runtime_error);
     }
     SECTION("Test deserialization with the maximum number of triangles") {
-        const uint32_t MAX_TRIANGLES = 1000000;
         const std::string filename = "max_triangles.stl";
 
         // Create a file with exactly MAX_TRIANGLES triangles
@@ -177,11 +176,10 @@ TEST_CASE("Binary STL Serialization/Deserialization Security and Integrity Tests
         REQUIRE(deserialized_triangles.size() == MAX_TRIANGLES);
     }
     SECTION("Test deserialization exceeding the maximum number of triangles") {
-        const uint32_t EXCEEDING_TRIANGLES = 1'000'001;
         const std::string filename = "exceeding_triangles.stl";
 
         // Create a file with more than MAX_TRIANGLES triangles
-        std::vector<Triangle> triangles(EXCEEDING_TRIANGLES);
+        std::vector<Triangle> triangles(MAX_TRIANGLES+1);
         testutils::createStlWithTriangles(triangles, filename);
 
         std::ifstream file(filename, std::ios::binary);
@@ -190,6 +188,20 @@ TEST_CASE("Binary STL Serialization/Deserialization Security and Integrity Tests
         // Test that deserialization throws an exception for exceeding MAX_TRIANGLES
         CHECK_THROWS_AS(deserializeBinaryStl(file), std::runtime_error);
     }
+    SECTION("Test deserialization exceeding the maximum number of triangles with deactivated safety") {
+        const std::string filename = "exceeding_triangles.stl";
+
+        // Create a file with more than MAX_TRIANGLES triangles
+        std::vector<Triangle> triangles(MAX_TRIANGLES+1);
+        testutils::createStlWithTriangles(triangles, filename);
+
+        std::ifstream file(filename, std::ios::binary);
+        REQUIRE(file.is_open());
+
+        // Deactivate buffer overflow safety
+        activateOverflowSafety() = false;
+        CHECK_NOTHROW(deserializeBinaryStl(file));
+    }
     SECTION("Test deserialization with an empty file") {
         const std::string filename{"empty_triangles.stl"};
         testutils::createEmptyStlFile(filename);  // Generate an empty file