diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml index 63bc5c2..54f535d 100644 --- a/.github/workflows/publish-to-test-pypi.yml +++ b/.github/workflows/publish-to-test-pypi.yml @@ -24,19 +24,19 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v5 with: python-version: '3.8' - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install . + python3 -m pip install --upgrade pip + python3 -m pip install . - name: Run tests run: | python3 -m pip install numpy pytest - pytest + python3 -m pytest bump_version_and_tag: if: "github.event_name == 'push' && github.ref == 'refs/heads/main' && !startsWith(github.event.head_commit.message, 'bump:')" @@ -76,7 +76,7 @@ jobs: run: | python -c "import platform; print(f'System: {platform.system()}'); print(f'Architecture: {platform.architecture()[0]}')" - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.18.1 + run: python -m pip install cibuildwheel==2.20.0 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index e859bf7..551c026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,58 @@ +## v1.2.9 (2024-10-15) + +### Fix + +- add a activateOverflowSafety to activate/deactivate buffer overflow safety. + +## v1.2.8 (2024-08-15) + +### Fix + +- remove build for cp313 + +## v1.2.7 (2024-08-15) + +### Fix + +- OPENBLAS_NUM_THREADS=1 to fix numpy on python 3.13 + +## v1.2.6 (2024-08-15) + +### Fix + +- avoid cp313-win32 and trigger build + +## v1.2.5 (2024-08-15) + +### Fix + +- upgrade cibuildwheel and trigger build +- avoid messing with rpath + +## v1.2.4 (2024-08-15) + +### Fix + +- avoid messing with rpath + +## v1.2.3 (2024-08-15) + +### Fix + +- trigger a release + +## v1.2.2 (2024-08-09) + +### Fix + +- put back cp39-win32 in ci build + +## v1.2.1 (2024-08-09) + +### Fix + +- update README.md + ## v1.2.0 (2024-08-09) ### Feat diff --git a/README.md b/README.md index 17c2e86..f677077 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,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++ @@ -224,7 +229,6 @@ C++11 or higher. # DISCLAIMER: STL File Format # - The STL file format, while widely used for 3D modeling and printing, was designed to be simple and easy to parse. However, this simplicity comes with some significant limitations: - Lack of Built-in Validation Mechanisms: The STL format does not include built-in mechanisms such as checksums, hashes, or any form of file validation. This makes it challenging to detect certain types of file corruption, such as a truncated header or malformed data. As a result, errors in file transmission, storage, or manipulation might go undetected. 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/pyproject.toml b/pyproject.toml index 89fc743..8a89ea0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,13 +26,13 @@ testpaths = ["tests/python"] [tool.commitizen] name = "cz_conventional_commits" -version = "1.2.0" +version = "1.2.9" tag_format = "v$version" [tool.cibuildwheel] test-command = "pytest {project}/tests" test-extras = ["test"] -test-skip = ["*universal2:arm64", "pp*", "cp{38,39,310,311,312}-manylinux_i686", "cp38-macosx_arm64", "*musllinux*", "*ppc64le", "*s390x", "cp39-win32"] +test-skip = ["*universal2:arm64", "pp*", "cp{38,39,310,311,312}-manylinux_i686", "cp38-macosx_arm64", "*musllinux*", "*ppc64le", "*s390x", "cp{39,310,311,312,313}-win32", "cp313*"] # Setuptools bug causes collision between pypy and cpython artifacts before-build = "rm -rf {project}/build" 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/setup.py b/setup.py index 687e6b4..17e49c0 100644 --- a/setup.py +++ b/setup.py @@ -52,10 +52,6 @@ def build_extension(self, ext: CMakeExtension) -> None: f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", f"-DPYTHON_EXECUTABLE={sys.executable}", f"-DCMAKE_BUILD_TYPE={cfg}", - "-DCMAKE_BUILD_TYPE=Release", - '-DCMAKE_INSTALL_RPATH=$ORIGIN', - '-DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON', - '-DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=OFF', '-DOPENSTL_BUILD_PYTHON:BOOL=ON' ] @@ -149,7 +145,7 @@ def build_extension(self, ext: CMakeExtension) -> None: tests_require =test_deps, extras_require ={'test': test_deps}, include_package_data =False, - zip_safe =True, + zip_safe =False, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", 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