Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Innoptech/OpenSTL
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanchristopheruel committed Oct 15, 2024
2 parents 5f2e98f + 0792be6 commit 7d9d417
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 27 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:')"
Expand Down Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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++
Expand Down Expand Up @@ -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.
Expand Down
26 changes: 16 additions & 10 deletions modules/core/include/openstl/core/stl.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ SOFTWARE.
#include <cmath>
#include <algorithm>

#define MAX_TRIANGLES 1000000

namespace openstl
{
// Disable padding for the structure
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
}
Expand All @@ -185,48 +199,40 @@ 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));

if (stream.gcount() != sizeof(header)) {
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));

if (stream.gcount() != sizeof(triangle_qty) || stream.fail() || stream.eof()) {
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);

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
13 changes: 12 additions & 1 deletion python/core/src/stl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 1 addition & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]

Expand Down Expand Up @@ -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",
Expand Down
18 changes: 15 additions & 3 deletions tests/core/src/deserialize.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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
Expand Down

0 comments on commit 7d9d417

Please sign in to comment.