mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 14:20:58 +00:00
Harden 3MF Loading Against Malformed Plate IDs and Paint Data (#15959)
* Reject 3MF Plate IDs Below 1 Instead of Indexing Before the Plate List The plate importer copied each plater_id from model_settings.config into the 1-based plate list after checking only the upper bound, so plater_id="0" wrote to plate_data_list[-1] and crashed on load. Both copy sites now reject ids below 1 with the same "invalid plate index" error already used for ids past the end. * Drop Malformed 3MF Paint Data Instead of Reading Past the Bitstream Painted facets are decoded from a bitstream a nibble at a time with no bound check, so a truncated or corrupt paint string in a 3MF (for example split codes with no children behind them) read past the end and crashed on load and slice. A one- or two-side split naming side 3 also indexed past the triangle's vertices. Every nibble read now goes through a bounds-checked reader. Loading validates each triangle's tree and drops a malformed one with a warning, so the stored data, used extruder states and later decoding all agree. deserialize() also unwinds and clears any triangle whose tree is incomplete or malformed, and has_facets() stops at a truncated triangle. Valid streams decode unchanged.
This commit is contained in:
@@ -1633,7 +1633,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
}
|
||||
while (it != m_plater_data.end())
|
||||
{
|
||||
if (it->first > m_plater_data.size())
|
||||
if (it->first <= 0 || static_cast<size_t>(it->first) > m_plater_data.size())
|
||||
{
|
||||
add_error("invalid plate index");
|
||||
return false;
|
||||
@@ -2316,7 +2316,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
}
|
||||
while (it != m_plater_data.end())
|
||||
{
|
||||
if (it->first > m_plater_data.size())
|
||||
if (it->first <= 0 || static_cast<size_t>(it->first) > m_plater_data.size())
|
||||
{
|
||||
add_error("invalid plate index");
|
||||
return false;
|
||||
|
||||
@@ -3750,7 +3750,11 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri
|
||||
m_data.bitstream.insert(m_data.bitstream.end(), bool(dec & (1 << i)));
|
||||
}
|
||||
|
||||
m_data.update_used_states(bitstream_start_idx);
|
||||
if (!m_data.update_used_states(bitstream_start_idx)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": dropping malformed paint data of triangle " << triangle_id;
|
||||
m_data.bitstream.resize(bitstream_start_idx);
|
||||
m_data.triangles_to_split.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
bool FacetsAnnotation::equals(const FacetsAnnotation &other) const
|
||||
|
||||
@@ -1778,6 +1778,13 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const {
|
||||
return out.data;
|
||||
}
|
||||
|
||||
// A split code keeps the split side (one split) or the kept side (two splits) in its upper two
|
||||
// bits, where 3 is not a side. The value is ignored for a three-side split.
|
||||
static bool split_code_valid(int code)
|
||||
{
|
||||
return (code & 0b11) == 3 || (code >> 2) != 3;
|
||||
}
|
||||
|
||||
void TriangleSelector::deserialize(const TriangleSplittingData &data,
|
||||
bool needs_reset,
|
||||
EnforcerBlockerType max_ebt,
|
||||
@@ -1812,11 +1819,12 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
|
||||
|
||||
for (auto [triangle_id, ibit] : data.triangles_to_split) {
|
||||
assert(triangle_id < int(m_triangles.size()));
|
||||
assert(ibit < int(data.bitstream.size()));
|
||||
auto next_nibble = [&data, &ibit = ibit]() {
|
||||
// Set when the bitstream runs out or holds an impossible split before this triangle's tree is complete.
|
||||
bool corrupt = false;
|
||||
auto next_nibble = [&data, &ibit = ibit, &corrupt]() {
|
||||
int n = 0;
|
||||
for (int i = 0; i < 4; ++ i)
|
||||
n |= data.bitstream[ibit ++] << i;
|
||||
if (! data.read_nibble(ibit, n))
|
||||
corrupt = true;
|
||||
return n;
|
||||
};
|
||||
// Decode a leaf state stored behind the "11" prefix: one nibble of (state-3) for states
|
||||
@@ -1835,6 +1843,10 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
|
||||
bool is_split = num_of_children != 0;
|
||||
// Only valid if not is_split.
|
||||
auto state = is_split ? EnforcerBlockerType::NONE : ((code & 0b1100) == 0b1100 ? decode_leaf_state() : EnforcerBlockerType(code >> 2));
|
||||
if (is_split && ! split_code_valid(code))
|
||||
corrupt = true;
|
||||
if (corrupt)
|
||||
break;
|
||||
|
||||
// BBS
|
||||
if (state == to_delete_filament)
|
||||
@@ -1849,7 +1861,7 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
|
||||
}
|
||||
|
||||
// Only valid if is_split.
|
||||
int special_side = code >> 2;
|
||||
int special_side = num_of_split_sides == 3 ? 0 : code >> 2;
|
||||
|
||||
// Take care of the first iteration separately, so handling of the others is simpler.
|
||||
if (parents.empty()) {
|
||||
@@ -1904,47 +1916,55 @@ void TriangleSelector::deserialize(const TriangleSplittingData &data,
|
||||
if (parents.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
if (corrupt) {
|
||||
// Every split above allocated all of its children, so the partial tree unwinds cleanly.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": malformed paint data, dropping paint of triangle " << triangle_id;
|
||||
undivide_triangle(triangle_id);
|
||||
m_triangles[triangle_id].set_state(EnforcerBlockerType::NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::TriangleSplittingData::update_used_states(const size_t bitstream_start_idx) {
|
||||
assert(bitstream_start_idx < this->bitstream.size());
|
||||
assert(!this->bitstream.empty() && this->bitstream.size() != bitstream_start_idx);
|
||||
assert((this->bitstream.size() - bitstream_start_idx) % 4 == 0);
|
||||
bool TriangleSelector::TriangleSplittingData::update_used_states(const size_t bitstream_start_idx) {
|
||||
int ibit = static_cast<int>(bitstream_start_idx);
|
||||
uint64_t states = 0;
|
||||
do {
|
||||
// Walk one triangle's tree depth-first, counting the nodes still to be read; a split node adds its children.
|
||||
for (int pending_nodes = 1; pending_nodes > 0; --pending_nodes) {
|
||||
int code;
|
||||
if (!this->read_nibble(ibit, code))
|
||||
return false;
|
||||
|
||||
if (this->bitstream.empty() || this->bitstream.size() == bitstream_start_idx)
|
||||
return;
|
||||
if (const int num_of_split_sides = code & 0b11; num_of_split_sides != 0) {
|
||||
if (!split_code_valid(code))
|
||||
return false;
|
||||
pending_nodes += num_of_split_sides + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t nibble_idx = bitstream_start_idx;
|
||||
|
||||
auto read_next_nibble = [&data_bitstream = std::as_const(this->bitstream), &nibble_idx]() -> uint8_t {
|
||||
assert(nibble_idx + 3 < data_bitstream.size());
|
||||
uint8_t code = 0;
|
||||
for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx)
|
||||
code |= data_bitstream[nibble_idx++] << bit_idx;
|
||||
return code;
|
||||
};
|
||||
|
||||
while (nibble_idx < this->bitstream.size()) {
|
||||
const uint8_t code = read_next_nibble();
|
||||
|
||||
if (const bool is_split = (code & 0b11) != 0; is_split)
|
||||
continue;
|
||||
|
||||
uint8_t facet_state;
|
||||
if ((code & 0b1100) == 0b1100) {
|
||||
// Leaf behind the "11" prefix: one nibble of (state-3), or 0b1111 + (state-18).
|
||||
const uint8_t nibble = read_next_nibble();
|
||||
facet_state = nibble == 0b1111 ? uint8_t(read_next_nibble() + 18) : uint8_t(nibble + 3);
|
||||
} else {
|
||||
facet_state = code >> 2;
|
||||
int facet_state = code >> 2;
|
||||
if (facet_state == 0b11) {
|
||||
// Leaf behind the "11" prefix: one nibble of (state-3), or 0b1111 + (state-18).
|
||||
int nibble;
|
||||
if (!this->read_nibble(ibit, nibble))
|
||||
return false;
|
||||
facet_state = nibble + 3;
|
||||
if (nibble == 0b1111) {
|
||||
if (!this->read_nibble(ibit, nibble))
|
||||
return false;
|
||||
facet_state = nibble + 18;
|
||||
}
|
||||
}
|
||||
states |= uint64_t(1) << facet_state;
|
||||
}
|
||||
assert(facet_state < this->used_states.size());
|
||||
if (facet_state >= this->used_states.size())
|
||||
continue;
|
||||
} while (static_cast<size_t>(ibit) < this->bitstream.size());
|
||||
|
||||
this->used_states[facet_state] = true;
|
||||
}
|
||||
// The leaf encoding tops out at state 33, so every state fits the 64-bit mask.
|
||||
for (size_t state_idx = 0; state_idx < std::min<size_t>(this->used_states.size(), 64); ++state_idx)
|
||||
if (states & (uint64_t(1) << state_idx))
|
||||
this->used_states[state_idx] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Lightweight variant of deserialization, which only tests whether a face of test_state exists.
|
||||
@@ -1956,11 +1976,12 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
|
||||
|
||||
for (const TriangleBitStreamMapping &triangle_id_and_ibit : data.triangles_to_split) {
|
||||
int ibit = triangle_id_and_ibit.bitstream_start_idx;
|
||||
assert(ibit < int(data.bitstream.size()));
|
||||
auto next_nibble = [&data, &ibit = ibit]() {
|
||||
// Stop reading a triangle whose stream is truncated.
|
||||
bool truncated = false;
|
||||
auto next_nibble = [&data, &ibit = ibit, &truncated]() {
|
||||
int n = 0;
|
||||
for (int i = 0; i < 4; ++ i)
|
||||
n |= data.bitstream[ibit ++] << i;
|
||||
if (! data.read_nibble(ibit, n))
|
||||
truncated = true;
|
||||
return n;
|
||||
};
|
||||
// < 0 -> negative of a number of children
|
||||
@@ -1978,6 +1999,8 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
|
||||
};
|
||||
|
||||
int state = num_children_or_state();
|
||||
if (truncated)
|
||||
continue;
|
||||
if (state < 0) {
|
||||
// Root is split.
|
||||
parents_children.clear();
|
||||
@@ -1985,6 +2008,8 @@ bool TriangleSelector::has_facets(const TriangleSplittingData &data, const Enfor
|
||||
do {
|
||||
if (-- parents_children.back() >= 0) {
|
||||
int state = num_children_or_state();
|
||||
if (truncated)
|
||||
break;
|
||||
if (state < 0)
|
||||
// Child is split.
|
||||
parents_children.emplace_back(- state);
|
||||
|
||||
@@ -297,8 +297,20 @@ public:
|
||||
std::fill(used_states.begin(), used_states.end(), false);
|
||||
}
|
||||
|
||||
// Update used states based on the bitstream. It just iterated over the bitstream from the bitstream_start_idx till the end.
|
||||
void update_used_states(size_t bitstream_start_idx);
|
||||
// Update used states from the triangle trees stored between bitstream_start_idx and the end of the bitstream.
|
||||
// Returns false and leaves used states untouched if a tree is truncated or malformed.
|
||||
bool update_used_states(size_t bitstream_start_idx);
|
||||
|
||||
// Read the 4-bit code at bit index ibit (LSB first) and advance ibit past it.
|
||||
// Returns false without advancing when fewer than 4 bits remain.
|
||||
bool read_nibble(int &ibit, int &nibble) const {
|
||||
if (ibit < 0 || static_cast<size_t>(ibit) + 4 > bitstream.size())
|
||||
return false;
|
||||
nibble = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
nibble |= static_cast<int>(bitstream[ibit++]) << i;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
|
||||
Reference in New Issue
Block a user