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 #include +#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 std::vector 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(&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(expected_data_size)) { throw std::runtime_error("Not enough data in stream for the expected triangle count."); } - // Read triangles std::vector triangles(triangle_qty); stream.read(reinterpret_cast(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_(m, "format") .value("ascii", StlFormat::ASCII) 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 triangles(EXCEEDING_TRIANGLES); + std::vector 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 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